从零开始入门 K8s | 有状态应用编排 - StatefulSet
作者 | 酒祝? 阿里巴巴技術(shù)專(zhuān)家
本文整理自《CNCF x Alibaba 云原生技術(shù)公開(kāi)課》第 22 講。
關(guān)注“阿里巴巴云原生”公眾號(hào),回復(fù)關(guān)鍵詞**“入門(mén)”**,即可下載從零入門(mén) K8s 系列文章 PPT。
導(dǎo)讀:有狀態(tài)應(yīng)用的部署交付向來(lái)都是應(yīng)用運(yùn)維領(lǐng)域的難點(diǎn)之一,常見(jiàn)的有狀態(tài)需求如在磁盤(pán)持久化狀態(tài)、每個(gè)機(jī)器需要獨(dú)立且穩(wěn)定的網(wǎng)絡(luò)標(biāo)識(shí)、發(fā)布順序確定性等。針對(duì)這類(lèi)問(wèn)題 Kubernetes 提供了 StatefulSet 控制器,作為幫助有狀態(tài)應(yīng)用部署和在 K8s 環(huán)境落地的 Workload。
一、“有狀態(tài)”需求
我們之前講到過(guò) Deployment 作為一個(gè)應(yīng)用編排管理工具,它為我們提供了哪些功能?
如下圖所示:
- 首先它支持定義一組 Pod 的期望數(shù)量,Controller 會(huì)為我們維持 Pod 的數(shù)量在期望的版本以及期望的數(shù)量;
- 第二它支持配置 Pod 發(fā)布方式,配置完成后 Controller 會(huì)按照我們給出的策略來(lái)更新 Pod,同時(shí)在更新的過(guò)程中,也會(huì)保證不可用 Pod 數(shù)量在我們定義的范圍內(nèi);
- 第三,如果我們?cè)诎l(fā)布的過(guò)程中遇到問(wèn)題,Deployment 也支持一鍵來(lái)回滾。
可以簡(jiǎn)單地說(shuō),**Deployment 認(rèn)為:它管理的所有相同版本的 Pod 都是一模一樣的副本。**也就是說(shuō),在 Deployment Controller 看來(lái),所有相同版本的 Pod,不管是里面部署的應(yīng)用還是行為,都是完全相同的。
這樣一種能力對(duì)于無(wú)狀態(tài)應(yīng)用是支持滿(mǎn)足的,如果我們遇到一些有狀態(tài)應(yīng)用呢?
需求分析
比如下圖所示的一些需求:
以上的這些需求都是 Deployment 無(wú)法滿(mǎn)足的,因此 Kubernetes 社區(qū)為我們提供了一個(gè)叫 StatefluSet 的資源,用來(lái)管理有狀態(tài)應(yīng)用。
StatefulSet:主要面向有狀態(tài)應(yīng)用管理的控制器
其實(shí)現(xiàn)在社區(qū)很多無(wú)狀態(tài)應(yīng)用也通過(guò) StatefulSet 來(lái)管理,通過(guò)本文的學(xué)習(xí),大家也會(huì)明白為什么我們將部分無(wú)狀態(tài)應(yīng)用也通過(guò) StatefulSet 來(lái)管理。
如上圖右側(cè)所示,StatefulSet 中的 Pod 都是有序號(hào)的,從 0 開(kāi)始一直到定義的 replica 數(shù)量減一。每個(gè) Pod 都有獨(dú)立的網(wǎng)絡(luò)標(biāo)識(shí):一個(gè) hostname、一塊獨(dú)立的 pvc 以及 pv 存儲(chǔ)。這樣的話(huà),同一個(gè) StatefulSet 下不同的 Pod,有不同的網(wǎng)絡(luò)標(biāo)識(shí)、有自己獨(dú)享的存儲(chǔ)盤(pán),這就能很好地滿(mǎn)足了絕大部分有狀態(tài)應(yīng)用的需求。
如上圖右側(cè)所示:
- 首先,每個(gè) Pod 會(huì)有 Order 序號(hào),會(huì)按照序號(hào)來(lái)創(chuàng)建,刪除和更新 Pod;
- 其次,通過(guò)配置一個(gè) headless Service,使每個(gè) Pod 有一個(gè)唯一的網(wǎng)絡(luò)標(biāo)識(shí) (hostname);
- 第三,通過(guò)配置 pvc 模板,就是 pvc template,使每個(gè) Pod 有一塊或者多塊 pv 存儲(chǔ)盤(pán);
- 最后,支持一定數(shù)量的灰度發(fā)布。比如現(xiàn)在有三個(gè)副本的 StatefulSet,我們可以指定只升級(jí)其中的一個(gè)或者兩個(gè),更甚至是三個(gè)到新版本。通過(guò)這樣的方式,來(lái)達(dá)到灰度升級(jí)的目的。
二、用例解讀
StatefulSet 范例創(chuàng)建
上圖左側(cè)是一個(gè) Service 的配置,我們通過(guò)配置 headless Service,其實(shí)想要達(dá)到的目標(biāo)是:期望 StatefulSet 里面的 Pod 有獨(dú)立的網(wǎng)絡(luò)標(biāo)識(shí)。這里的 Service name 叫 nginx。
上圖右側(cè)是一個(gè) StatefulSet 的配置,在 spec 中有個(gè) serviceName 也叫 nginx。通過(guò)這個(gè) serviceName 來(lái)指定這個(gè) StatefulSet 要對(duì)應(yīng)哪一個(gè) Service。
這個(gè) spec 中還有其它幾個(gè)很熟悉的字段,比如 selector 和 template。selector 是一個(gè)標(biāo)簽選擇器,selector 定義的標(biāo)簽選擇邏輯,必須匹配 template 中 metadata 中 labels 包含 app: nginx。在 template 中定義一個(gè) nginx container,這個(gè) container 用的 image 版本是 alpine 版本,對(duì)外暴露的 80 端口作為一個(gè) web 服務(wù)。
最后,template.spec 里面定義了一個(gè) volumeMounts,這個(gè) volumeMounts 并不是來(lái)源于 spec 中的一個(gè) Volumes,而是來(lái)自于 volumeClaimTemplates,也就是 pvc 模板。我們?cè)?pvc 模板中定義了一個(gè)叫 www-storage 的 pvc 名稱(chēng)。這個(gè) pvc 名稱(chēng),我們也會(huì)寫(xiě)到 volumeMounts 作為一個(gè) volume name,掛載到? /usr/share/nginx/html 這個(gè)目錄下。通過(guò)這樣的方式來(lái)達(dá)到每個(gè) Pod 都有獨(dú)立的一個(gè) pvc,并且掛載到容器中對(duì)應(yīng)目錄的一個(gè)需求。
Service、StatefulSet 狀態(tài)
通過(guò)將上文中的兩個(gè)對(duì)象創(chuàng)建之后,我們可以通過(guò) get 命令可以看到 Service nginx 資源已經(jīng)創(chuàng)建成功。
同時(shí)可以通過(guò)查看 endpoints 看到,這個(gè)后端已經(jīng)注冊(cè)了三個(gè) IP 和端口,這三個(gè) IP 對(duì)應(yīng)了 Pod 的 IP,端口對(duì)應(yīng)了之前 spec 中配置的 80 端口。
最后通過(guò) get sts (StatefulSet 縮寫(xiě)) nginx-web。從結(jié)果可以看到有一列叫做 READY,值為 3/3。分母 3 是 StatefulSet 中期望的數(shù)量,而分子 3 表示 Pod 已經(jīng)達(dá)到期望 READY 的狀態(tài)數(shù)量。
Pod、PVC 狀態(tài)
下圖中的 get pod 可以看到三個(gè) Pod 的狀態(tài)都是 Running 狀態(tài),并且已經(jīng) READY。它的 IP 就是前面看到的 endpoint 地址。
通過(guò) get pvc 可以看到 NAME 那一列名稱(chēng),前綴為 www-storage,中間是 nginx-web,后綴是一個(gè)序號(hào)。通過(guò)分析可以知道 www-storage 是 volumeClaimTemplates 中定義的 name,中間為 StatefulSet 定義的 name,末尾的序號(hào)對(duì)應(yīng)著 Pod 的序號(hào),也就是三個(gè) PVC 分別被三個(gè) Pod 綁定。通過(guò)這樣一種方式,達(dá)到不同的 Pod 享有不同的 PVC;PVC 也會(huì)綁定相應(yīng)的一個(gè) PV, 來(lái)達(dá)到不同的 Pod 綁定不同 PV 的目的。
Pod 的版本
之前我們學(xué)到 Deployment 使用 ReplicaSet 來(lái)管理 Pod 的版本和所期望的 Pod 數(shù)量,但是在 StatefulSet 中,是由 StatefulSet Controller 來(lái)管理下屬的 Pod,因此 StatefulSet 通過(guò) Pod 的 label 來(lái)標(biāo)識(shí)這個(gè) Pod 所屬的版本,這里叫 controller-revision-hash。這個(gè) label 標(biāo)識(shí)和 Deployment 以及 StatefulSet 在 Pod 中注入的 Pod template hash 是類(lèi)似的。
如上圖所示,通過(guò) get pod 查看到 controller-revision-hash,這里的 hash 就是第一次創(chuàng)建 Pod 對(duì)應(yīng)的 template 版本,可以看到后綴是 677759c9b8。這里先記錄一下,接下來(lái)會(huì)做 Pod 升級(jí),再來(lái)看一下 controller-revision-hash 會(huì)不會(huì)發(fā)生改變。
更新鏡像
通過(guò)執(zhí)行上圖的命令,可以看到上圖下方的 StatefulSet 配置中,已經(jīng)把 StatefulSet 中的 image 更新到了 mainline 新版本。
查看新版本狀態(tài)
通過(guò) get pod 命令查詢(xún) Revision hash,可以看到三個(gè) Pod 后面的 controller-revision-hash 都已經(jīng)升級(jí)到了新的 Revision hash,后面變成了 7c55499668。通過(guò)這三個(gè) Pod 創(chuàng)建的時(shí)間可以發(fā)現(xiàn):序號(hào)為 2 的 Pod 創(chuàng)建的是最早的,之后是序號(hào)是 1 和 0。這表示在升級(jí)的過(guò)程中,真實(shí)的升級(jí)順序?yàn)?2-1-0,通過(guò)這么一個(gè)倒序的順序來(lái)逐漸把 Pod 升級(jí)為新版本,并且我們升級(jí)的 Pod,還復(fù)用了之前 Pod 使用的 PVC。所以之前在 PV 存儲(chǔ)盤(pán)中的數(shù)據(jù),仍然會(huì)掛載到新的 Pod 上。
上圖右上方是在 StatefulSet 的 status 中看到的數(shù)據(jù),這里有幾個(gè)重要的字段:
- currentReplica:表示當(dāng)前版本的數(shù)量
- **currentRevision: **表示當(dāng)前版本號(hào)
- updateReplicas:表示新版本的數(shù)量
- **updateRevision:**表示當(dāng)前要更新的版本號(hào)
當(dāng)然這里也能看到 currentReplica 和 updateReplica,以及 currentRevision 和 updateRevision 都是一樣的,這就表示所有 Pod 已經(jīng)升級(jí)到了所需要的版本。
三、操作演示
StatefulSet 編排文件
首先這里已經(jīng)連接到了阿里云的一個(gè)集群,集群中有三個(gè)節(jié)點(diǎn)。
現(xiàn)在開(kāi)始創(chuàng)建一個(gè) StatefulSet 和對(duì)應(yīng)的 Service,首先看一下對(duì)應(yīng)的編排文件。
如上圖中的例子所示,Service 對(duì)應(yīng)的 nginx 對(duì)外暴露的 80 端口。StatefulSet 配置中 metadata 定義了 name 為 nginx-web;template 中的 containers 定義了鏡像信息;最后定義了一個(gè) volumeClaimTemplates 作為 PVC 模板。
開(kāi)始創(chuàng)建
執(zhí)行上面的命令后,我們就把 Service 和 StatefulSet 創(chuàng)建成功了。通過(guò) get pod 可以看到首先創(chuàng)建的 Pod 序號(hào)為 0;通過(guò) get pvc 可以看到序號(hào)為 0 的 PVC 已經(jīng)和 PV 進(jìn)行了綁定。
此時(shí)序號(hào)為 0 的 Pod 已經(jīng)開(kāi)始創(chuàng)建了,狀態(tài)為 ContainerCreating。
當(dāng)序號(hào)為 0 的 Pod 創(chuàng)建完成后,開(kāi)始創(chuàng)建序號(hào)為 1 的 Pod,然后看到新的 PVC 也已經(jīng)創(chuàng)建成功,緊接著是序號(hào)為 2 的 Pod。
可以看到每一個(gè) Pod 創(chuàng)建之前,會(huì)先創(chuàng)建 PVC。PVC 創(chuàng)建完成后,Pod 從 Pending 狀態(tài)和 PV 進(jìn)行綁定,然后變成了 ContainerCreating,最后達(dá)到 Running。
查看狀態(tài)
然后通過(guò) kubectl get sts nginx-web -o yaml 查看 StatefulSet 的狀態(tài)。
如上圖所示,期望的 replicas 數(shù)量為 3 個(gè),當(dāng)前可用數(shù)量為 3 個(gè),并且達(dá)到最新的版本。
接著來(lái)看一下 Service 和 endpoints,可以看到 Service 的 Port 為 80,endpoint 有三個(gè)對(duì)應(yīng)的 IP 地址。
再來(lái) get pod ,可以看到三個(gè) Pod 對(duì)應(yīng)了上面的 endpoints 的 IP 地址。
以上的操作結(jié)果為:三個(gè) PVC 和三個(gè) Pod 已經(jīng)達(dá)到了所期望的狀態(tài),并且 StatefulSet 上報(bào)的 status 中,replicas 以及 currentReplicas 都為三個(gè)。
升級(jí)操作
這里重復(fù)說(shuō)一下,kubectl set image 為聲明鏡像的固定寫(xiě)法;StatefulSet 表示自愿類(lèi)型;nginx-web是資源名稱(chēng);nginx=nginx:mainline,等號(hào)前面的 nginx 是我們?cè)?template 中定義的 container 名稱(chēng),后面的nginx:mainline是所期望更新的鏡像版本。
通過(guò)上面的命令,已經(jīng)成功的將 StatefulSet 中的鏡像更新為新的版本。
通過(guò) get pod 看一下?tīng)顟B(tài),nginx-web-1,nginx-web-2 已經(jīng)進(jìn)入了 Running 狀態(tài)。對(duì)應(yīng)的 controller-revision-hash 已經(jīng)是新的版本。那么 nginx-web-0 這個(gè) Pod,舊的 Pod 已經(jīng)被刪除了,新 Pod 還在 Createing 狀態(tài)。
再次查看一下?tīng)顟B(tài),所有的 Pod 都已經(jīng) Running 狀態(tài)了。
查看一下 StatefulSet 信息,目前 StatefulSet 中的 status 里定義的 currentRevision 已經(jīng)更新到了新的版本,表示 StatefulSet 已經(jīng)獲取到的三個(gè) Pod 都已經(jīng)進(jìn)入了新版本。
如何查看這三個(gè) Pod 是否還復(fù)用了之前的網(wǎng)絡(luò)標(biāo)識(shí)和存儲(chǔ)盤(pán)呢?
其實(shí) headless Service 配置的 hostname 只是和 Pod name 掛鉤的,所以只要升級(jí)后的 Pod 名稱(chēng)和舊的 Pod 名稱(chēng)相同,那么就可以沿用之前 Pod 使用的網(wǎng)絡(luò)標(biāo)識(shí)。
關(guān)于存儲(chǔ)盤(pán),由上圖可以看到 PVC 的狀態(tài),它們的創(chuàng)建時(shí)間一直沒(méi)有改變,還是第一次創(chuàng)建 Pod 時(shí)的時(shí)間,所以現(xiàn)在升級(jí)后的 Pod 使用的還是舊 Pod 中使用的 PVC。
比如可以查看其中的某一個(gè) Pod,這個(gè) Pod 里面同樣有個(gè)聲明的 volumes,這個(gè) persistentVolumeClaim 里的名稱(chēng) www-storage-nginx-web-0,對(duì)應(yīng)著 PVC 列表中看到的序號(hào)為 0 的 PVC,之前是被舊的 Pod 所使用。升級(jí)過(guò)程中 Controller 刪除舊 Pod,并且創(chuàng)建了一個(gè)同名的新 Pod,新的 Pod 仍然復(fù)用了舊 Pod 所使用的 PVC。
通過(guò)這種方式來(lái)達(dá)到升級(jí)前后,網(wǎng)絡(luò)存儲(chǔ)都能復(fù)用的目的。
四、架構(gòu)設(shè)計(jì)
管理模式
StatefulSet 可能會(huì)創(chuàng)建三種類(lèi)型的資源。
- 第一種資源:ControllerRevision
通過(guò)這個(gè)資源,StatefulSet 可以很方便地管理不同版本的 template 模板。
舉個(gè)例子:比如上文中提到的 nginx,在創(chuàng)建之初擁有的第一個(gè) template 版本,會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的 ControllerRevision。而當(dāng)修改了 image 版本之后,StatefulSet Controller 會(huì)創(chuàng)建一個(gè)新的 ControllerRevision,大家可以理解為每一個(gè) ControllerRevision 對(duì)應(yīng)了每一個(gè)版本的 Template,也對(duì)應(yīng)了每一個(gè)版本的 ControllerRevision hash。其實(shí)在 Pod label 中定義的 ControllerRevision hash,就是 ControllerRevision 的名字。通過(guò)這個(gè)資源 StatefulSet Controller 來(lái)管理不同版本的 template 資源。
- 第二個(gè)資源:PVC
如果在 StatefulSet 中定義了 volumeClaimTemplates,StatefulSet 會(huì)在創(chuàng)建 Pod 之前,先根據(jù)這個(gè)模板創(chuàng)建 PVC,并把 PVC 加到 Pod volume 中。
如果用戶(hù)在 spec 的 pvc 模板中定義了 volumeClaimTemplates,StatefulSet 在創(chuàng)建 Pod 之前,根據(jù)模板創(chuàng)建 PVC,并加到 Pod 對(duì)應(yīng)的 volume 中。當(dāng)然也可以在 spec 中不定義 pvc template,那么所創(chuàng)建出來(lái)的 Pod 就不會(huì)掛載單獨(dú)的一個(gè) pv。
- 第三個(gè)資源:Pod
StatefulSet 按照順序創(chuàng)建、刪除、更新 Pod,每個(gè) Pod 有唯一的序號(hào)。
如上圖所示,StatefulSet Controller 是 Owned 三個(gè)資源:ControllerRevision、Pod、PVC。
這里不同的地方在于,當(dāng)前版本的 StatefulSet 只會(huì)在 ControllerRevision 和 Pod 中添加 OwnerReference,而不會(huì)在 PVC 中添加 OwnerReference。之前的系列文章中提到過(guò),擁有 OwnerReference 的資源,在管理的這個(gè)資源進(jìn)行刪除的默認(rèn)情況下,會(huì)關(guān)聯(lián)級(jí)聯(lián)刪除下屬資源。因此默認(rèn)情況下刪除 StatefulSet 之后,StatefulSet 創(chuàng)建的 ControllerRevision 和 Pod 都會(huì)被刪除,但是 PVC 因?yàn)闆](méi)有寫(xiě)入 OwnerReference,PVC 并不會(huì)被級(jí)聯(lián)刪除。
StatefulSet 控制器
上圖為 StatefulSet 控制器的工作流程,下面來(lái)簡(jiǎn)單介紹一下整個(gè)工作處理流程。
首先通過(guò)注冊(cè) Informer 的 Event Handler(事件處理),來(lái)處理 StatefulSet 和 Pod 的變化。在 Controller 邏輯中,每一次收到 StatefulSet 或者是 Pod 的變化,都會(huì)找到對(duì)應(yīng)的 StatefulSet 放到隊(duì)列。緊接著從隊(duì)列取出來(lái)處理后,先做的操作是 Update Revision,也就是先查看當(dāng)前拿到的 StatefulSet 中的 template,有沒(méi)有對(duì)應(yīng)的 ControllerRevision。如果沒(méi)有,說(shuō)明 template 已經(jīng)更新過(guò),Controller 就會(huì)創(chuàng)建一個(gè)新版本的 Revision,也就有了一個(gè)新的 ControllerRevision hash 版本號(hào)。
然后 Controller 會(huì)把所有版本號(hào)拿出來(lái),并且按照序號(hào)整理一遍。這個(gè)整理的過(guò)程中,如果發(fā)現(xiàn)有缺少的 Pod,它就會(huì)按照序號(hào)去創(chuàng)建,如果發(fā)現(xiàn)有多余的 Pod,就會(huì)按照序號(hào)去刪除。當(dāng)保證了 Pod 數(shù)量和 Pod 序號(hào)滿(mǎn)足 Replica 數(shù)量之后,Controller 會(huì)去查看是否需要更新 Pod。也就是說(shuō)這兩步的區(qū)別在于,Manger pods in order 去查看所有的 Pod 是否滿(mǎn)足序號(hào);而后者 Update in order 查看 Pod 期望的版本是否符合要求,并且通過(guò)序號(hào)來(lái)更新。
Update in order?其更新過(guò)程如上圖所示,其實(shí)這個(gè)過(guò)程比較簡(jiǎn)單,就是刪除 Pod。刪除 Pod 之后,其實(shí)是在下一次觸發(fā)事件,Controller 拿到這個(gè) success 之后會(huì)發(fā)現(xiàn)缺少 Pod,然后再?gòu)那耙粋€(gè)步驟 Manger pod in order 中把新的 Pod 創(chuàng)建出來(lái)。在這之后 Controller 會(huì)做一次 Update status,也就是之前通過(guò)命令行看到的 status 信息。
通過(guò)整個(gè)這樣的一個(gè)流程,StatefulSet 達(dá)到了管理有狀態(tài)應(yīng)用的能力。
擴(kuò)容模擬
假設(shè) StatefulSet 初始配置 replicas 為 1,有一個(gè) Pod0。那么將 replicas 從 1 修改到 3 之后,其實(shí)我們是先創(chuàng)建 Pod1,默認(rèn)情況是等待 Pod1 狀態(tài) READY 之后,再創(chuàng)建 Pod2。
通過(guò)上圖可以看到每個(gè) StatefulSet 下面的 Pod 都是從序號(hào) 0 開(kāi)始創(chuàng)建的。因此一個(gè) replicas 為 N 的 StatefulSet,它創(chuàng)建出來(lái)的 Pod 序號(hào)為 [0,N),0 是開(kāi)曲線,N 是閉曲線,也就是當(dāng) N>0 的時(shí)候,序號(hào)為 0 到 N-1。
擴(kuò)縮容管理策略
可能有的同學(xué)會(huì)有疑問(wèn):如果我不想按照序號(hào)創(chuàng)建和刪除,那 StatefulSet 也支持其它的創(chuàng)建和刪除的邏輯,這也就是為什么社區(qū)有些人把無(wú)狀態(tài)應(yīng)用也通過(guò) StatefulSet 來(lái)管理。它的好處是它能擁有唯一的網(wǎng)絡(luò)標(biāo)識(shí)以及網(wǎng)絡(luò)存儲(chǔ),同時(shí)也能通過(guò)并發(fā)的方式進(jìn)行擴(kuò)縮容。
StatefulSet.spec 中有個(gè)字段叫 podMangementPolicy 字段,這個(gè)字段的可選策略為 OrderedReady 和 Parallel,默認(rèn)情況下為前者。
如我們剛才創(chuàng)建的例子,沒(méi)有在 spec 中定義 podMangementPolicy。那么 Controller 默認(rèn) OrderedReady 作為策略,然后在 OrderedReady 情況下,擴(kuò)縮容就嚴(yán)格按照 Order 順序來(lái)執(zhí)行,必須要等前面的 Pod 狀態(tài)為 Ready 之后,才能擴(kuò)容下一個(gè) Pod。在縮容的時(shí)候,倒序刪除,序號(hào)從大到小進(jìn)行刪除。
舉個(gè)例子,上圖右側(cè)中,從 Pod0 擴(kuò)容到 Pod0、Pod1、Pod2 的時(shí)候,必須先創(chuàng)建 Pod1,等 Pod1 Ready 之后再創(chuàng)建 Pod2。其實(shí)還存在一種可能性:比如在創(chuàng)建 Pod1 的時(shí)候,Pod0 因?yàn)槟承┰?#xff0c;可能是宿主機(jī)的原因或者是應(yīng)用本身的原因,Pod0 變成 NotReady 狀態(tài)。這時(shí) Controller 也不會(huì)創(chuàng)建 Pod2,所以不只是我們所創(chuàng)建 Pod 的前一個(gè) Pod 要 Ready,而是前面所有的 Pod 都要 Ready 之后,才會(huì)創(chuàng)建下一個(gè) Pod。上圖中的例子,如果要?jiǎng)?chuàng)建 Pod2,那么 Pod0、Pod1 都要 ready。
另一種策略叫做 Parallel,顧名思義就是并行擴(kuò)縮容,不需要等前面的 Pod 都 Ready 或者刪除后再處理下一個(gè)。
發(fā)布模擬
假設(shè)這里的 StatefulSet template1 對(duì)應(yīng)邏輯上的 Revision1,這時(shí) StatefulSet 下面的三個(gè) Pod 都屬于 Revision1 版本。在我們修改了 template,比如修改了鏡像之后,Controller 是通過(guò)倒序的方式逐一升級(jí) Pod。上圖中可以看到 Controller 先創(chuàng)建了一個(gè) Revision2,對(duì)應(yīng)的就是創(chuàng)建了 ControllerRevision2 這么一個(gè)資源,并且將 ControllerRevision2 這個(gè)資源的 name 作為一個(gè)新的 Revision hash。在把 Pod2 升級(jí)為新版本后,逐一刪除 Pod0、Pod1,再去創(chuàng)建 Pod0、Pod1。
它的邏輯其實(shí)很簡(jiǎn)單,在升級(jí)過(guò)程中 Controller 會(huì)把序號(hào)最大并且符合條件的 Pod 刪除掉,那么刪除之后在下一次 Controller 在做 reconcile?的時(shí)候,它會(huì)發(fā)現(xiàn)缺少這個(gè)序號(hào)的 Pod,然后再按照新版本把 Pod 創(chuàng)建出來(lái)。
spec 字段解析
首先來(lái)看一下 spec 中前幾個(gè)字段,Replica 和 Selector 都是我們比較熟悉的字段。
- Replica 主要是期望的數(shù)量;
- Selector 是事件選擇器,必須匹配 spec.template.metadata.labels 中定義的條件;
- Template:Pod 模板,定義了所要?jiǎng)?chuàng)建的 Pod 的基礎(chǔ)信息模板;
- VolumeClaimTemplates:PVC 模板列表,如果在 spec 中定義了這個(gè),PVC 會(huì)先于 Pod 模板 Template 進(jìn)行創(chuàng)建。在 PVC 創(chuàng)建完成后,把創(chuàng)建出來(lái)的 PVC name 作為一個(gè) volume 注入到根據(jù) Template 創(chuàng)建出來(lái)的 Pod 中。
- ServiceName:對(duì)應(yīng) Headless Service 的名字。當(dāng)然如果有人不需要這個(gè)功能的時(shí)候,會(huì)給 Service 定一個(gè)不存在的 value,Controller 也不會(huì)去做校驗(yàn),所以可以寫(xiě)一個(gè) fake 的 ServiceName。但是這里推薦每一個(gè) Service 都要配置一個(gè) Headless Service,不管 StatefulSet 下面的 Pod 是否需要網(wǎng)絡(luò)標(biāo)識(shí);
- PodMangementPolicy:Pod 管理策略。前面提到過(guò)這個(gè)字段的可選策略為 OrderedReady 和 Parallel,默認(rèn)情況下為前者;
- UpdataStrategy:Pod 升級(jí)策略。這是一個(gè)結(jié)構(gòu)體,下面再詳細(xì)介紹;
- RevisionHistoryLimit:保留歷史 ControllerRevision 的數(shù)量限制(默認(rèn)為 10)。需要注意的一點(diǎn)是,這里清楚的版本,必須沒(méi)有相關(guān)的 Pod 對(duì)應(yīng)這些版本,如果有 Pod 還在這個(gè)版本中,這個(gè) ControllerRevision 是不能被刪除的。
升級(jí)策略字段解析
在上圖右側(cè)可以看到 StatefulSetUpdateStrategy 有個(gè) type 字段,這個(gè) type 定義了兩個(gè)類(lèi)型:一個(gè)是 RollingUpdate;一個(gè)是OnDelete。
-
RollingUpdate 其實(shí)跟 Deployment 中的升級(jí)是有點(diǎn)類(lèi)似的,就是根據(jù)滾動(dòng)升級(jí)的方式來(lái)升級(jí);
-
OnDelete 是在刪除的時(shí)候升級(jí),叫做禁止主動(dòng)升級(jí),Controller 并不會(huì)把存活的 Pod 做主動(dòng)升級(jí),而是通過(guò) OnDelete 的方式。比如說(shuō)當(dāng)前有三個(gè)舊版本的 Pod,但是升級(jí)策略是 OnDelete,所以當(dāng)更新 spec 中鏡像的時(shí)候,Controller 并不會(huì)把三個(gè) Pod 逐一升級(jí)為新版本,而是當(dāng)我們縮小 Replica 的時(shí)候,Controller 會(huì)先把 Pod 刪除掉,當(dāng)我們下一次再進(jìn)行擴(kuò)容的時(shí)候,Controller 才會(huì)擴(kuò)容出來(lái)新版本的 Pod。
在 RollingUpdateStatefulSetSetStrategy 中,可以看到有個(gè)字段叫 Partition。這個(gè) Partition 表示滾動(dòng)升級(jí)時(shí),保留舊版本 Pod 的數(shù)量。很多剛結(jié)束 StatefulSet 的同學(xué)可能會(huì)認(rèn)為這個(gè)是灰度新版本的數(shù)量,這是錯(cuò)誤的。
舉個(gè)例子:假設(shè)當(dāng)前有個(gè) replicas 為 10 的 StatefulSet,當(dāng)我們更新版本的時(shí)候,如果 Partition 是 8,并不是表示要把 8 個(gè) Pod 更新為新版本,而是表示需要保留 8 個(gè) Pod 為舊版本,只更新 2 個(gè)新版本作為灰度。當(dāng) Replica 為 10 的時(shí)候,下面的 Pod 序號(hào)為 [0,9),因此當(dāng)我們配置 Partition 為 8 的時(shí)候,其實(shí)還是保留 [0,7) 這 8個(gè) Pod 為舊版本,只有 [8,9) 進(jìn)入新版本。
總結(jié)一下,假設(shè) replicas=N,Partition=M (M < N) ,則最終舊版本 Pod 為 [0,M) ,新版本 Pod 為 [M,N)。通過(guò)這樣一個(gè) Partition 的方式來(lái)達(dá)到灰度升級(jí)的目的,這是目前 Deployment 所不支持的。
五、本節(jié)總結(jié)
本文的主要內(nèi)容就到此為止了,這里為大家簡(jiǎn)單總結(jié)一下:
- StatefulSet 是 Kubernetes 中常見(jiàn)的一種 Workload,其初始目標(biāo)是面向有狀態(tài)應(yīng)用部署,但也支持部署無(wú)狀態(tài)應(yīng)用;
- 與 Deployment 不同,StatefulSet 是直接操作 Pod 來(lái)做擴(kuò)縮容/發(fā)布,并沒(méi)有通過(guò)類(lèi)似 ReplicaSet 的其他 workload 來(lái)管控;
- StatefulSet 的特點(diǎn)是:支持每個(gè) Pod 獨(dú)享 PVC、有一個(gè)唯一網(wǎng)絡(luò)標(biāo)識(shí),且在升級(jí)發(fā)布后還能復(fù)用 PVC 和網(wǎng)絡(luò)標(biāo)識(shí);
直播推薦
“阿里巴巴云原生關(guān)注微服務(wù)、Serverless、容器、Service Mesh 等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢(shì)、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開(kāi)發(fā)者的技術(shù)圈。”
總結(jié)
以上是生活随笔為你收集整理的从零开始入门 K8s | 有状态应用编排 - StatefulSet的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 你不得不了解 Helm 3 中的 5 个
- 下一篇: 五分钟学会使用 go modules(含