Kubernetes 中 Pod 的選舉過程
為什麼需要 Pod 之間的 Leader Election
一般來說,由 Deployment 建立的 1 個或多個 Pod 都是對等關係,彼此之間提供一樣的服務。但是在某些場合,多個 Pod 之間需要有一個 Leader 的角色,即:
-
Pod 之間有且只有一個 Leader;
-
Leader 在一定週期不可用時,其他 Pod 會再選出一個 Leader;
-
由處於 Leader 身份的 Pod 來完成某些特殊的業務邏輯(通常是寫操作);
比如,當多個 Pod 之間只需要一個寫者時,如果不採用 Leader Election,那麼就必須在 Pod 啟動之初人為地配置一個 Leader。如果配置的 Leader 在後續的服務中失效且沒有對應機制來生成新的 Leader,那麼對應 Pod 服務就可能處於不可用狀態,違背高可用原則。
典型地,Kubernetes 的核心元件 kube-controller-manager 就需要一個需要 Leader 的場景。當 kube-controller-manager 的啟動引數設定--leader-elect=true
時,對應節點的 kube-controller-manager 在啟動時會執行選主操作。當選出一個 Leader 之後,由 Leader 來啟動所有的控制器。如果 Leader Pod 不可用,將會自動選出新的 Leader Pod,從而保障控制器仍處於執行狀態。
一個簡單的 Leader Election 的例子
備註:該例子取自專案文件。
啟動一個 leader-elector 的 Pod
-
建立一個
leader-elector
的 Deployment,其中的 Pod 會進行 Leader Election 的過程1
$ kubectl run leader-elector --image=k8s.gcr.io/leader-elector:0.5 --replicas=3 -- --election=example --http=0.0.0.0:4040
副本數為 3,即將生成 3 個 Pod,如果執行成功,可觀察到:
1 2 3 4 5
$ kubectl get po NAME READY STATUS RESTARTS AGE leader-elector-68dcb58d55-7dhdz 1/1 Running 0 2m36s leader-elector-68dcb58d55-g5zp8 1/1 Running 0 2m36s leader-elector-68dcb58d55-q45pd 1/1 Running 0 2m36s
-
檢視哪個 Pod 成為 Leader;
可以逐個檢視 Pod 的日誌:
1
$ kubectl logs -f ${pod_name}
如果是 Leader 的話,將會有如下的日誌:
1 2 3 4 5 6 7
$ kubectl logs leader-elector-68dcb58d55-g5zp8 leader-elector-9577494c7-l64lp is the leader I0122 03:24:31.779331 8 leaderelection.go:296] lock is held by leader-elector-9577494c7-l64lp and has not yet expired I0122 03:24:36.101800 8 leaderelection.go:296] lock is held by leader-elector-9577494c7-l64lp and has not yet expired I0122 03:24:41.426387 8 leaderelection.go:296] lock is held by leader-elector-9577494c7-l64lp and has not yet expired I0122 03:24:45.947321 8 leaderelection.go:215] sucessfully acquired lease default/example leader-elector-68dcb58d55-g5zp8 is the leader
更通用的方式是檢視資源鎖的身份標識資訊:
1
$ kubectl get ep example -o yaml
通過檢視 annotations 中的
control-plane.alpha.kubernetes.io/leader
欄位來獲得 Leader 的資訊; -
使用
leader-elector
的 HTTP 介面檢視 Leader;leader-elector
實現了一個簡單的 HTTP 介面(:4040
)來檢視當前 Leader:1 2
curl http://localhost:8001/api/v1/namespaces/default/pods/leader-elector-5d77ccc44d-gwsgg:4040/proxy/ {"name":"leader-elector-5d77ccc44d-7tmgm"}
用 Sidecar 模式使用 leader-elector
如果自己的專案中需要用到 Leader Election 的邏輯,可以有兩種方式:
-
將呼叫
leaderelection
庫的邏輯內嵌到自己專案中; -
使用 Sidecar 的方式將
leader-elector
容器組合在 Pod 中,通過呼叫 HTTP 介面來始終獲得 Leader 的資訊;
文件中以 Node.js 的方式舉了一個簡單例子,大家可以參考,此處不展開了。
Leader Election 的實現
Leader Election 的過程本質上就是一個競爭分散式鎖的過程。在 Kubernetes 中,這個分散式鎖是以建立 Endpoint 或者 ConfigMap 資源的形式進行:誰先建立了某種資源,誰就獲得鎖。
按照我們以往的慣例,帶著問題去看原始碼。有這麼幾個問題:
-
Leader Election 如何競選?
-
Leader 不可用之後如何競選新的 Leader?
不同於 Raft 演算法的一致性演算法的 Leader 競選,Pod 之間的 Leader Election 是無狀態的,也就是說現在的 Leader 無需同步上一個 Leader 的資料資訊,這就把競選的過程變得非常簡單:先到先得。
這部分程式碼在kubernetes/staging/src/k8s.io/client-go/tools/leaderelection
中,取 1.9.2 版本來分析。
資源鎖的實現
Kubernetes 實現了兩種資源鎖(resourcelock):Endpoint 和 ConfigMap。如果是基於 Endpoint 的資源鎖,獲取到鎖的 Pod 將會在對應 Namespace 下建立對應的 Endpoint 物件,並在其 Annotations 上記錄 Pod 的資訊。
比如 kube-controller-manager:
1
2
3
4
5
6
7
8
9
10
11
|
$ kubectl get ep -n kube-system | grep kube-controller-manager kube-controller-manager <none> 41d $ kubectl describe ep kube-controller-manager -n kube-system Name: kube-controller-manager Namespace: kube-system Labels: <none> Annotations: control-plane.alpha.kubernetes.io/leader: {"holderIdentity":"szdc-k8sm-0-5","leaseDurationSeconds":15,"acquireTime":"2018-12-11T0... Subsets: Events: <none> |
發現在 kube-system 中建立了同名的 Endpoint(kube-controller-manager),並在 Annotations 中以設定了 key 為control-plane.alpha.kubernetes.io/leader
,value 為對應 Leader 資訊的 JSON 資料。同理,如果採用 ConfigMap 作為資源鎖也是類似的實現模式。
resourcelock 是以 interface 的形式對外暴露,在建立過程(New()
)通過相應的引數來控制具體例項化的過程:
|
|
其中Get()
、Create()
和Update()
本質上就是對LeaderElectionRecord
的讀寫操作。LeaderElectionRecord
定義如下:
|
|
理論上,LeaderElectionRecord
是儲存在資源鎖的 Annotations 中,可以是任意的字串,此處是將 JSON 序列化為字串來進行儲存。
在leaderelection/resourcelock/configmaplock.go
和leaderelection/resourcelock/endpointslock.go
分別是基於 Endpoint 和 ConfigMap 對上面介面的實現。拿endpointslock.go
來看,對這幾個介面的實現實際上就是對 Endpoint 資源中 Annotations 的增刪查改罷了,比較簡單,就不詳細展開。
競爭鎖的過程
完整的 Leader Election 過程在leaderelection/leaderelection.go
中。
整個過程可以簡單描述為:
-
每個 Pod 在啟動的時候都會建立
LeaderElector
物件,然後執行LeaderElector.Run()
迴圈; -
在迴圈中,Pod 會定期(
RetryPeriod
)去不斷嘗試建立資源,如果建立成功,就在對應資源的欄位中記錄 Pod 相關的 Id(比如節點的 hostname); -
在迴圈週期中,Leader 會不斷 Update 資源鎖的對應時間資訊,從節點則會不斷檢查資源鎖是否過期,如果過期則嘗試更新資源,標記資源所有權。這樣一來,一旦 Leader 不可用,則對應的資源鎖將得不到更新,過期之後其他從節點會再次建立新的資源鎖成為 Leader;
其中,LeaderElector.Run()
的原始碼為:
|
|
acquire()
會週期性地建立鎖或探查鎖有沒有過期:
|
|
執行的週期為RetryPeriod
。
我們重點關注tryAcquireOrRenew()
的邏輯:
|
|
由上可以看出,tryAcquireOrRenew()
就是一個不斷嘗試 Update 操作的過程。
如果執行邏輯從le.acquire()
跳出,往下執行le.renew()
,這說明當前 Pod 已經成功搶到資源鎖成為 Leader,必須定期續租:
|
|
如何使用leaderelection
庫
讓我們來關注一下election的實現。
主要的邏輯位於election/lib/election.go
:
|
|
主體邏輯很簡單,就是不斷執行Run()
。而Run()
的實現就是上文中leaderelection
的Run()
。
上層應用只需要建立(NewElection()
)建立LeaderElector
物件,然後在一個 loop 中呼叫Run()
即可。
綜上所述,Kubernetes 中 Pod 的選舉過程本質上還是為了服務的高可用。希望大家研究得愉快!