天天看點

課時 22:有狀态應用編排 StatefulSet(酒祝)

本文将主要分享以下四方面的内容:

  1. “有狀态”需求
  2. 用例解讀
  3. 操作示範
  4. 架構設計

“有狀态”需求

課程回顧

我們之前講到過 Deployment 作為一個應用編排管理工具,它為我們提供了哪些功能?

如下圖所示:

課時 22:有狀态應用編排 StatefulSet(酒祝)
  • 首先它支援定義一組 Pod 的期望數量,Controller 會為我們維持 Pod 的數量在期望的版本以及期望的數量;
  • 第二它支援配置 Pod 釋出方式,配置完成後 Controller 會按照我們給出的政策來更新 Pod,同時在更新的過程中,也會保證不可用 Pod 數量在我們定義的範圍内;
  • 第三,如果我們在釋出的過程中遇到問題,Deployment 也支援一鍵來復原。

可以簡單地說,Deployment 認為:它管理的所有相同版本的 Pod 都是一模一樣的副本。也就是說,在 Deployment Controller 看來,所有相同版本的 Pod,不管是裡面部署的應用還是行為,都是完全相同的。

這樣一種能力對于無狀态應用是支援滿足的,如果我們遇到一些有狀态應用呢?

需求分析

比如下圖所示的一些需求:

課時 22:有狀态應用編排 StatefulSet(酒祝)

以上的這些需求都是 Deployment 無法滿足的,是以 Kubernetes 社群為我們提供了一個叫 StatefluSet 的資源,用來管理有狀态應用。

StatefulSet:主要面向有狀态應用管理的控制器

其實作在社群很多無狀态應用也通過 StatefulSet 來管理,通過這節課程,大家也會明白為什麼我們将部分無狀态應用也通過 StatefulSet 來管理。

課時 22:有狀态應用編排 StatefulSet(酒祝)

如上圖右側所示,StatefulSet 中的 Pod 都是有序号的,從 0 開始一直到定義的 replica 數量減一。每個 Pod 都有獨立的網絡辨別:一個 hostname、一塊獨立的 pvc 以及 pv 存儲。這樣的話,同一個 StatefulSet 下不同的 Pod,有不同的網絡辨別、有自己獨享的存儲盤,這就能很好地滿足了絕大部分有狀态應用的需求。

如上圖右側所示:

  • 首先,每個 Pod 會有 Order 序号,會按照序号來建立,删除和更新 Pod;
  • 其次,通過配置一個 headless Service,使每個 Pod 有一個唯一的網絡辨別 (hostname);
  • 第三,通過配置 pvc 模闆,就是 pvc template,使每個 Pod 有一塊或者多塊 pv 存儲盤;
  • 最後,支援一定數量的灰階釋出。比如現在有三個副本的 StatefulSet,我們可以指定隻更新其中的一個或者兩個,更甚至是三個到新版本。通過這樣的方式,來達到灰階更新的目的。

用例解讀

StatefulSet 範例建立

課時 22:有狀态應用編排 StatefulSet(酒祝)

上圖左側是一個 Service 的配置,我們通過配置 headless Service,其實想要達到的目标是:期望 StatefulSet 裡面的 Pod 有獨立的網絡辨別。這裡的 Service name 叫 nginx。

上圖右側是一個 StatefulSet 的配置,在 spec 中有個 serviceName 也叫 nginx。通過這個 serviceName 來指定這個 StatefulSet 要對應哪一個 Service。

這個 spec 中還有其它幾個很熟悉的字段,比如 selector 和 template。selector 是一個标簽選擇器,selector 定義的标簽選擇邏輯,必須比對 template 中 metadata 中 labels 包含 app: nginx。在 template 中定義一個 nginx container,這個 container 用的 image 版本是 alpine 版本,對外暴露的 80 端口作為一個 Web 服務。

最後,template.spec 裡面定義了一個 volumeMounts,這個 volumeMounts 并不是來源于 spec 中的一個 Volumes,而是來自于 volumeClaimTemplates,也就是 pvc 模闆。我們在 pvc 模闆中定義了一個叫 www-storage 的 pvc 名稱。這個 pvc 名稱,我們也會寫到 volumeMounts 作為一個 volume name,挂載到 /usr/share/nginx/html 這個目錄下。通過這樣的方式來達到每個 Pod 都有獨立的一個 pvc,并且挂載到容器中對應目錄的一個需求。

