Kubernetes 提供兩種完全不同的方式來為客戶端提供支持,這些客戶端可能運行在集群中, 也可能與集群的控制面相關(guān), 需要向 API 服務(wù)器完成身份認(rèn)證。
服務(wù)賬號(Service Account) 為 Pod 中運行的進程提供身份標(biāo)識, 并映射到 ServiceAccount 對象。當(dāng)向 API 服務(wù)器執(zhí)行身份認(rèn)證時, 會將自己標(biāo)識為某個用戶(User)。Kubernetes 能夠識別用戶的概念, 但是 Kubernetes 自身并不提供 User API。本篇教程將展示為 Pod 配置 ServiceAccount 的一些方法。
一、準(zhǔn)備
必須擁有一個 Kubernetes 的集群,同時必須配置 kubectl 命令行工具與集群通信。 建議在至少有兩個不作為控制平面主機的節(jié)點的集群上運行本教程。 如果還沒有集群,可以通過 Minikube 構(gòu)建一個自己的集群,或者可以使用下面的 Kubernetes 練習(xí)環(huán)境之一:
- Killercoda;
- 玩轉(zhuǎn) Kubernetes。
二、訪問API服務(wù)器
當(dāng) Pod 與 API 服務(wù)器聯(lián)系時,Pod 會被認(rèn)證為某個特定的 ServiceAccount(例如:default)。 在每個名字空間中,至少存在一個 ServiceAccount。
每個 Kubernetes 名字空間至少包含一個 ServiceAccount:也就是該名字空間的默認(rèn)服務(wù)賬號, 名為 default。如果在創(chuàng)建 Pod 時沒有指定 ServiceAccount,Kubernetes 會自動將該名字空間中名為 default 的 ServiceAccount 分配給該 Pod。
可以檢視剛剛創(chuàng)建的 Pod 的細(xì)節(jié)。例如:
kubectl get pods/<podname> -o yaml
在輸出中,可以看到字段 spec.serviceAccountName。當(dāng)在創(chuàng)建 Pod 時未設(shè)置該字段時, Kubernetes 自動為 Pod 設(shè)置這一屬性的取值。Pod 中運行的應(yīng)用可以使用這一自動掛載的服務(wù)賬號憑據(jù)來訪問 Kubernetes API。 當(dāng) Pod 被身份認(rèn)證為某個 ServiceAccount 時, 其訪問能力取決于所使用的鑒權(quán)插件和策略。
如果不希望 kubelet 自動掛載某 ServiceAccount 的 API 訪問憑據(jù),可以選擇不采用這一默認(rèn)行為。 通過在 ServiceAccount 對象上設(shè)置 automountServiceAccountToken: false,可以放棄在 /var/run/secrets/kubernetes.io/serviceaccount/token 處自動掛載該服務(wù)賬號的 API 憑據(jù)。
例如:
apiVersion: v1 kind: ServiceAccount metadata: name: build-robot automountServiceAccountToken: false ...
也可以選擇不給特定 Pod 自動掛載 API 憑據(jù):
apiVersion: v1 kind: Pod metadata: name: my-pod spec: serviceAccountName: build-robot automountServiceAccountToken: false ...
如果 ServiceAccount 和 Pod 的 .spec 都設(shè)置了 automountServiceAccountToken 值, 則 Pod 上 spec 的設(shè)置優(yōu)先于服務(wù)賬號的設(shè)置。
三、使用多個服務(wù)賬號
每個名字空間都至少有一個 ServiceAccount:名為 default 的默認(rèn) ServiceAccount 資源。 可以用下面的命令列舉當(dāng)前名字空間 中的所有 ServiceAccount 資源:
kubectl get serviceaccounts
輸出類似于:
NAME SECRETS AGE default 1 1d
可以像這樣來創(chuàng)建額外的 ServiceAccount 對象:
kubectl apply -f - <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: build-robot EOF
ServiceAccount 對象的名字必須是一個有效的 DNS 子域名。
如果查詢服務(wù)賬號對象的完整信息,如下所示:
kubectl get serviceaccounts/build-robot -o yaml
輸出類似于:
apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: 2019-06-16T00:12:34Z name: build-robot namespace: default resourceVersion: "272500" uid: 721ab723-13bc-11e5-aec2-42010af0021e
可以使用鑒權(quán)插件來設(shè)置服務(wù)賬號的訪問許可。
要使用非默認(rèn)的服務(wù)賬號,將 Pod 的 spec.serviceAccountName 字段設(shè)置為想用的服務(wù)賬號名稱。只能在創(chuàng)建 Pod 時或者為新 Pod 指定模板時,才可以設(shè)置 serviceAccountName。 不能更新已經(jīng)存在的 Pod 的 .spec.serviceAccountName 字段。
注意:.spec.serviceAccount 字段是 .spec.serviceAccountName 的已棄用別名。 如果要從工作負(fù)載資源中刪除這些字段,請在 Pod 模板上將這兩個字段顯式設(shè)置為空。
如果嘗試了創(chuàng)建前文示例中所給的 build-robot ServiceAccount, 可以通過運行下面的命令來完成清理操作:
kubectl delete serviceaccount/build-robot
四、手動創(chuàng)建API令牌
假設(shè)已經(jīng)有了一個前文所提到的名為 “build-robot” 的服務(wù)賬號。 可以使用 kubectl 為該 ServiceAccount 獲得一個有時限的 API 令牌:
kubectl create token build-robot
這一命令的輸出是一個令牌,可以使用該令牌來將身份認(rèn)證為對應(yīng)的 ServiceAccount。 可以使用 kubectl create token 命令的 –duration 參數(shù)來請求特定的令牌有效期 (實際簽發(fā)的令牌的有效期可能會稍短一些,也可能會稍長一些)。
當(dāng)啟用了 ServiceAccountTokenNodeBinding 和 ServiceAccountTokenNodeBindingValidation 特性,并將 KUBECTL_NODE_BOUND_TOKENS 環(huán)境變量設(shè)置為 true 時, 可以創(chuàng)建一個直接綁定到 Node 的服務(wù)賬號令牌:
KUBECTL_NODE_BOUND_TOKENS=true kubectl create token build-robot --bound-object-kind Node --bound-object-name node-001 --bound-object-uid 123...456
此令牌將有效直至其過期或關(guān)聯(lián)的 Node 或服務(wù)賬戶被刪除。
1、手動為 ServiceAccount 創(chuàng)建長期有效的 API 令牌
如果需要為 ServiceAccount 獲得一個 API 令牌,可以創(chuàng)建一個新的、帶有特殊注解 kubernetes.io/service-account.name 的 Secret 對象。
kubectl apply -f - <<EOF apiVersion: v1 kind: Secret metadata: name: build-robot-secret annotations: kubernetes.io/service-account.name: build-robot type: kubernetes.io/service-account-token EOF
如果通過下面的命令來查看 Secret:
kubectl get secret/build-robot-secret -o yaml
可以看到 Secret 中現(xiàn)在包含針對 “build-robot” ServiceAccount 的 API 令牌。
鑒于所設(shè)置的注解,控制面會自動為該 ServiceAccount 生成一個令牌,并將其保存到相關(guān)的 Secret 中??刂泼嬉矔橐褎h除的 ServiceAccount 執(zhí)行令牌清理操作。
kubectl describe secrets/build-robot-secret
輸出類似于這樣:
Name: build-robot-secret Namespace: default Labels: <none> Annotations: kubernetes.io/service-account.name: build-robot kubernetes.io/service-account.uid: da68f9c6-9d26-11e7-b84e-002dc52800da Type: kubernetes.io/service-account-token Data ==== ca.crt: 1338 bytes namespace: 7 bytes token: ...
當(dāng)刪除一個與某 Secret 相關(guān)聯(lián)的 ServiceAccount 時,Kubernetes 的控制面會自動清理該 Secret 中長期有效的令牌。
五、添加ImagePullSecrets
現(xiàn)在為服務(wù)賬號添加 ImagePullSecrets。首先,生成一個 imagePullSecret; 接下來,驗證該 Secret 已被創(chuàng)建。例如:
按為 Pod 設(shè)置 imagePullSecret 所描述的,生成一個鏡像拉取 Secret:
kubectl create secret docker-registry myregistrykey --docker-server=DUMMY_SERVER \ --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \ --docker-email=DUMMY_DOCKER_EMAIL
檢查該 Secret 已經(jīng)被創(chuàng)建。
kubectl get secrets myregistrykey
輸出類似于這樣:
NAME TYPE DATA AGE myregistrykey kubernetes.io/.dockerconfigjson 1 1d
1、將鏡像拉取 Secret 添加到服務(wù)賬號
接下來更改名字空間的默認(rèn)服務(wù)賬號,將該 Secret 用作 imagePullSecret。
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'
也可以通過手動編輯該對象來實現(xiàn)同樣的效果:
kubectl edit serviceaccount/default
sa.yaml 文件的輸出類似于:
所選擇的文本編輯器會被打開,展示如下所示的配置:
apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: 2021-07-07T22:02:39Z name: default namespace: default resourceVersion: "243024" uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6
使用編輯器,刪掉包含 resourceVersion 主鍵的行,添加包含 imagePullSecrets: 的行并保存文件。對于 uid 而言,保持其取值與讀到的值一樣。
當(dāng)完成這些變更之后,所編輯的 ServiceAccount 看起來像是這樣:
apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: 2021-07-07T22:02:39Z name: default namespace: default uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6 imagePullSecrets: - name: myregistrykey
2、檢查 imagePullSecrets 已經(jīng)被設(shè)置到新 Pod 上
現(xiàn)在,在當(dāng)前名字空間中創(chuàng)建新 Pod 并使用默認(rèn) ServiceAccount 時, 新 Pod 的 spec.imagePullSecrets 會被自動設(shè)置。
kubectl run nginx --image=nginx --restart=Never kubectl get pod nginx -o=jsonpath='{.spec.imagePullSecrets[0].name}{"\n"}'
輸出為:
myregistrykey
六、服務(wù)賬號令牌卷投射
為了啟用令牌請求投射,必須為 kube-apiserver 設(shè)置以下命令行參數(shù):
- –service-account-issuer:定義服務(wù)賬號令牌發(fā)放者的身份標(biāo)識(Identifier)??梢远啻沃付?–service-account-issuer 參數(shù),對于需要變更發(fā)放者而又不想帶來業(yè)務(wù)中斷的場景, 這樣做是有用的。如果這個參數(shù)被多次指定,其第一個參數(shù)值會被用來生成令牌, 而所有參數(shù)值都會被用來確定哪些發(fā)放者是可接受的。所運行的 Kubernetes 集群必須是 v1.22 或更高版本才能多次指定 –service-account-issuer。
- –service-account-key-file:給出某文件的路徑,其中包含 PEM 編碼的 x509 RSA 或 ECDSA 私鑰或公鑰,用來檢查 ServiceAccount 的令牌。所指定的文件中可以包含多個秘鑰,并且可以多次使用此參數(shù),每個參數(shù)值為不同的文件。 多次使用此參數(shù)時,由所給的秘鑰之一簽名的令牌會被 Kubernetes API 服務(wù)器認(rèn)為是合法令牌。
- –service-account-signing-key-file:指向某文件的路徑,其中包含當(dāng)前服務(wù)賬號令牌發(fā)放者的私鑰。 此發(fā)放者使用此私鑰來簽署所發(fā)放的 ID 令牌。
- –api-audiences(可以省略):為 ServiceAccount 令牌定義其受眾(Audiences)。 服務(wù)賬號令牌身份檢查組件會檢查針對 API 訪問所使用的令牌, 確認(rèn)令牌至少是被綁定到這里所給的受眾之一。 如果 api-audiences 被多次指定,則針對所給的多個受眾中任何目標(biāo)的令牌都會被 Kubernetes API 服務(wù)器當(dāng)做合法的令牌。如果指定了
- –service-account-issuer 參數(shù),但沒有設(shè)置 –api-audiences,則控制面認(rèn)為此參數(shù)的默認(rèn)值為一個只有一個元素的列表, 且該元素為令牌發(fā)放者的 URL。
kubelet 還可以將 ServiceAccount 令牌投射到 Pod 中??梢灾付钆频钠谕麑傩?, 例如受眾和有效期限。這些屬性在 default ServiceAccount 令牌上無法配置。 當(dāng) Pod 或 ServiceAccount 被刪除時,該令牌也將對 API 無效。
可以使用類型為 ServiceAccountToken 的投射卷來為 Pod 的 spec 配置此行為。
來自此投射卷的令牌是一個 JSON Web Token (JWT)。 此令牌的 JSON 載荷遵循明確定義的模式,綁定到 Pod 的令牌的示例載荷如下:
{ "aud": [ # 匹配請求的受眾,或當(dāng)沒有明確請求時匹配 API 服務(wù)器的默認(rèn)受眾 "https://kubernetes.default.svc" ], "exp": 1731613413, "iat": 1700077413, "iss": "https://kubernetes.default.svc", # 匹配傳遞到 --service-account-issuer 標(biāo)志的第一個值 "jti": "ea28ed49-2e11-4280-9ec5-bc3d1d84661a", # ServiceAccountTokenJTI 特性必須被啟用才能出現(xiàn)此申領(lǐng) "kubernetes.io": { "namespace": "kube-system", "node": { # ServiceAccountTokenPodNodeInfo 特性必須被啟用,API 服務(wù)器才會添加此節(jié)點引用申領(lǐng) "name": "127.0.0.1", "uid": "58456cb0-dd00-45ed-b797-5578fdceaced" }, "pod": { "name": "coredns-69cbfb9798-jv9gn", "uid": "778a530c-b3f4-47c0-9cd5-ab018fb64f33" }, "serviceaccount": { "name": "coredns", "uid": "a087d5a0-e1dd-43ec-93ac-f13d89cd13af" }, "warnafter": 1700081020 }, "nbf": 1700077413, "sub": "system:serviceaccount:kube-system:coredns" }
1、啟動使用服務(wù)賬號令牌投射的Pod
要為某 Pod 提供一個受眾為 vault 并且有效期限為 2 小時的令牌,可以定義一個與下面類似的 Pod 清單:
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - image: nginx name: nginx volumeMounts: - mountPath: /var/run/secrets/tokens name: vault-token serviceAccountName: build-robot volumes: - name: vault-token projected: sources: - serviceAccountToken: path: vault-token expirationSeconds: 7200 audience: vault
創(chuàng)建此 Pod:
kubectl create -f https://k8s.io/examples/pods/pod-projected-svc-token.yaml
kubelet 組件會替 Pod 請求令牌并將其保存起來;通過將令牌存儲到一個可配置的路徑以使之在 Pod 內(nèi)可用;在令牌快要到期的時候刷新它。kubelet 會在令牌存在期達到其 TTL 的 80% 的時候或者令牌生命期超過 24 小時的時候主動請求將其輪換掉。
應(yīng)用負(fù)責(zé)在令牌被輪換時重新加載其內(nèi)容。通常而言,周期性地(例如,每隔 5 分鐘) 重新加載就足夠了,不必跟蹤令牌的實際過期時間。
七、服務(wù)賬號分發(fā)者
如果在集群中已經(jīng)為 ServiceAccount 啟用了令牌投射, 那么也可以利用其發(fā)現(xiàn)能力。Kubernetes 提供一種方式來讓客戶端將一個或多個外部系統(tǒng)進行聯(lián)邦, 作為標(biāo)識提供者(Identity Provider),而這些外部系統(tǒng)的角色是依賴方(Relying Party)。
注意:
- 分發(fā)者的 URL 必須遵從 OIDC 發(fā)現(xiàn)規(guī)范。 實現(xiàn)上,這意味著 URL 必須使用 https 模式,并且必須在路徑 {service-account-issuer}/.well-known/openid-configuration 處給出 OpenID 提供者(Provider)的配置信息。
- 如果 URL 沒有遵從這一規(guī)范,ServiceAccount 分發(fā)者發(fā)現(xiàn)末端末端就不會被注冊也無法訪問。
當(dāng)此特性被啟用時,Kubernetes API 服務(wù)器會通過 HTTP 發(fā)布一個 OpenID 提供者配置文檔。 該配置文檔發(fā)布在 /.well-known/openid-configuration 路徑。 這里的 OpenID 提供者配置(OpenID Provider Configuration)有時候也被稱作 “發(fā)現(xiàn)文檔(Discovery Document)”。 Kubernetes API 服務(wù)器也通過 HTTP 在 /openid/v1/jwks 處發(fā)布相關(guān)的 JSON Web Key Set(JWKS)。
使用 RBAC 的集群都包含一個的默認(rèn) RBAC ClusterRole, 名為 system:service-account-issuer-discovery。 默認(rèn)的 RBAC ClusterRoleBinding 將此角色分配給 system:serviceaccounts 組, 所有 ServiceAccount 隱式屬于該組。這使得集群上運行的 Pod 能夠通過它們所掛載的服務(wù)賬號令牌訪問服務(wù)賬號發(fā)現(xiàn)文檔。 此外,管理員可以根據(jù)其安全性需要以及期望集成的外部系統(tǒng),選擇是否將該角色綁定到 system:authenticated 或 system:unauthenticated。
JWKS 響應(yīng)包含依賴方可以用來驗證 Kubernetes 服務(wù)賬號令牌的公鑰數(shù)據(jù)。 依賴方先會查詢 OpenID 提供者配置,之后使用返回響應(yīng)中的 jwks_uri 來查找 JWKS。
在很多場合,Kubernetes API 服務(wù)器都不會暴露在公網(wǎng)上,不過對于緩存并向外提供 API 服務(wù)器響應(yīng)數(shù)據(jù)的公開末端而言,用戶或者服務(wù)提供商可以選擇將其暴露在公網(wǎng)上。 在這種環(huán)境中,可能會重載 OpenID 提供者配置中的 jwks_uri,使之指向公網(wǎng)上可用的末端地址,而不是 API 服務(wù)器的地址。 這時需要向 API 服務(wù)器傳遞 –service-account-jwks-uri 參數(shù)。 與分發(fā)者 URL 類似,此 JWKS URI 也需要使用 https 模式。