當前位置: 妍妍網 > 碼農

K8s 許可權管理詳解

2024-07-01碼農

戳下方名片 ,關註並 星標

回復「 1024 」獲取 2TB 學習資源!

👉 體系化學習:

特色專欄

/ /

/ /

/ /

/ /

/ /

大家好,我是民工哥!

今天來聊一聊 k8s-許可權管理!

身份認證

我們在目前的k8s集群環境裏面,只能在master節點上執行kubectl的一些命令,在其他節點上執行就會報錯。

# 看一下是不是
[root@node1 ~]# kubectl get nodes
E0220 12:50:15.695133 6091 memcache.go:238] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0220 12:50:15.695771 6091 memcache.go:238] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0220 12:50:15.697555 6091 memcache.go:238] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0220 12:50:15.699191 6091 memcache.go:238] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0220 12:50:15.700655 6091 memcache.go:238] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?

我們可以看到在node1上執行kubectl get nodes都會報錯,那就更不談建立pod之類的操作了,那為什麽master可以而其他節點不行呢?

這是因為在master節點上是有一個kubeconfig的。

[root@master ~]# env |grep -i kubeconfig
KUBECONFIG=/etc/kubernetes/admin.conf

我們可以看到在master節點上是有一個環境變量載入了這個admin.conf這個檔的,這個檔就是k8s集群預設的管理員檔,換一種說法,只要你有本事偷走這個檔並且保障你的網路跟這個集群的網路是通的,那麽恭喜你,得到了一個k8s集群。

node節點操作

我們現在來將這個admin.conf傳到node1上,再來看看node1能不能去執行命令。

# 傳檔
[root@master ~]# scp /etc/kubernetes/admin.conf node1:~
admin.conf 100% 5669 6.2MB/s 00:00
# 在node1上執行命令看看效果
[root@node1 ~]# kubectl get node --kubeconfig=admin.conf
NAME STATUS ROLES AGE VERSION
master Ready control-plane,master 43d v1.26.0
node1 Ready node1 43d v1.26.0
node2 Ready node2 43d v1.26.0

我們透過這個小實驗看到,node1節點確實是可以獲取到節點資訊了,但是他執行的命令跟master上有所不同,在node1上執行的時候他是需要執行配置檔的,如果你不想執行的話可以將這個註冊到環境變量裏面。

[root@node1 ~]# echo "export KUBECONFIG=/root/admin.conf" >> /etc/profile
[root@node1 ~]# tail -1 /etc/profile
export KUBECONFIG=/root/admin.conf

只需要這樣就好了。

但是這樣存在一個問題,他們用的都是管理員的配置檔,那麽就相當於他們都是管理員,對集群有全部許可權,我們追求是的最小許可權原則,就是我給你的許可權正好能夠讓你完成屬於你自己的任務,多的許可權不應該有,那麽我們能不能像Linux一樣建立普通使用者,給普通使用者客製許可權呢?當然是可以的。

建立普通使用者並授權

我們現在來建立一個普通使用者zhangsan並授權。

生成私鑰

# 使用openssl生成一個rsa型別的私鑰,私鑰檔名是client.key 2048位元
[root@master ca]# openssl genrsa -out client.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
........................+++++
...............................................................................................................................................................................................................................................+++++
e is 65537 (0x010001)
[root@master ca]# ls
client.key

生成zhangsan使用者證書請求檔

# 使用client.key 生成一個新的檔叫做client.csr
[root@master ca]# openssl req -new -key client.key -subj "/CN=zhangsan" -out client.csr
[root@master ca]# ls
client.csr client.key

為zhangsan使用者頒發證書

zhangsan使用者如何將請求發送給k8s的ca進行證書頒發呢?這個時候我們可以使用k8s內建的ca來頒發證書

# k8s的ca在/etc/kubernetes/pki下
[root@master ca]# openssl x509 -req -in client.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out client.crt -days 3650
Signature ok
subject=CN = zhangsan
Getting CA Private Key
# 拷貝ca到當前目錄
[root@master ca]# cp /etc/kubernetes/pki/ca.crt .
[root@master ca]# ls
ca.crt client.crt client.csr client.key

