從零開始入門 K8s | 有狀態應用編排 - StatefulSet

做者 | 酒祝  阿里巴巴技術專家html

本文整理自《CNCF x Alibaba 雲原生技術公開課》第 22 講。nginx

關注「阿里巴巴雲原生」公衆號,回覆關鍵詞**「入門」**,便可下載從零入門 K8s 系列文章 PPT。web

導讀:有狀態應用的部署交付向來都是應用運維領域的難點之一,常見的有狀態需求如在磁盤持久化狀態、每一個機器須要獨立且穩定的網絡標識、發佈順序肯定性等。針對這類問題 Kubernetes 提供了 StatefulSet 控制器,做爲幫助有狀態應用部署和在 K8s 環境落地的 Workload。後端

1、「有狀態」需求

咱們以前講到過 Deployment 做爲一個應用編排管理工具,它爲咱們提供了哪些功能?網絡

以下圖所示:架構

1.png

  • 首先它支持定義一組 Pod 的指望數量,Controller 會爲咱們維持 Pod 的數量在指望的版本以及指望的數量;
  • 第二它支持配置 Pod 發佈方式,配置完成後 Controller 會按照咱們給出的策略來更新 Pod,同時在更新的過程當中,也會保證不可用 Pod 數量在咱們定義的範圍內;
  • 第三,若是咱們在發佈的過程當中遇到問題,Deployment 也支持一鍵來回滾。

能夠簡單地說,**Deployment 認爲:它管理的全部相同版本的 Pod 都是如出一轍的副本。**也就是說,在 Deployment Controller 看來,全部相同版本的 Pod,不論是裏面部署的應用仍是行爲,都是徹底相同的。併發

這樣一種能力對於無狀態應用是支持知足的,若是咱們遇到一些有狀態應用呢?app

需求分析

好比下圖所示的一些需求: 2.pngless

以上的這些需求都是 Deployment 沒法知足的,所以 Kubernetes 社區爲咱們提供了一個叫 StatefluSet 的資源,用來管理有狀態應用。運維

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

其實如今社區不少無狀態應用也經過 StatefulSet 來管理,經過本文的學習,你們也會明白爲何咱們將部分無狀態應用也經過 StatefulSet 來管理。

3.png

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

如上圖右側所示:

  • 首先,每一個 Pod 會有 Order 序號,會按照序號來建立,刪除和更新 Pod;
  • 其次,經過配置一個 headless Service,使每一個 Pod 有一個惟一的網絡標識 (hostname);
  • 第三,經過配置 pvc 模板,就是 pvc template,使每一個 Pod 有一塊或者多塊 pv 存儲盤;
  • 最後,支持必定數量的灰度發佈。好比如今有三個副本的 StatefulSet,咱們能夠指定只升級其中的一個或者兩個,更甚至是三個到新版本。經過這樣的方式,來達到灰度升級的目的。

2、用例解讀

StatefulSet 範例建立

4.png

上圖左側是一個 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 狀態

5.png

經過將上文中的兩個對象建立以後,咱們能夠經過 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 地址。

6.png

經過 get pvc 能夠看到 NAME 那一列名稱,前綴爲 www-storage,中間是 nginx-web,後綴是一個序號。經過分析能夠知道 www-storage 是 volumeClaimTemplates 中定義的 name,中間爲 StatefulSet 定義的 name,末尾的序號對應着 Pod 的序號,也就是三個 PVC 分別被三個 Pod 綁定。經過這樣一種方式,達到不一樣的 Pod 享有不一樣的 PVC;PVC 也會綁定相應的一個 PV, 來達到不一樣的 Pod 綁定不一樣 PV 的目的。

Pod 的版本

7.png

以前咱們學到 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 會不會發生改變。

更新鏡像

8.png

經過執行上圖的命令,能夠看到上圖下方的 StatefulSet 配置中,已經把 StatefulSet 中的 image 更新到了 mainline 新版本。

查看新版本狀態

9.png

經過 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 已經升級到了所須要的版本。

3、操做演示

StatefulSet 編排文件

首先這裏已經鏈接到了阿里雲的一個集羣,集羣中有三個節點。 10.png

如今開始建立一個 StatefulSet 和對應的 Service,首先看一下對應的編排文件。 11.png

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

開始建立

12.png

執行上面的命令後,咱們就把 Service 和 StatefulSet 建立成功了。經過 get pod 能夠看到首先建立的 Pod 序號爲 0;經過 get pvc 能夠看到序號爲 0 的 PVC 已經和 PV 進行了綁定。

13.png

此時序號爲 0 的 Pod 已經開始建立了,狀態爲 ContainerCreating。

14.png

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

15.png

能夠看到每個 Pod 建立以前,會先建立 PVC。PVC 建立完成後,Pod 從 Pending 狀態和 PV 進行綁定,而後變成了 ContainerCreating,最後達到 Running。

查看狀態

而後經過 kubectl get sts nginx-web -o yaml 查看 StatefulSet 的狀態。

16.png

如上圖所示,指望的 replicas 數量爲 3 個,當前可用數量爲 3 個,而且達到最新的版本。

17.png

接着來看一下 Service 和 endpoints,能夠看到 Service 的 Port 爲 80,endpoint 有三個對應的 IP 地址。

18.png

再來 get pod ,能夠看到三個 Pod 對應了上面的 endpoints 的 IP 地址。

以上的操做結果爲:三個 PVC 和三個 Pod 已經達到了所指望的狀態,而且 StatefulSet 上報的 status 中,replicas 以及 currentReplicas 都爲三個。

升級操做

19.png

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

