kubeadm概述
kubeadm是社區維護的Kubernetes集群一鍵部署利器,使用兩條命令即可完成k8s集群中master節點以及node節點的部署,其底層原理是利用了k8s TLS bootstrap特性。
kubeadm部署k8s集群示例
(1)k8s master節點部署:
$ kubeadm init
此外,我們也可以自己編寫yaml文件來自定義kubeadm的啟動過程和一些組件的啟動參數等等
$ kubeadm init --config xxx.yaml
(2)k8s node節點部署(將一個node節點加入到已有集群當中):
$ kubeadm join <kube-apiserver的ip + 端口> --token <token>
此外,我們也可以自己編寫yaml文件來自定義kubeadm的啟動過程和一些組件的啟動參數,包括kube-apiserver的ip與端口、token等
$ kubeadm join --config xxx.yaml
關於自定義yaml文件以及更多的kubeadm用法請參考:https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
k8s TLS bootstrap概述
當k8s集群開啟了TLS認證后,每個節點的kubelet組件都要使用由kube-apiserver的CA簽發的有效證書才能與kube-apiserver通信;當節點非常多的時候,為每個節點都單獨簽署證書是一件非常繁瑣而又耗時的事情。
此時k8s TLS bootstrap功能應運而生。
k8s TLS bootstrap功能就是讓kubelet先使用一個預先商定好的低權限token連接到kube-apiserver,向kube-apiserver申請證書,然后kube-controller-manager給kubelet動態簽署證書,后續kubelet都將通過動態簽署的證書與kube-apiserver通信。
關於k8s TLS bootstrap的詳細分析這里暫時不展開。
kubeadm原理解析
大致流程
在k8s master中,會先啟動一個kubelet,控制面組件通過kubelet static pod特性啟動,在k8s master控制面組件啟動成功后,其他節點需要加入到k8s集群時,使用TLS bootstrap來簡化加入的過程,先通過bootstrap-token與kube-apiserver通信,自動從kube-controller-manager處簽發拿到與kube-apiserver通信的證書,然后自動生成與kube-apiserver通信的kubeconfig文件,后續將使用kubeconfig文件與kube-apiserver進行通信。
詳細流程解析
1.kubeadm init
kubeadm init的結果是完成一個k8s master節點的部署,包括kube-apiserver、kube-controller-manager、kube-scheduler、etcd等控制面組件以及kubelet數據面組件,即該master節點既是控制面又是數據面,所以master節點上也是可以運行pod的;
以下為kubeadm init的處理流程代碼(基於k8s v1.17.4版本),一共13步:
// cmd/kubeadm/app/cmd/init.go-NewCmdInit()
...
// initialize the workflow runner with the list of phases
initRunner.AppendPhase(phases.NewPreflightPhase()) // 1.環境檢查
initRunner.AppendPhase(phases.NewKubeletStartPhase()) // 2.配置並啟動kubelet
initRunner.AppendPhase(phases.NewCertsPhase()) // 3.證書生成
initRunner.AppendPhase(phases.NewKubeConfigPhase()) // 4.kubeconfig文件生成
initRunner.AppendPhase(phases.NewControlPlanePhase()) // 5.控制面組件yaml生成
initRunner.AppendPhase(phases.NewEtcdPhase()) // 6.etcd組件yaml生成
initRunner.AppendPhase(phases.NewWaitControlPlanePhase()) // 7.等待控制面組件運行
initRunner.AppendPhase(phases.NewUploadConfigPhase()) // 8.上傳配置
initRunner.AppendPhase(phases.NewUploadCertsPhase()) // 9.上傳CA證書/私鑰
initRunner.AppendPhase(phases.NewMarkControlPlanePhase()) // 10.master節點打污點
initRunner.AppendPhase(phases.NewBootstrapTokenPhase()) // 11.生成bootstrap token和ca證書configmap
initRunner.AppendPhase(phases.NewKubeletFinalizePhase()) // 12.更換kubelet證書
initRunner.AppendPhase(phases.NewAddonPhase()) // 13.安裝Addon
...
(1)環境檢查。檢查項包括操作系統內核版本、k8s組件暴露服務的指定端口是否被占用、docker是否安裝、iptables命令是否安裝等等,其實這一步還包括了拉取kubeadm部署所需的鏡像;
(2)配置並啟動kubelet。創建kubelet啟動所需的配置文件,並啟動kubelet,kubeadm使用了systemd的方式部署啟動kubelet;
# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; disabled; vendor preset: enabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) ...
為什么master上還需要配置啟動kubelet呢?
因為kubeadm init的時候需要將master控制面組件kube-apiserver、kube-controller-manager、kube-scheduler、etcd以pod的方式運行起來,而現在又沒有在運行的控制面以及kubelet,怎么辦呢,kubeadm的做法是,給master節點上也安裝啟動kubelet,然后使用kubelet static pod特性將master控制面組件運行起來。
關於static pod,詳細內容可參考:https://kubernetes.io/zh/docs/tasks/configure-pod-container/static-pod/
(3)證書生成。即生成kubernetes對外提供服務所需的各種證書,放到/etc/kubernetes/pki目錄下;
# ls /etc/kubernetes/pki
apiserver.crt apiserver-etcd-client.crt apiserver-etcd-client.key apiserver.key apiserver-kubelet-client.crt apiserver-kubelet-client.key ca.crt ca.key etcd front-proxy-ca.crt front-proxy-ca.key front-proxy-client.crt front-proxy-client.key sa.key sa.pub
(4)kubeconfig配置文件生成。即生成master節點上kube-controller-manager、kube-scheduler、kubelet組件等訪問kube-apiserver的kubeconfig文件,放到/etc/kubernetes目錄下,文件包含了apiserver的地址、監聽端口、證書等信息,使用該kubeconfig文件即可直接與kube-apiserver通信;
# ls /etc/kubernetes
admin.conf controller-manager.conf kubelet.conf manifests pki scheduler.conf
master上的kubelet啟動后,使用kubeadm生成的kubeconfig與kube-apiserver進行通信,通過證書輪換,向kube-apiserver申請新的證書,由kube-controller-manager簽發證書返回。
注意:這里master上的kubelet不會使用TLS bootstrap特性。
(5)控制面組件yaml文件生成。即kubeadm為4個控制面組件kube-apiserver、kube-controller-manager、kube-scheduler生成pod yaml文件,放到/etc/kubernetes/manifests目錄下,然后kubelet會根據static pod特性,使用pod的方式將它們部署起來;
# ls /etc/kubernetes/manifests
kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
(6)etcd組件yaml文件生成。即kubeadm為etcd組件生成pod yaml文件,放到/etc/kubernetes/manifests目錄下,然后kubelet會根據static pod特性,使用pod的方式將etcd部署起來;
# ls /etc/kubernetes/manifests
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
(7)等待控制面組件運行。kubeadm會不間斷檢查localhost:6443/healthz這個url,等待master組件完全啟動;
(8)上傳配置。這里會創建2個configmap有,都創建在kube-system命名空間下,名稱分別是kubeadm-config、kubelet-config-xxx(k8s版本),分別存儲着kubeadm的集群配置信息、kubelet的配置信息;
(9)上傳CA證書/私鑰。該步驟默認不執行,通過增加——upload-certs
參數啟用,它會將相關的CA證書/私鑰加密后作為data,在kube-system
命名空間下創建名稱為kubeadm-certs
的secret,給后續的master節點kubeadm join使用,這樣join時可以直接從secret中解密出CA證書/私鑰,然后簽發其他證書,而無需手工復制相關CA證書/私鑰;
kubeadm init執行完成后,會輸出一個名稱為certificateKey的值,然后在其他master節點join時,加上--certificate-key
參數即可。
certificateKey是在添加新的master節點時用來解密kubeadm-certs
secret中的證書的秘鑰。
kubeadm-certs示例如下,其中的證書和私鑰均已加密,通過certificateKey解密即可使用:
apiVersion: v1
data:
ca.crt: KfdZpEDF1wJfaexXls5...
ca.key: VXfm7luIyM3QT+Rd04+...
etcd-ca.crt: wwSzqCcltkrP26...
etcd-ca.key: gqusZazZLF33Ip...
front-proxy-ca.crt: EmfgKP6...
front-proxy-ca.key: wKMYSrk...
sa.key: pscxeFTGoCFZ6hrE1XK...
sa.pub: keey1WPkWdj2TjEb/oM...
kind: Secret
metadata:
name: kubeadm-certs
namespace: kube-system
ownerReferences:
- apiVersion: v1
blockOwnerDeletion: true
controller: true
kind: Secret
name: bootstrap-token-xxxxxx
...
...
type: Opaque
注意:secret kubeadm-certs
和解密密鑰certificateKey會在兩個小時后失效。
(10)master節點打污點。將該master節點打上污點,不作為計算節點數據面使用;
(11)生成bootstrap token和ca證書configmap。
kubeadm會為該k8s集群生成一個bootstrap token並打印出來,后續的node節點通過這個token,通過kubeadm join命令,使用TLS bootstrap特性即可加入到這個k8s集群中,當然,這里還包括了為該token創建RBAC的各個對象,賦予該token創建CSR證書簽名請求的權限、自動批復CSR請求的權限、輪換證書請求自動批復的權限等,這里不展開介紹,后續分析k8s TLS bootstrap原理時再做分析;
kubeadm init執行完成后,也可以通過以下命令獲取token:
# kubeadm token list
kubeadm還會將ca.crt、apiserver url等信息,保存到一個configmap當中,給后續加入該k8s集群的node節點使用,configmap名稱為cluster-info,位於kube-public命名空間下;
# kubectl get configmap -n kube-public -o yaml cluster-info
apiVersion: v1
data:
kubeconfig: |
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0F...
server: https://192.168.1.10:6443
name: ""
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null
kind: ConfigMap
metadata:
(12)更換kubelet證書。前面說過,master上的kubelet啟動后,使用kubeadm生成的kubeconfig與kube-apiserver進行通信,通過證書輪換,向kube-apiserver申請新的證書,由kube-controller-manager簽發證書返回。而這里說的更換kubelet證書,其實就是將kubelet與kube-apiserver通信的kubeconfig文件中的證書替換成由kube-controller-manager簽發返回的證書,即將kubeconfig文件中的client-certificate
與client-key
的值都替換成/var/lib/kubelet/pki/kubelet-client-current.pem
;
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0F...
server: https://192.168.1.10:6443
name: test-cluster
contexts:
- context:
cluster: test-cluster
user: system:node:test-cluster-master-1
name: system:node:test-cluster-master-1
current-context: system:node:test-cluster-master-1
kind: Config
preferences: {}
users:
- name: system:node:test-cluster-master-1
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
(13)安裝Addon。安裝coredns與kube-proxy,kubeadm init流程結束。
2.kubeadm join
kubeadm join的結果是完成一個k8s master節點或node節點加入一個已有的k8s集群。
以下為kubeadm join的處理流程代碼(基於k8s v1.17.4版本),一共5步:
// cmd/kubeadm/app/cmd/join.go-NewCmdInit()
...
joinRunner.AppendPhase(phases.NewPreflightPhase()) // 1.環境檢查
joinRunner.AppendPhase(phases.NewControlPlanePreparePhase()) // 2.控制面准備
joinRunner.AppendPhase(phases.NewCheckEtcdPhase()) // 3.檢查etcd是否健康
joinRunner.AppendPhase(phases.NewKubeletStartPhase()) // 4.啟動kubelet
joinRunner.AppendPhase(phases.NewControlPlaneJoinPhase()) // 5.控制面操作
...
(1)環境檢查。檢查項包括操作系統內核版本、k8s組件暴露服務的指定端口是否被占用、docker是否安裝、iptables命令是否安裝等等,但這里的環境檢查與kubeadm init時的檢查有點不同,這里會區分是join的master節點還是node節點,如果是node節點,則僅僅進行node相關的檢查;
另外,這里還會獲取kube-public命名空間下的configmap對象cluster-info,從中CA、master api等信息;
(2)控制面准備。如果是node的join,這一步的邏輯不會執行。這里會從kube-system命名空間中加載名稱為kubeadm-certs的secret對象,然后生成控制面組件kube-apiserver、kube-controller-manager、kube-scheduler所需的證書,最后生成它們的部署yaml,放置到kubelet的static pod目錄下,被kubelet使用static pod特性啟動;
(3)檢查etcd是否健康。
(4)啟動kubelet。根據CA、bootstrap token等信息生成/etc/kubernetes/bootstrap-kubelet.conf文件,通過TLS bootstrap機制,kubelet使用bootstrap token來向kube-apiserver申請證書,由kube-controller-manager簽發證書返回,然后kubelet根據返回的證書生成kubeconfig文件並寫入到/etc/kubernetes/kubelet.conf文件,后續kubelet將會使用該kubeconfig文件來與kube-apiserver通信;
# cat /etc/kubernetes/kubelet.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0F...
server: https://192.168.1.10:6443
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
users:
- name: default-auth
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
注意:bootstrap-kubelet.conf文件會在kubelet.conf文件生成后,被kubeadm刪除掉;
(5)控制面操作。如果是node的join,這一步的邏輯不會執行。控制面操作包括生成etcd的static pod yaml、更新kube-system命名空間下的configmap對象kubeadm-config,將該控制節點信息更新進去、將該master節點打上污點,不作為計算節點數據面使用;至此,kubeadm join流程結束。