Service、StatefulSet 狀态

課時 22:有狀态應用編排 StatefulSet(酒祝)

通過将上文中的兩個對象建立之後,我們可以通過 get 指令可以看到 Service nginx 資源已經建立成功。

同時可以通過檢視 endpoints 看到,這個後端已經注冊了三個 IP 和端口,這三個 IP 對應了 Pod 的 IP,端口對應了之前 spec 中配置的 80 端口。

最後通過 get sts(StatefulSet 縮寫)nginx-web。從結果可以看到有一列叫做 READY,值為 3/3。分母 3 是 StatefulSet 中期望的數量,而分子 3 表示 Pod 已經達到期望 READY 的狀态數量。

Pod、PVC 狀态

下圖中的 get pod 可以看到三個 Pod 的狀态都是 Running 狀态,并且已經 READY。它的 IP 就是前面看到的 endpoint 位址。

課時 22:有狀态應用編排 StatefulSet(酒祝)

通過 get pvc 可以看到 NAME 那一列名稱,字首為 www-storage,中間是 nginx-web,字尾是一個序号。通過分析可以知道 www-storage 是 volumeClaimTemplates 中定義的 name,中間為 StatefulSet 定義的 name,末尾的序号對應着 Pod 的序号,也就是三個 PVC 分别被三個 Pod 綁定。通過這樣一種方式,達到不同的 Pod 享有不同的 PVC;PVC 也會綁定相應的一個 PV, 來達到不同的 Pod 綁定不同 PV 的目的。

Pod 的版本

課時 22:有狀态應用編排 StatefulSet(酒祝)

之前我們學到 Deployment 使用 ReplicaSet 來管理 Pod 的版本和所期望的 Pod 數量,但是在 StatefulSet 中,是由 StatefulSet Controller 來管理下屬的 Pod,是以 StatefulSet 通過 Pod 的 label 來辨別這個 Pod 所屬的版本,這裡叫 controller-revision-hash。這個 label 辨別和 Deployment 以及 StatefulSet 在 Pod 中注入的 Pod template hash 是類似的。

如上圖所示,通過 get pod 檢視到 controller-revision-hash,這裡的 hash 就是第一次建立 Pod 對應的 template 版本,可以看到字尾是 677759c9b8。這裡先記錄一下,接下來會做 Pod 更新,再來看一下 controller-revision-hash 會不會發生改變。

更新鏡像

課時 22:有狀态應用編排 StatefulSet(酒祝)

通過執行上圖的指令,可以看到上圖下方的 StatefulSet 配置中,已經把 StatefulSet 中的 image 更新到了 mainline 新版本。

檢視新版本狀态

課時 22:有狀态應用編排 StatefulSet(酒祝)

通過 get pod 指令查詢 Revision hash,可以看到三個 Pod 後面的 controller-revision-hash 都已經更新到了新的 Revision hash,後面變成了 7c55499668。通過這三個 Pod 建立的時間可以發現:序号為 2 的 Pod 建立的是最早的,之後是序号是 1 和 0。這表示在更新的過程中,真實的更新順序為 2-1-0,通過這麼一個倒序的順序來逐漸把 Pod 更新為新版本,并且我們更新的 Pod,還複用了之前 Pod 使用的 PVC。是以之前在 PV 存儲盤中的資料,仍然會挂載到新的 Pod 上。

上圖右上方是在 StatefulSet 的 status 中看到的資料,這裡有幾個重要的字段:

  • currentReplica:表示目前版本的數量
  • currentRevision:表示目前版本号
  • updateReplicas:表示新版本的數量
  • updateRevision:表示目前要更新的版本号

當然這裡也能看到 currentReplica 和 updateReplica,以及 currentRevision 和 updateRevision 都是一樣的,這就表示所有 Pod 已經更新到了所需要的版本。

操作示範

StatefulSet 編排檔案

首先這裡已經連接配接到了阿裡雲的一個叢集,叢集中有三個節點。