經過上面的命令,已經成功的將 StatefulSet 中的鏡像更新爲新的版本。

20.png

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

21.png

再次查看一下狀態,全部的 Pod 都已經 Running 狀態了。

22.png

查看一下 StatefulSet 信息,目前 StatefulSet 中的 status 裏定義的 currentRevision 已經更新到了新的版本,表示 StatefulSet 已經獲取到的三個 Pod 都已經進入了新版本。

23.png

如何查看這三個 Pod 是否還複用了以前的網絡標識和存儲盤呢?

其實 headless Service 配置的 hostname 只是和 Pod name 掛鉤的,因此只要升級後的 Pod 名稱和舊的 Pod 名稱相同,那麼就能夠沿用以前 Pod 使用的網絡標識。

關於存儲盤,由上圖能夠看到 PVC 的狀態,它們的建立時間一直沒有改變,仍是第一次建立 Pod 時的時間,因此如今升級後的 Pod 使用的仍是舊 Pod 中使用的 PVC。

24.png

好比能夠查看其中的某一個 Pod,這個 Pod 裏面一樣有個聲明的 volumes,這個 persistentVolumeClaim 裏的名稱 www-storage-nginx-web-0,對應着 PVC 列表中看到的序號爲 0 的 PVC,以前是被舊的 Pod 所使用。升級過程當中 Controller 刪除舊 Pod,而且建立了一個同名的新 Pod,新的 Pod 仍然複用了舊 Pod 所使用的 PVC。

經過這種方式來達到升級先後,網絡存儲都能複用的目的。

4、架構設計

管理模式

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 有惟一的序號。 25.png

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

這裏不一樣的地方在於,當前版本的 StatefulSet 只會在 ControllerRevision 和 Pod 中添加 OwnerReference,而不會在 PVC 中添加 OwnerReference。以前的系列文章中提到過,擁有 OwnerReference 的資源,在管理的這個資源進行刪除的默認狀況下,會關聯級聯刪除下屬資源。所以默認狀況下刪除 StatefulSet 以後,StatefulSet 建立的 ControllerRevision 和 Pod 都會被刪除,可是 PVC 由於沒有寫入 OwnerReference,PVC 並不會被級聯刪除。

StatefulSet 控制器

26.png

上圖爲 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 達到了管理有狀態應用的能力。

擴容模擬

假設 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。

擴縮容管理策略

28.png

可能有的同窗會有疑問:若是我不想按照序號建立和刪除,那 StatefulSet 也支持其它的建立和刪除的邏輯,這也就是爲何社區有些人把無狀態應用也經過 StatefulSet 來管理。它的好處是它能擁有惟一的網絡標識以及網絡存儲,同時也能經過併發的方式進行擴縮容。

StatefulSet.spec 中有個字段叫 podMangementPolicy 字段,這個字段的可選策略爲 OrderedReady 和 Parallel,默認狀況下爲前者。

如咱們剛纔建立的例子,沒有在 spec 中定義 podMangementPolicy。那麼 Controller 默認 OrderedReady 做爲策略,而後在 OrderedReady 狀況下,擴縮容就嚴格按照 Order 順序來執行,必需要等前面的 Pod 狀態爲 Ready 以後,才能擴容下一個 Pod。在縮容的時候,倒序刪除,序號從大到小進行刪除。

舉個例子,上圖右側中,從 Pod0 擴容到 Pod0、Pod一、Pod2 的時候,必須先建立 Pod1,等 Pod1 Ready 以後再建立 Pod2。其實還存在一種可能性:好比在建立 Pod1 的時候,Pod0 由於某些緣由,多是宿主機的緣由或者是應用自己的緣由,Pod0 變成 NotReady 狀態。這時 Controller 也不會建立 Pod2,因此不僅是咱們所建立 Pod 的前一個 Pod 要 Ready,而是前面全部的 Pod 都要 Ready 以後,纔會建立下一個 Pod。上圖中的例子,若是要建立 Pod2,那麼 Pod0、Pod1 都要 ready。

另外一種策略叫作 Parallel,顧名思義就是並行擴縮容,不須要等前面的 Pod 都 Ready 或者刪除後再處理下一個。

發佈模擬

29.png

假設這裏的 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 字段解析

30.png

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

  • Replica 主要是指望的數量;
  • Selector 是事件選擇器,必須匹配 spec.template.metadata.labels 中定義的條件;
  • Template:Pod 模板,定義了所要建立的 Pod 的基礎信息模板;
  • VolumeClaimTemplates:PVC 模板列表,若是在 spec 中定義了這個,PVC 會先於 Pod 模板 Template 進行建立。在 PVC 建立完成後,把建立出來的 PVC name 做爲一個 volume 注入到根據 Template 建立出來的 Pod 中。

31.png

  • 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 是不能被刪除的。

升級策略字段解析

32.png

在上圖右側能夠看到 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 所不支持的。

5、本節總結

本文的主要內容就到此爲止了,這裏爲你們簡單總結一下:

  • StatefulSet 是 Kubernetes 中常見的一種 Workload,其初始目標是面向有狀態應用部署,但也支持部署無狀態應用;
  • 與 Deployment 不一樣,StatefulSet 是直接操做 Pod 來作擴縮容/發佈,並無經過相似 ReplicaSet 的其餘 workload 來管控;
  • StatefulSet 的特色是:支持每一個 Pod 獨享 PVC、有一個惟一網絡標識,且在升級發佈後還能複用 PVC 和網絡標識;

直播推薦

2.11推廣海報.jpg

阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」

相關文章
相關標籤/搜索