建立名稱空間及pod

[root@master ca]# kubectl create ns zhangsan
namespace/zhangsan created
# 切到這個名稱空間
[root@master ca]# kubectl config set-context --current --namespace zhangsan
Context "kubernetes-admin@kubernetes" modified.
# 建立一個pod
[root@master ca]# kubectl run test01 --image nginx --image-pull-policy IfNotPresent
pod/test01 created
[root@master ca]# kubectl get pods
NAME READY STATUS RESTARTS AGE
test01 1/1 Running 0 13s

建立角色

角色是什麽呢?我們可以這樣想,假如我們現在有增刪改查4個許可權,使用者用張三,李四,王五,那我們現在給他們授權的話只能是一個許可權一個許可權的去給,萬一後面又新增了使用者我們依舊是一個個去指定,過於麻煩,而角色就是介於許可權與使用者之間的一個樣版,就像這樣。

  • • 我們指定了一個角色是管理員,擁有增刪改查4個許可權

  • • 一個是開發的角色,擁有增改查的許可權

  • • 一個是普通使用者角色,只有查的許可權

  • 我們指定好這3個樣版之後,後面新來的使用者只需要知道他的作用是什麽,比如他就是一個普通使用者,那我們直接把這個樣版給他套上,那他就只有查的許可權,來了一個開發者,那我們就給他開發的這個角色樣版,他就自然而然的擁有增改查的許可權。

    # 這裏的pod-reader是role的名字 後面的--verb就是這個角色所包含哪些許可權,並且這個角色所能操作的資源物件僅僅只有pod
    [root@master ca]# kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
    role.rbac.authorization.k8s.io/pod-reader created

    繫結角色給使用者

    # 建立一個rolebinding名字叫zhangsan,這個zhangsan並不是使用者張三,而是這個rolebinding的名字,後面--user這個才是使用者張三
    [root@master ca]# kubectl create rolebinding zhangsan --role pod-reader --user zhangsan
    rolebinding.rbac.authorization.k8s.io/zhangsan created
    [root@master ca]# kubectl get rolebindings.rbac.authorization.k8s.io 
    NAME ROLE AGE
    zhangsan Role/pod-reader 4s

    編輯kubeconfig檔

    關於這個檔的框架,我們可以到官網去找到地址:https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/,在官網找到之後我們只需要做一些修改就行,改成這樣就可以,直接復制這個去改也行。

    apiVersion:v1
    kind:Config
    clusters:
    -cluster:
    name:cluster-zs
    users:
    -name:zhangsan
    contexts:
    -context:
    name:context-zs
    namespace:zhangsan
    current-context:"context-zs"


    這個檔就寫好了,但是目前來看他與管理員的那個admin.conf好像不一樣,那個檔裏面內容很多,這個很少,這是因為我們還沒有將剛剛建立出來的那些金鑰檔嵌入進去。

    嵌入金鑰檔

    # 1. 嵌入ca檔
    # set-cluster與剛剛檔裏的一樣就好了 server地址就是master的ip加上6443埠 
    [root@master ca]# kubectl config --kubeconfig=kube-zhangsan set-cluster cluster-zs --server=https://192.168.200.200:6443 --certificate-authority=ca.crt --embed-certs=true
    Cluster "cluster-zs" set.
    # 2. 嵌入client
    [root@master ca]# kubectl config --kubeconfig=kube-zhangsan set-credentials zhangsan --client-certificate=client.crt --client-key=client.key --embed-certs=true
    User "zhangsan" set.
    # 3. 設定上下文資訊
    [root@master ca]# kubectl config --kubeconfig=kube-zhangsan set-context context-zs --cluster=cluster-zs --namespace=zhangsan --user=zhangsan
    Context "context-zs" modified.

    這3個操作搞定之後,你再去看看這個檔,你會發現他跟admin.conf是一樣一樣的了。

    驗證許可權

    這個檔我們就算搞定了,我們來看看使用這個檔所擁有的許可權是否是與我們預期的一樣。

    [root@node1 ~]# kubectl get pods --kubeconfig=kube-zhangsan
    NAME READY STATUS RESTARTS AGE
    test01 1/1Running09m
    # 可以看到pod,我們嘗試一下能否建立pod
    [root@node1 ~]# kubectl run test02 --image nginx --kubeconfig=kube-zhangsan
    Errorfrom server (Forbidden): pods is forbidden:User"zhangsan" cannot create resource "pods"in API group""in the namespace"zhangsan"
    # 我們看報錯資訊,使用者zhangsan是不能建立的,我們來看看除了pod之外的其他資源是否可見
    [root@node1 ~]# kubectl get ns --kubeconfig=kube-zhangsan
    Errorfrom server (Forbidden): namespaces is forbidden:User"zhangsan" cannot list resource "namespaces"in API group"" at the cluster scope

    現在這個檔符合我們預期的許可權,那麽這就是建立一個使用者並授權的過程。

    靜態token登入

    這個方法用人話來講就是,帳號密碼登入,靜態的方式就是建立一個csv檔,csv檔的格式 token,user,id ,token這一欄我們可以使用openssl生成。

    生成token

    # 註意檔位置,最好放在/etc/kubernetes/pki下,因為k8s預設只對/etc/kubernetes這個目錄有許可權操作,放在其他位置可能會產生許可權錯誤
    [root@master pki]# openssl rand -hex 10> jerry.csv
    [root@master pki]# cat jerry.csv
    # 這裏的使用者名稱和id可以自己改動
    3127c2e2b863d4c23878a,jerry,2000
    在apiserver加入參數
    # 預設情況下你剛剛寫的檔與集群是沒有任何關聯的,如果想要產生作用需要在kube-apiserver檔加入參數
    [root@master manifests]# vim /etc/kubernetes/manifests/kube-apiserver.yaml
    spec:
    containers:
    - command:
    - kube-apiserver
    # 在這裏加上 --token-auth-file後面就是你剛剛的那個檔
    ---token-auth-file=/etc/kubernetes/pki/jerry.csv
    ---advertise-address=192.168.200.200
    ---allow-privileged=true
    # 然後重新開機kubelet
    [root@master pki]# systemctl restart kubelet

    嘗試登入集群

    [root@node1 pki]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" get pod -n default
    Unable to connect to the server: x509: certificate signed by unknown authority

    他會有一個報錯,但是我們現在沒有使用x509的證書啊,所以我們需要讓他跳過安全認證

    帶上參數再次嘗試

    [root@node1 pki]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" get pod --insecure-skip-tls-verify=true -n zhangsan
    Error from server (Forbidden): pods is forbidden: User "jerry" cannot list resource "pods" in API group "" in the namespace "zhangsan"

    現在我們再來看,他報的錯是不是跟剛剛不一樣了,這個報錯說的是jerry這個使用者沒有許可權,能看到這個其實就說明我們已經可以登入了,只是沒有許可權看到一些資訊罷了。

    角色授權

    上面我們提到了使用者的登入,提到了一點點授權,現在開始聊授權的那些事,預設情況下k8s采用的是Node和RBAC的鑒權模式。RBAC就是基於角色的存取控制 R就是role嘛,我們可以在 kube-apiserver 檔裏面看到。

    spec:
    containers:
    -command:
    -kube-apiserver
    ---token-auth-file=/etc/kubernetes/pki/jerry.csv
    ---advertise-address=192.168.200.200
    ---allow-privileged=true
    # 就是這一行
    ---authorization-mode=Node,RBAC
    剛剛我們不是使用jerry使用者登入但是沒有任何許可權嗎?我們現在將這一行參數改掉
    # 將之前的註釋掉,然後寫一行新的
    # - --authorization-mode=Node,RBAC
    # 這個是總是允許,不會鑒權,你能登入就有許可權,這個模式僅用於測試
    ---authorization-mode=AlwaysAllow

    # 重新開機kubelet
    [root@master manifests]# systemctl restart kubelet

    然後我們來到node節點再嘗試一下jerry使用者。

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" get pod --insecure-skip-tls-verify=true -n zhangsan
    NAME READY STATUS RESTARTS AGE
    test01 1/1 Running 0 56m

    我們發現他確實有許可權檢視了,好了但是我們的重點並不是這個,我們將他改回來。

    role與rolebinding

    是透過名稱空間來授權的,你在哪個名稱空間建立的角色,那麽這個角色只有這個名稱空間下的許可權 ,rolebinding就是將角色與使用者進行繫結。

    建立角色

    剛剛我們不是有一個Jerry使用者可以登入集群,但是沒有任何許可權嗎?那我們現在來授權。

    # 不知道參數是怎麽來的可以使用kubectl create role --help 裏面有範例
    [root@master role]# kubectl create role jerry --verb=get --verb=list --verb=watch --resource=pods --dry-run=client -o yaml > jerry.yaml
    [root@master role]# kubectl apply -f jerry.yaml 
    role.rbac.authorization.k8s.io/jerry created
    [root@master role]# kubectl get role
    NAME CREATED AT
    jerry 2024-02-20T09:31:35Z
    pod-reader 2024-02-20T05:23:30Z

    我們現在有2個role一個是之前的,一個jerry就是剛剛我們建立出來的。現在我們角色有了,但是jerry使用者依舊是查不到任何資訊的,因為我們沒有對他進行繫結。

    rolebinding

    # 註意一個坑,當這個使用者是token登入的時候必須指定他的token,老版本不會有這個問題,新版本不指定的話依然是沒有許可權的,註意一下
    [root@master role]# kubectl create rolebinding jerry --role=jerry --user=jerry --token="3127c2e2b863d4c23878a" --dry-run=client -o yaml > rolebinding.yaml
    [root@master role]# kubectl apply -f rolebinding.yaml 
    rolebinding.rbac.authorization.k8s.io/jerry created
    [root@master role]# kubectl get rolebindings.rbac.authorization.k8s.io 
    NAME ROLE AGE
    jerry Role/jerry 5s
    zhangsan Role/pod-reader 4h3m

    這裏的每一步操作都應該能看懂吧,然後我們回到node節點上使用jerry來查一下zhangsan名稱空間下的pod。

    驗證許可權

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" get pod --insecure-skip-tls-verify=true -n zhangsan
    NAME READY STATUS RESTARTS AGE
    test01 1/1 Running 0 4h24m

    我們可以看到,他現在就可以看到pod的資訊了,但是我們在指定許可權的時候是沒有給他建立的許可權的,那麽他肯定不能建立pod,但是我們現在想要他可以建立pod怎麽辦呢?

    也是很簡單,只需要給角色加上一個許可權就可以了。

    修改許可權

    修改許可權我們只需要修改jerry.yaml

    apiVersion:rbac.authorization.k8s.io/v1
    kind:Role
    metadata:
    creationTimestamp:null
    name:jerry
    rules:
    -apiGroups:
    -""
    resources:
    -pods
    verbs:
    -get
    -list
    -watch
    # 加上這個他就可以建立pod了,如果加上delete那麽他就可以刪除
    -create

    然後我們再apply這個檔

    [root@master role]# kubectl apply -f jerry.yaml 
    role.rbac.authorization.k8s.io/jerry configured

    驗證是否成功增加許可權

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" --insecure-skip-tls-verify=true -n zhangsan run test02 --image nginx
    pod/test02 created

    我們可以看到pod被建立出來了,說明剛剛的許可權已經增加上了,這裏我們僅僅只是針對pod的操作,如果我要建立一個deployment控制器呢?

    deploymentde的操作

    我們仔細觀察一下jerry.yaml這個檔,發現裏面有一行寫的是pods,那我們是不是直接在這裏加上deployments就好了呢?我們來試試。

    # 修改yaml檔
    apiVersion:rbac.authorization.k8s.io/v1
    kind:Role
    metadata:
    creationTimestamp:null
    name:jerry
    rules:
    -apiGroups:
    -""
    resources:
    -pods
    -deployments
    verbs:
    -get
    -list
    -watch
    -create

    然後我們apply這個檔。

    [root@master role]# kubectl apply -f jerry.yaml 
    role.rbac.authorization.k8s.io/jerry configured

    我們來看看是不是能夠建立deployment了。

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" --insecure-skip-tls-verify=true -n zhangsan create deployment test03 --image nginx
    error: failed to create deployment: deployments.apps is forbidden: User "jerry" cannot create resource "deployments" in API group "apps" in the namespace "zhangsan"

    喔謔,報錯了,我們不是加上了deployment嗎?

    這其實是因為我們還需要給他指定apiGroup,光指定資源是不行的,能建立pod是因為pod他的apiVersion就是v1,而deployment的apiVersion是apps/v1所以他會報錯,那我們再來修改一下檔。

    apiVersion:rbac.authorization.k8s.io/v1
    kind:Role
    metadata:
    creationTimestamp:null
    name:jerry
    rules:
    -apiGroups:
    -""
    # 加上這個,如果你想要建立其他的資源,那麽你也要在這裏寫上
    # 查詢apiVersion很簡單,你可以使用kubectl create xxx --dry-run 的方式,也可以直接 kubectl api-version去查,查到之後填到這裏
    -"apps"
    resources:
    -pods
    -deployments
    verbs:
    -get
    -list
    -watch
    -create
    ·

    後我們apply之後再來建立。

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" --insecure-skip-tls-verify=true -n zhangsan create deployment test03 --image nginx
    deployment.apps/test03 created

    我們現在是可以建立deployment了,那我們想更新他的副本數量也是可以的嘛?來看看。

    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" --insecure-skip-tls-verify=true -n zhangsan scale deployment test03 --replicas 3
    Error from server (Forbidden): deployments.apps "test03" is forbidden: User "jerry" cannot patch resource "deployments/scale" in API group "apps" in the namespace "zhangsan"

    他又報錯了,他說不能patch,那我們在verb裏面加上個試試看呢,等一下,註意看完報錯,他說resource裏面是deployments/scale我們好像也沒有給他這個資源,一並加上。

    最終的yaml檔是這樣的:

    apiVersion:rbac.authorization.k8s.io/v1
    kind:Role
    metadata:
    creationTimestamp:null
    name:jerry
    rules:
    -apiGroups:
    -""
    -"apps"
    resources:
    -pods
    -deployments/scale
    -deployments
    verbs:
    -get
    -patch
    -list
    -watch
    -create

    我們apply之後再來試試看呢。

    [root@master role]# kubectl apply -f jerry.yaml 
    role.rbac.authorization.k8s.io/jerry configured
    # 修改副本數
    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443" --token="3127c2e2b863d4c23878a" --insecure-skip-tls-verify=true -n zhangsan scale deployment test03 --replicas 3
    deployment.apps/test03 scaled

    我們可以看到現在他可以了,這個yaml檔還可以有另外一種格式:

    apiVersion:rbac.authorization.k8s.io/v1
    kind:Role
    metadata:
    creationTimestamp:null
    name:jerry
    rules:
    -apiGroups:["","apps"]
    resources:["pods","deployments"]
    verbs:["get","delete","watch"]

    這種方式也可以,喜歡用哪種就用哪種,無所謂的嘛。

    這個就是role和rolebinding!

    clusterrole和clusterrolebinding

    clusterrole對於role來說,role是屬於某個名稱空間的,而clusterrole是屬於整個集群的,clusterrole可以進行clusterrolebinding,也可以進行rolebinding,rolebinding的時候指定一下名稱空間就可以了。

    使用rolebinding的時候它就相當於是將clusterrole的許可權樣版給了某個名稱空間下的某個使用者,也就是說在你進行rolebinding的時候你就當這個clusterrole就是一個普通的沒有指定特定名稱空間的role。

    我們可以這樣想一下,我們有很多個名稱空間,然後每個名稱空間裏的使用者許可權其實都是差不多的,那麽如果我要是使用role的話,我就需要每個名稱空間下都要去建立role,費時費力。

    但是我們使用clusterrole的話,所有名稱空間都可以看到這個clusterrole,那麽就無需每個名稱空間都去建立role了,直接rolebingding就好了。

    建立一個新的使用者,使用token

    [root@master pki]# openssl rand -hex 10 >> /etc/kubernetes/pki/jerry.csv
    [root@master pki]# cat jerry.csv
    # 這裏的token不一樣長可能是因為我按a插入的時候多按了一下,沒什麽太大的問題,token是可以自己寫的
    3127c2e2b863d4c23878a,jerry,2000
    958a15cfa9431e088e0b,tom,2001

    建立clusterrole

    這個建立方法與role是一樣的。

    [root@masterrole]# kubectl create clusterrole cluster-pod --verb=get,list,watch --resource=pods --dry-run=client -o yaml > clusterrole.yaml
    [root@masterrole]# cat clusterrole.yaml
    apiVersion:rbac.authorization.k8s.io/v1
    kind:ClusterRole
    metadata:
    creationTimestamp:null
    name:cluster-pod
    rules:
    -apiGroups:
    -""
    resources:
    -pods
    verbs:
    -get
    -list
    -watch

    clusterrolebinding

    [root@master role]# kubectl create clusterrolebinding cluster-tom --clusterrole=cluster-pod --user=tom --token="958a15cfa9431e088e0b"

    驗證許可權

    在驗證許可權之前我建議結束shell重新登入一下,或者重新開機一下節點,因為你直接登入的話他可能會報錯。

    error: You must be logged in to the server (Unauthorized)

    我就遇到這個問題了,我的解決方式是將kube-system裏面的apiserver這個pod重新開機了。

    # 檢視pod
    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443"--token="958a15cfa9431e088e0bb"--insecure-skip-tls-verify=true-n zhangsan get pods
    NAME READY STATUS RESTARTS AGE
    test01 1/1Running06h29m
    test03-6484c64bb6-88xlr1/1Running081m
    test03-6484c64bb6-9hf4l1/1Running075m
    test03-6484c64bb6-w4zwk 1/1Running075m
    # 檢視其他名稱空間的pod
    [root@node1 manifests]# kubectl --server="https://192.168.200.200:6443"--token="958a15cfa9431e088e0bb"--insecure-skip-tls-verify=true-n kube-system get pods
    NAME READY STATUS RESTARTS AGE
    coredns-5bbd96d687-9tsbb1/1Running38(7h6m ago)42d
    coredns-5bbd96d687-q6dl8 1/1Running38(7h6m ago)42d
    etcd-master 1/1Running42(7h6m ago)44d
    kube-apiserver-master 1/1Running013m
    kube-controller-manager-master 1/1Running63(3h1m ago)44d
    kube-proxy-mp98s 1/1Running40(7h6m ago)44d
    kube-proxy-snk8k 1/1Running46(7h6m ago)44d
    kube-proxy-xmxpj 1/1Running38(7h6m ago)44d
    kube-scheduler-master 1/1Running61(3h1m ago)44d
    metrics-server-54b5b8fb6-v4cqx 1/1Running29(7h4m ago) 7d1h

    我們可以看到,他可以看到其他名稱空間下的pod,這就是role和clusterrole的區別了。

    至於他能不能建立pod,能不能建立deployment,這些東西就是跟role是一樣的了。

    作者:FuShudi 連結:

    https://cnblogs.com/fsdstudy/p/18023589

    👍 如果你喜歡這篇文章,請點贊並分享給你的朋友!

    公眾號讀者專屬技術群

    構建高品質的技術交流社群,歡迎從事後端開發、運維技術進群( 備註崗位,已在技術交流群的請勿重復添加微信好友 )。主要以技術交流、內推、行業探討為主,請文明發言。 廣告人士勿入,切勿輕信私聊,防止被騙。

    掃碼加我好友,拉你進群

    PS:因為公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下 在看 ,加個 星標 ,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。 在看 支持我們吧!