課時 22:有狀态應用編排 StatefulSet(酒祝)

現在開始建立一個 StatefulSet 和對應的 Service,首先看一下對應的編排檔案。

課時 22:有狀态應用編排 StatefulSet(酒祝)

如上圖中的例子所示,Service 對應的 nginx 對外暴露的 80 端口。StatefulSet 配置中 metadata 定義了 name 為 nginx-web;template 中的 containers 定義了鏡像資訊;最後定義了一個 volumeClaimTemplates 作為 PVC 模闆。

開始建立

課時 22:有狀态應用編排 StatefulSet(酒祝)

執行上面的指令後,我們就把 Service 和 StatefulSet 建立成功了。通過 get pod 可以看到首先建立的 Pod 序号為 0;通過 get pvc 可以看到序号為 0 的 PVC 已經和 PV 進行了綁定。

課時 22:有狀态應用編排 StatefulSet(酒祝)

此時序号為 0 的 Pod 已經開始建立了,狀态為 ContainerCreating。

課時 22:有狀态應用編排 StatefulSet(酒祝)

當序号為 0 的 Pod 建立完成後,開始建立序号為 1 的 Pod,然後看到新的 PVC 也已經建立成功,緊接着是序号為 2 的 Pod。

課時 22:有狀态應用編排 StatefulSet(酒祝)

可以看到每一個 Pod 建立之前,會先建立 PVC。PVC 建立完成後,Pod 從 Pending 狀态和 PV 進行綁定,然後變成了 ContainerCreating,最後達到 Running。

檢視狀态

然後通過

kubectl get sts nginx-web -o yaml

檢視 StatefulSet 的狀态。

課時 22:有狀态應用編排 StatefulSet(酒祝)

如上圖所示,期望的 replicas 數量為 3 個,目前可用數量為 3 個,并且達到最新的版本。

課時 22:有狀态應用編排 StatefulSet(酒祝)

接着來看一下 Service 和 endpoints,可以看到 Service 的 Port 為 80,endpoint 有三個對應的 IP 位址。

課時 22:有狀态應用編排 StatefulSet(酒祝)

再來 get pod,可以看到三個 Pod 對應了上面的 endpoints 的 IP 位址。

以上的操作結果為:三個 PVC 和三個 Pod 已經達到了所期望的狀态,并且 StatefulSet 上報的 status 中,replicas 以及 currentReplicas 都為三個。

更新操作

課時 22:有狀态應用編排 StatefulSet(酒祝)

這裡重複說一下,kubectl set image 為聲明鏡像的固定寫法;StatefulSet 表示自願類型;nginx-web 是資源名稱;nginx=nginx:mainline,等号前面的 nginx 是我們在 template 中定義的 container 名稱,後面的 nginx:mainline 是所期望更新的鏡像版本。

通過上面的指令,已經成功的将 StatefulSet 中的鏡像更新為新的版本。

課時 22:有狀态應用編排 StatefulSet(酒祝)

通過 get pod 看一下狀态,nginx-web-1,nginx-web-2 已經進入了 Running 狀态。對應的 controller-revision-hash 已經是新的版本。那麼 nginx-web-0 這個 Pod,舊的 Pod 已經被删除了,新 Pod 還在 Createing 狀态。

課時 22:有狀态應用編排 StatefulSet(酒祝)

再次檢視一下狀态,所有的 Pod 都已經 Running 狀态了。

課時 22:有狀态應用編排 StatefulSet(酒祝)

檢視一下 StatefulSet 資訊,目前 StatefulSet 中的 status 裡定義的 currentRevision 已經更新到了新的版本,表示 StatefulSet 已經擷取到的三個 Pod 都已經進入了新版本。

課時 22:有狀态應用編排 StatefulSet(酒祝)

如何檢視這三個 Pod 是否還複用了之前的網絡辨別和存儲盤呢?

其實 headless Service 配置的 hostname 隻是和 Pod name 挂鈎的,是以隻要更新後的 Pod 名稱和舊的 Pod 名稱相同,那麼就可以沿用之前 Pod 使用的網絡辨別。

