Kubernetes 的 Service Account (服務帳戶) 是一個由 Kubernetes 管理的帳戶類型,在管理上可說是特別方便,但是在剛接觸到這種帳戶類型時,不是很容易理解應用的情境。這篇文章是我閱讀了好多份文件之後,整理出來的完整脈絡,相信可以對服務帳戶有一定程度的理解。
帳號類型
Kubernetes 的帳號有兩種類型,分別為:
-
使用者帳戶 (Normal Users)
任何人想要連接並存取 Kubernetes 叢集,都需要先建立一個 "使用者帳戶" 並將憑證資訊提供給用戶端 (如: kubectl
),以便通過 Kubernetes 的 API server 的認證 (Authentication)。
我覺得這個名字應該稱為 User Accounts
會比較好理解,但是 Kubernetes 官網稱一般使用者 (Normal Users)。
-
服務帳戶 (Service Accounts)
任何跑在 Pod 裡面的容器想要存取 Kubernetes 的 API 伺服器 (kube-apiserver
),就需要先有一個 "服務帳戶" 綁定在 Pod 身上,然後以便通過 Kubernetes 的 API 伺服器的身份認證 (Authentication)。
體驗命名空間預設的服務帳戶 (Service Account)
當你建立 namespace
的時候,預設就會幫你建立好一個名為 default
的服務帳戶:
-
建立 dev
命名空間
kubectl create namespace dev
kubectl label namespace dev name=dev
-
取得 dev
命名空間下的 serviceaccounts
kubectl get serviceaccounts --namespace=dev
NAME SECRETS AGE
default 1 9s
-
取得 dev
命名空間下的 default
服務帳戶內容
kubectl get serviceaccounts default -n=dev -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2022-08-23T14:55:51Z"
name: default
namespace: dev
resourceVersion: "1434536"
selfLink: /api/v1/namespaces/dev/serviceaccounts/default
uid: 3b750bc5-fd6c-43b0-9c64-4a4700f522ae
secrets:
- name: default-token-xpqc7
他會自動綁定一個 secrets
保存該服務帳戶的 Token 資訊
-
取得 dev
命名空間下的 default
服務帳戶綁定的 secrets
內容
kubectl get secrets default-token-xpqc7 -n=dev -o yaml
apiVersion: v1
data:
ca.crt: DATA+OMITTED
namespace: ZGV2
token: TOKEN+OMITTED
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 3b750bc5-fd6c-43b0-9c64-4a4700f522ae
creationTimestamp: "2022-08-23T14:55:51Z"
name: default-token-xpqc7
namespace: dev
resourceVersion: "1434535"
selfLink: /api/v1/namespaces/dev/secrets/default-token-xpqc7
uid: 868a7d4f-74b8-4be4-8c0e-b9d5a3e678b2
type: kubernetes.io/service-account-token
體驗 Pod 如何使用服務帳戶 (Service Account)
-
我們先在 dev
命名空間中建立一個 Pod,但不特別指定 default
服務帳戶
kubectl run microbot --image=dontrebootme/microbot:v1 -n dev
-
事實上,所有的 Pod 預設會加入 default
服務帳戶
在 namespace
建立 Pod 的時候,如果沒有特別指定 spec.serviceAccountName
的話,Kubernetes 也會預設幫你加上相同命名空間下的 default
服務帳戶。因此,每個 Pod 其實都一定會綁定一個服務帳戶。
kubectl get microbot -n dev -o yaml
此時你會看到如下的 YAML 檔,有個 serviceAccountName: default
已經被自動設定進去了:
apiVersion: v1
kind: Pod
metadata:
labels:
run: microbot
name: microbot
namespace: dev
spec:
containers:
- image: dontrebootme/microbot:v1
imagePullPolicy: IfNotPresent
name: microbot
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-dfs8b
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: microk8s-vm
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
...
而且,在 Pod 裡面預設會掛載一個 /var/run/secrets/kubernetes.io/serviceaccount
目錄!
-
查看 Pod 裡面的 /var/run/secrets/kubernetes.io/serviceaccount
目錄內容
kubectl exec microbot -it -n dev -- sh
ls -laF /var/run/secrets/kubernetes.io/serviceaccount
看起來他會自動把 serviceaccounts
所包含的 secrets
內容全部掛載到這個目錄下!
-
在 Pod 的容器中對 kube-apiserver
發送 HTTP 要求
apk update
apk add curl
curl --insecure https://kubernetes.default.svc.cluster.local:443/api/
在沒有帶入 TOKEN 的情況下,會是以 system:anonymous
匿名使用者存取:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
-
帶入 default
服務帳戶的 TOKEN 來存取 kube-apiserver
CACERT='/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/
此時你會看到 API 要求已經通過驗證:
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "172.25.239.227:16443"
}
]
}
以上就是在 Pod 裡面使用「服務帳戶」的標準方法! 👍
替服務帳戶 (Service Account) 加入角色權限
事實上,這個「通過身份驗證」的 TOKEN 雖然可以呼叫 kube-apiserver
的部分 API,但事實上目前這個預設的服務帳戶並無法存取任何 K8s 叢集中的資源,我們還必須透過 RBAC 機制,先建立一個 Role
並指派權限進去,再透過 RoleBinding
將此帳號綁定,才能賦予他足夠的權限存取資源。
-
先試圖取得 PodList
清單
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
你會得到以下錯誤訊息:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:dev:default\" cannot list resource \"pods\" in API group \"\" in the namespace \"dev\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
此時我們會知道我們的 User 為 system:serviceaccount:dev:default
,且 API group 為 ""
,命名空間(namespace
)為 dev
,資源類型(kind
)為 pods
除此之外,你也可以透過 kubectl auth can-i
命令,快速查出特定使用者是否具有特定資源的權限,相當實用!
kubectl auth can-i get pods -n=dev --as=system:serviceaccount:dev:default
他會很簡單的回你 yes
或 no
,以目前的狀態來說,應該會回 no
才對! 👍
-
建立 Role
物件
kubectl create role read-pods -n=dev --verb='get,list' --resource=pods
或透過 --dry-run=client -o yaml
參數產生相對應的 YAML 檔內容:
kubectl create role read-pods -n=dev --verb='get,list' --resource=pods --dry-run=client -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: read-pods
namespace: dev
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
-
建立 RoleBinding
物件
kubectl create rolebinding read-pods -n dev --user=system:serviceaccount:dev:default --role=read-pods
或透過 --dry-run=client -o yaml
參數產生相對應的 YAML 檔內容:
kubectl create rolebinding read-pods -n dev --user=system:serviceaccount:dev:default --role=read-pods --dry-run=client -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: read-pods
namespace: dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-pods
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:serviceaccount:dev:default
上述語法也可以在 subjects:
欄位寫成這樣,其實都是一樣的,服務帳戶 (kind: ServiceAccount
) (name: default
) 就是 一般帳戶 (kind: User
) (name: system:serviceaccount:dev:default
),只是名稱表達的方式不同而已:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-pods
subjects:
- kind: ServiceAccount
name: default
先檢查一下 system:serviceaccount:dev:default
服務帳戶是否已經獲得 pods
的 get
權限:
kubectl auth can-i get pods -n=dev --as=system:serviceaccount:dev:default
kubectl auth can-i list pods -n=dev --as=system:serviceaccount:dev:default
-
再取得一次 PodList
清單
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
此時應該就能夠取得完整的 PodList
清單與詳細資訊了:
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/dev/pods/",
"resourceVersion": "1446386"
},
"items": [
{
"metadata": {
"name": "microbot",
"namespace": "dev",
...
}
...
}
...
]
}
體驗建立新的服務帳戶
有了上述對於 default
服務帳戶的理解,相信也就不難理解自訂服務帳戶的用法,以下是體驗的步驟:
-
在 dev
命名空間建立自訂的服務帳戶 monitor
kubectl create serviceaccount monitor -n dev
-
設定 Role
與 RoleBinding
kubectl create role monitor-pods -n=dev --verb='get,list,watch' --resource='pods,pods/status'
kubectl create rolebinding monitor-pods -n dev --serviceaccount='dev:monitor' --role=monitor-pods
-
使用 YAML 建立 Pod 並指定 serviceAccountName: monitor
服務帳戶
apiVersion: v1
kind: Pod
metadata:
name: "microbot"
namespace: dev
labels:
app: "microbot"
spec:
containers:
- name: microbot
image: "dontrebootme/microbot:v1"
serviceAccountName: monitor
用 PowerShell 快速套用的方法:
@'
apiVersion: v1
kind: Pod
metadata:
name: "microbot"
namespace: dev
labels:
app: "microbot"
spec:
containers:
- name: microbot
image: "dontrebootme/microbot:v1"
serviceAccountName: monitor
'@ | kubectl apply -f -
-
從 Pods 內的容器呼叫 API 伺服器
kubectl exec microbot -it -n dev -- sh
apk update
apk add curl
CACERT='/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
總結
有了這一層理解,你大概就知道如何從你在 Pod 的應用程式中存取 Kubernetes 叢集中的資源。
以前我會覺得要從 Pod 讀取 ConfigMaps
或 Secrets
都要透過 volumeMounts
(目錄或檔案) 或 env
(環境變數) 的方式掛載進去,但事實上透過 volumeMounts
或 env
的方式並無法套用 RBAC 授權,當你想限制應用程式對叢集資源的存取範圍時,還要透過變更 YAML 並找有力人士套用更新,這也太不可靠了吧。況且,每次變更組態設定都要重新部署 Deployment 或重新啟動 Deployment 才能生效,也沒有很方便。
現在透過服務帳戶的方式,你可以直接從應用程式存取 Kubernetes 資源,還能透過 RBAC 機制限制存取的範圍,可謂是兼具彈性與安全,我覺得相當不錯! 👍
相關連結