這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Kubernetes API Server是整個Kubernetes叢集的核心,我們不僅有從叢集外部存取API Server的需求,有時,我們還需要從Pod的內部訪問API Server。
然而,在生產環境中,Kubernetes API Server都是“設防”的。在《Kubernetes叢集的安全配置》一文中,我提到過:Kubernetes通過client cert、static token、basic auth等方法對用戶端請求進行身分識別驗證。對於運行於Pod中的Process而言,有些時候這些方法是適合的,但有些時候,像client cert、static token或basic auth這些資訊是不便於暴露給Pod中的Process的。並且通過這些方法通過API Server驗證後的請求是具有全部授權的,可以任意操作Kubernetes cluster,這顯然是不能滿足安全要求的。為此,Kubernetes更推薦大家使用service account這種方案的。本文就帶大家詳細說說如何通過service account從一個Pod中訪問API Server的。
零、實驗環境
本文的實驗環境是Kubernetes 1.3.7 cluster,雙節點,master承載負荷。cluster通過kube-up.sh搭建的,具體的搭建方法見《一篇文章帶你瞭解Kubernetes安裝》。
一、什麼是service account?
什麼是service account? 顧名思義,相對於user account(比如:kubectl訪問APIServer時用的就是user account),service account就是Pod中的Process用於訪問Kubernetes API的account,它為Pod中的Process提供了一種身份標識。相比於user account的全域性許可權,service account更適合一些輕量級的task,更聚焦於授權給某些特定Pod中的Process所使用。
service account作為一種resource存在於Kubernetes cluster中,我們可以通過kubectl擷取當前cluster中的service acount列表:
# kubectl get serviceaccount --all-namespacesNAMESPACE NAME SECRETS AGEdefault default 1 140dkube-system default 1 140d
我們查看一下kube-system namespace下名為”default”的service account的詳細資料:
# kubectl describe serviceaccount/default -n kube-systemName: defaultNamespace: kube-systemLabels: <none>Image pull secrets: <none>Mountable secrets: default-token-hpni0Tokens: default-token-hpni0
我們看到service account並不複雜,只是關聯了一個secret資源作為token,該token也叫service-account-token,該token才是真正在API Server驗證(authentication)環節起作用的:
# kubectl get secret -n kube-systemNAME TYPE DATA AGEdefault-token-hpni0 kubernetes.io/service-account-token 3 140d# kubectl get secret default-token-hpni0 -o yaml -n kube-systemapiVersion: v1data: ca.crt: {base64 encoding of ca.crt data} namespace: a3ViZS1zeXN0ZW0= token: {base64 encoding of bearer token}kind: Secretmetadata: annotations: kubernetes.io/service-account.name: default kubernetes.io/service-account.uid: 90ded7ff-9120-11e6-a0a6-00163e1625a9 creationTimestamp: 2016-10-13T08:39:33Z name: default-token-hpni0 namespace: kube-system resourceVersion: "2864" selfLink: /api/v1/namespaces/kube-system/secrets/default-token-hpni0 uid: 90e71909-9120-11e6-a0a6-00163e1625a9type: kubernetes.io/service-account-token
我們看到這個類型為service-account-token的secret資源套件含的資料有三部分:ca.crt、namespace和token。
這是一段用API Server私密金鑰簽發(sign)的bearer tokens的base64編碼,在API Server authenticating環節,它將派上用場。
二、API Server的service account authentication(身分識別驗證)
前面說過,service account為Pod中的Process提供了一種身份標識,在Kubernetes的身份校正(authenticating)環節,以某個service account提供身份的Pod的使用者名稱為:
system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)
以上面那個kube-system namespace下的“default” service account為例,使用它的Pod的username全稱為:
system:serviceaccount:kube-system:default
有了username,那麼credentials呢?就是上面提到的service-account-token中的token。在《Kubernetes叢集的安全配置》一文中我們談到過,API Server的authenticating環節支援多種身份校正方式:client cert、bearer token、static password auth等,這些方式中有一種方式通過authenticating(Kubernetes API Server會逐個方式嘗試),那麼身份校正就會通過。一旦API Server發現client發起的request使用的是service account token的方式,API Server就會自動採用signed bearer token方式進行身份校正。而request就會使用攜帶的service account token參與驗證。該token是API Server在建立service account時用API server啟動參數:–service-account-key-file的值簽署(sign)產生的。如果–service-account-key-file未傳入任何值,那麼將預設使用–tls-private-key-file的值,即API Server的私密金鑰(server.key)。
通過authenticating後,API Server將根據Pod username所在的group:system:serviceaccounts和system:serviceaccounts:(NAMESPACE)的許可權對其進行authority 和admission control兩個環節的處理。在這兩個環節中,cluster管理員可以對service account的許可權進行細化設定。
三、預設的service account
Kubernetes會為每個cluster中的namespace自動建立一個預設的service account資源,並命名為”default”:
# kubectl get serviceaccount --all-namespacesNAMESPACE NAME SECRETS AGEdefault default 1 140dkube-system default 1 140d
如果Pod中沒有顯式指定spec.serviceAccount欄位值,那麼Kubernetes會將該namespace下的”default” service account自動mount到在這個namespace中建立的Pod裡。我們以namespace “default”為例,我們查看一下其中的一個Pod的資訊:
# kubectl describe pod/index-api-2822468404-4oofrName: index-api-2822468404-4oofrNamespace: default... ...Containers: index-api: ... ... Volume Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-40z0x (ro) Environment Variables: <none>... ...Volumes:... ... default-token-40z0x: Type: Secret (a volume populated by a Secret) SecretName: default-token-40z0xQoS Class: BestEffortTolerations: <none>No events.
可以看到,kubernetes將default namespace中的service account “default”的service account token掛載(mount)到了Pod中容器的/var/run/secrets/kubernetes.io/serviceaccount路徑下。
深入容器內部,查看mount的serviceaccount路徑下的結構:
# docker exec 3d11ee06e0f8 ls /var/run/secrets/kubernetes.io/serviceaccountca.crtnamespacetoken
這三個檔案與上面提到的service account的token中的資料是一一對應的。
四、default service account doesn’t work
上面提到過,每個Pod都會被自動掛載一個其所在namespace的default service account,該service account用於該Pod中的Process訪問API Server時使用。Pod中的Process該怎麼用這個service account呢?Kubernetes官方提供了一個client-go項目可以為你示範如何使用service account訪問API Server。這裡我們就基於client-go項目中的examples/in-cluster/main.go來測試一下是否能成功訪問API Server。
先下載client-go源碼:
# go get k8s.io/client-go# ls -FCHANGELOG.md dynamic/ Godeps/ INSTALL.md LICENSE OWNERS plugin/ rest/ third_party/ transport/ vendor/discovery/ examples/ informers/ kubernetes/ listers/ pkg/ README.md testing/ tools/ util/
我們改造一下examples/in-cluster/main.go,考慮到panic會導致不便於觀察Pod日誌,我們將panic改為輸出到“標準輸出”,並且不return,讓Pod周期性的輸出相關日誌,即便fail:
// k8s.io/client-go/examples/in-cluster/main.go... ...func main() { // creates the in-cluster config config, err := rest.InClusterConfig() if err != nil { fmt.Println(err) } // creates the clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { fmt.Println(err) } for { pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) if err != nil { fmt.Println(err) } else { fmt.Printf("There are %d pods in the cluster\n", len(pods.Items)) } time.Sleep(10 * time.Second) }}
基於該main.go的go build預設輸出,建立一個簡單的Dockerfile:
From ubuntu:14.04MAINTAINER Tony Bai <bigwhite.cn@gmail.com>COPY main /root/mainRUN chmod +x /root/mainWORKDIR /rootENTRYPOINT ["/root/main"]
構建一個測試用docker image:
# docker build -t k8s/example1:latest .... ...# docker images|grep k8sk8s/example1 latest ceb3efdb2f91 14 hours ago 264.4 MB
建立一份deployment manifest:
//main.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata: name: k8s-example1spec: replicas: 1 template: metadata: labels: run: k8s-example1 spec: containers: - name: k8s-example1 image: k8s/example1:latest imagePullPolicy: IfNotPresent
我們來建立該deployment(kubectl create -f main.yaml -n kube-system),觀察Pod中的main程式能否成功訪問到API Server:
# kubectl logs k8s-example1-1569038391-jfxhxthe server has asked for the client to provide credentials (get pods)the server has asked for the client to provide credentials (get pods)API Server log(/var/log/upstart/kube-apiserver.log):E0302 15:45:40.944496 12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification errorE0302 15:45:50.946598 12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification errorE0302 15:46:00.948398 12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification error
出錯了!kube-system namespace下的”default” service account似乎不好用啊!(注意:這是在kubernetes 1.3.7環境)。
五、建立一個新的自用的service account
在kubernetes github issues中,有好多issue是關於”default” service account不好用的問題,給出的解決方案似乎都是建立一個新的service account。
service account的建立非常簡單,我們建立一個serviceaccount.yaml:
//serviceaccount.yamlapiVersion: v1kind: ServiceAccountmetadata: name: k8s-example1
建立該service account:
# kubectl create -f serviceaccount.yamlserviceaccount "k8s-example1" created# kubectl get serviceaccountNAME SECRETS AGEdefault 1 139dk8s-example1 1 12s
修改main.yaml,讓Pod顯示使用這個新的service account:
//main.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata: name: k8s-example1spec: replicas: 1 template: metadata: labels: run: k8s-example1 spec: serviceAccount: k8s-example1 containers: - name: k8s-example1 image: k8s/example1:latest imagePullPolicy: IfNotPresent
好了,我們重新建立該deployment,查看Pod日誌:
# kubectl logs k8s-example1-456041623-rqj87There are 14 pods in the clusterThere are 14 pods in the cluster... ...
我們看到main程式使用新的service account成功通過了API Server的身分識別驗證環節,並獲得了cluster的相關資訊。
六、尾聲
在我的另外一個使用kubeadm安裝的k8s 1.5.1環境中,我重複做了上面這個簡單測試,不同的是這次我直接使用了default service account。在k8s 1.5.1下,pod的執行結果是ok的,也就是說通過default serviceaccount,我們的client-go in-cluster example程式可以順利通過API Server的身分識別驗證,擷取到相關的Pods元資訊。
七、參考資料
- Kubernetes authentication
- Service Accounts
- Accessing the cluster
- Service Accounts Admin
微博:@tonybai_cn
公眾號:iamtonybai
github.com: https://github.com/bigwhite
2017, bigwhite. 著作權.