關于存儲盤,由上圖可以看到 PVC 的狀态,它們的建立時間一直沒有改變,還是第一次建立 Pod 時的時間,是以現在更新後的 Pod 使用的還是舊 Pod 中使用的 PVC。

課時 22:有狀态應用編排 StatefulSet(酒祝)

比如可以檢視其中的某一個 Pod,這個 Pod 裡面同樣有個聲明的 volumes,這個 persistentVolumeClaim 裡的名稱 www-storage-nginx-web-0,對應着 PVC 清單中看到的序号為 0 的 PVC,之前是被舊的 Pod 所使用。更新過程中 Controller 删除舊 Pod,并且建立了一個同名的新 Pod,新的 Pod 仍然複用了舊 Pod 所使用的 PVC。

通過這種方式來達到更新前後,網絡存儲都能複用的目的。

架構設計

管理模式

StatefulSet 可能會建立三種類型的資源。

第一種資源:ControllerRevision

通過這個資源,StatefulSet 可以很友善地管理不同版本的 template 模闆。

舉個例子:比如上文中提到的 nginx,在建立之初擁有的第一個 template 版本,會建立一個對應的 ControllerRevision。而當修改了 image 版本之後,StatefulSet Controller 會建立一個新的 ControllerRevision,大家可以了解為每一個 ControllerRevision 對應了每一個版本的 Template,也對應了每一個版本的 ControllerRevision hash。其實在 Pod label 中定義的 ControllerRevision hash,就是 ControllerRevision 的名字。通過這個資源 StatefulSet Controller 來管理不同版本的 template 資源。

第二個資源:PVC

如果在 StatefulSet 中定義了 volumeClaimTemplates,StatefulSet 會在建立 Pod 之前,先根據這個模闆建立 PVC,并把 PVC 加到 Pod volume 中。

如果使用者在 spec 的 pvc 模闆中定義了 volumeClaimTemplates,StatefulSet 在建立 Pod 之前,根據模闆建立 PVC,并加到 Pod 對應的 volume 中。當然也可以在 spec 中不定義 pvc template,那麼所建立出來的 Pod 就不會挂載單獨的一個 pv。

第三個資源:Pod

StatefulSet 按照順序建立、删除、更新 Pod,每個 Pod 有唯一的序号。

課時 22:有狀态應用編排 StatefulSet(酒祝)

如上圖所示,StatefulSet Controller 是 Owned 三個資源:ControllerRevision、Pod、PVC。

這裡不同的地方在于,目前版本的 StatefulSet 隻會在 ControllerRevision 和 Pod 中添加 OwnerReference,而不會在 PVC 中添加 OwnerReference。之前的課程中提到過,擁有 OwnerReference 的資源,在管理的這個資源進行删除的預設情況下,會關聯級聯删除下屬資源。是以預設情況下删除 StatefulSet 之後,StatefulSet 建立的 ControllerRevision 和 Pod 都會被删除,但是 PVC 因為沒有寫入 OwnerReference,PVC 并不會被級聯删除。

StatefulSet 控制器

課時 22:有狀态應用編排 StatefulSet(酒祝)

上圖為 StatefulSet 控制器的工作流程,下面來簡單介紹一下整個工作處理流程。

首先通過注冊 Informer 的 Event Handler(事件處理),來處理 StatefulSet 和 Pod 的變化。在 Controller 邏輯中,每一次收到 StatefulSet 或者是 Pod 的變化,都會找到對應的 StatefulSet 放到隊列。緊接着從隊列取出來處理後,先做的操作是 Update Revision,也就是先檢視目前拿到的 StatefulSet 中的 template,有沒有對應的 ControllerRevision。如果沒有,說明 template 已經更新過,Controller 就會建立一個新版本的 Revision,也就有了一個新的 ControllerRevision hash 版本号。

然後 Controller 會把所有版本号拿出來,并且按照序号整理一遍。這個整理的過程中,如果發現有缺少的 Pod,它就會按照序号去建立,如果發現有多餘的 Pod,就會按照序号去删除。當保證了 Pod 數量和 Pod 序号滿足 Replica 數量之後,Controller 會去檢視是否需要更新 Pod。也就是說這兩步的差別在于,Manger pods in order 去檢視所有的 Pod 是否滿足序号;而後者 Update in order 檢視 Pod 期望的版本是否符合要求,并且通過序号來更新。

Update in order 其更新過程如上圖所示,其實這個過程比較簡單,就是删除 Pod。删除 Pod 之後,其實是在下一次觸發事件,Controller 拿到這個 success 之後會發現缺少 Pod,然後再從前一個步驟 Manger pod in order 中把新的 Pod 建立出來。在這之後 Controller 會做一次 Update status,也就是之前通過指令行看到的 status 資訊。

通過整個這樣的一個流程,StatefulSet 達到了管理有狀态應用的能力。

擴容模拟

課時 22:有狀态應用編排 StatefulSet(酒祝)

假設 StatefulSet 初始配置 replicas 為 1,有一個 Pod0。那麼将 replicas 從 1 修改到 3 之後,其實我們是先建立 Pod1,預設情況是等待 Pod1 狀态 READY 之後,再建立 Pod2。

通過上圖可以看到每個 StatefulSet 下面的 Pod 都是從序号 0 開始建立的。是以一個 replicas 為 N 的 StatefulSet,它建立出來的 Pod 序号為 [0,N),0 是開曲線,N 是閉曲線,也就是當 N>0 的時候,序号為 0 到 N-1。

擴縮容管理政策

課時 22:有狀态應用編排 StatefulSet(酒祝)

可能有的同學會有疑問:如果我不想按照序号建立和删除,那 StatefulSet 也支援其它的建立和删除的邏輯,這也就是為什麼社群有些人把無狀态應用也通過 StatefulSet 來管理。它的好處是它能擁有唯一的網絡辨別以及網絡存儲,同時也能通過并發的方式進行擴縮容。

StatefulSet.spec 中有個字段叫 podMangementPolicy 字段,這個字段的可選政策為 OrderedReady 和 Parallel,預設情況下為前者。

如我們剛才建立的例子,沒有在 spec 中定義 podMangementPolicy。那麼 Controller 預設 OrderedReady 作為政策,然後在 OrderedReady 情況下,擴縮容就嚴格按照 Order 順序來執行,必須要等前面的 Pod 狀态為 Ready 之後,才能擴容下一個 Pod。在縮容的時候,倒序删除,序号從大到小進行删除。

舉個例子,上圖右側中,從 Pod0 擴容到 Pod0、Pod1、Pod2 的時候,必須先建立 Pod1,等 Pod1 Ready 之後再建立 Pod2。其實還存在一種可能性:比如在建立 Pod1 的時候,Pod0 因為某些原因,可能是主控端的原因或者是應用本身的原因,Pod0 變成 NotReady 狀态。這時 Controller 也不會建立 Pod2,是以不隻是我們所建立 Pod 的前一個 Pod 要 Ready,而是前面所有的 Pod 都要 Ready 之後,才會建立下一個 Pod。上圖中的例子,如果要建立 Pod2,那麼 Pod0、Pod1 都要 ready。

另一種政策叫做 Parallel,顧名思義就是并行擴縮容,不需要等前面的 Pod 都 Ready 或者删除後再處理下一個。

釋出模拟

課時 22:有狀态應用編排 StatefulSet(酒祝)

假設這裡的 StatefulSet template1 對應邏輯上的 Revision1,這時 StatefulSet 下面的三個 Pod 都屬于 Revision1 版本。在我們修改了 template,比如修改了鏡像之後,Controller 是通過倒序的方式逐一更新 Pod。上圖中可以看到 Controller 先建立了一個 Revision2,對應的就是建立了 ControllerRevision2 這麼一個資源,并且将 ControllerRevision2 這個資源的 name 作為一個新的 Revision hash。在把 Pod2 更新為新版本後,逐一删除 Pod0、Pod1,再去建立 Pod0、Pod1。

它的邏輯其實很簡單,在更新過程中 Controller 會把序号最大并且符合條件的 Pod 删除掉,那麼删除之後在下一次 Controller 在做 reconcile 的時候,它會發現缺少這個序号的 Pod,然後再按照新版本把 Pod 建立出來。

spec 字段解析

課時 22:有狀态應用編排 StatefulSet(酒祝)

首先來看一下 spec 中前幾個字段,Replica 和 Selector 都是我們比較熟悉的字段。

  • Replica 主要是期望的數量。
  • Selector 是事件選擇器,必須比對 spec.template.metadata.labels 中定義的條件。
  • Template:Pod 模闆,定義了所要建立的 Pod 的基礎資訊模闆。
  • VolumeClaimTemplates:PVC 模闆清單,如果在 spec 中定義了這個,PVC 會先于 Pod 模闆 Template 進行建立。在 PVC 建立完成後,把建立出來的 PVC name 作為一個 volume 注入到根據 Template 建立出來的 Pod 中。
課時 22:有狀态應用編排 StatefulSet(酒祝)
  • ServiceName:對應 Headless Service 的名字。當然如果有人不需要這個功能的時候,會給 Service 定一個不存在的 value,Controller 也不會去做校驗,是以可以寫一個 fake 的 ServiceName。但是這裡推薦每一個 Service 都要配置一個 Headless Service,不管 StatefulSet 下面的 Pod 是否需要網絡辨別。
  • PodMangementPolicy:Pod 管理政策。前面提到過這個字段的可選政策為 OrderedReady 和 Parallel,預設情況下為前者;
  • UpdataStrategy:Pod 更新政策。這是一個結構體,下面再詳細介紹。
  • RevisionHistoryLimit:保留曆史 ControllerRevision 的數量限制(預設為 10)。需要注意的一點是,這裡清楚的版本,必須沒有相關的 Pod 對應這些版本,如果有 Pod 還在這個版本中,這個 ControllerRevision 是不能被删除的。

更新政策字段解析

課時 22:有狀态應用編排 StatefulSet(酒祝)

在上圖右側可以看到 StatefulSetUpdateStrategy 有個 type 字段,這個 type 定義了兩個類型:一個是 RollingUpdate;一個是OnDelete。

RollingUpdate 其實跟 Deployment 中的更新是有點類似的,就是根據滾動更新的方式來更新。

OnDelete 是在删除的時候更新,叫做禁止主動更新,Controller 并不會把存活的 Pod 做主動更新,而是通過 OnDelete 的方式。比如說目前有三個舊版本的 Pod,但是更新政策是 OnDelete,是以當更新 spec 中鏡像的時候,Controller 并不會把三個 Pod 逐一更新為新版本,而是當我們縮小 Replica 的時候,Controller 會先把 Pod 删除掉,當我們下一次再進行擴容的時候,Controller 才會擴容出來新版本的 Pod。

在 RollingUpdateStatefulSetSetStrategy 中,可以看到有個字段叫 Partition。這個 Partition 表示滾動更新時,保留舊版本 Pod 的數量。很多剛結束 StatefulSet 的同學可能會認為這個是灰階新版本的數量,這是錯誤的。

舉個例子:假設目前有個 replicas 為 10 的 StatefulSet,當我們更新版本的時候,如果 Partition 是 8,并不是表示要把 8 個 Pod 更新為新版本,而是表示需要保留 8 個 Pod 為舊版本,隻更新 2 個新版本作為灰階。當 Replica 為 10 的時候,下面的 Pod 序号為 [0,9),是以當我們配置 Partition 為 8 的時候,其實還是保留 [0,7) 這 8個 Pod 為舊版本,隻有 [8,9) 進入新版本。

總結一下,假設 replicas=N,Partition=M (M<N),則最終舊版本 Pod 為 [0,M),新版本 Pod 為 [M,N)。通過這樣一個 Partition 的方式來達到灰階更新的目的,這是目前 Deployment 所不支援的。

本節總結

本節課的主要内容就到此為止了,這裡為大家簡單總結一下:

  • StatefulSet 是 Kubernetes 中常見的一種 Workload,其初始目标是面向有狀态應用部署,但也支援部署無狀态應用。
  • 與 Deployment 不同,StatefulSet 是直接操作 Pod 來做擴縮容/釋出,并沒有通過類似 ReplicaSet 的其他 workload 來管控。
  • StatefulSet 的特點是:支援每個 Pod 獨享 PVC、有一個唯一網絡辨別,且在更新釋出後還能複用 PVC 和網絡辨別。

繼續閱讀