- 一、概述
- 二、引入"有狀態"需求
- 1、管理無狀態服務的 Deployment 實現了什麼
- 1.1、建立 Deployment
- 1.2、驗證 Pod 數量
- 1.3、配置更新策略(更新映象版本)
- 1.4、觀察更新過程
- 1.5、驗證更新後 Pod 的狀態
- 1.6、回滾 Deployment
- 2、新需求分析
- 1、管理無狀態服務的 Deployment 實現了什麼
- 三、StatefulSet:面向有狀態應用管理的控制器
- 1、建立 Service、Statefulset
- 2、Service、StatefulSet 狀態
- 3、Pod、PVC 狀態
- 4、Pod 的版本控制
- 5、更新映象
- 6、檢視新版本的狀態
- 7、如何檢視 Pod 是否複用了之前的網路標識
- 8、如何檢視 Pod 是否複用了之前的儲存盤
- 四、架構設計
- 1、管理模式
- 1.1、ControllerRevision
- 1.2、PVC
- 1.3、Pod
- 2、OwnerReference
- 2.1、刪除 StatefulSet
- 3、StatefulSet 控制器工作流程
- 4、擴容能力
- 5、擴縮容管理策略
- 6、釋出模擬
- 1、管理模式
- 五、欄位分析
- 1、spec 欄位解析
- 2、升級策略欄位解析
一、概述
StatefulSet
是 Kubernetes 中用於管理有狀態應用程式的工作負載資源物件。它提供了一種管理有狀態服務的方式,確保每個 Pod 都有一個唯一的、持久的身份,並支援持久化儲存。它在 Kubernetes v1.9 版本中成為 GA 版本。StatefulSet
設計用於管理和部署有狀態服務,其管理的 Pod 擁有固定的名稱(通常是
在 StatefulSet
中,Pod 名稱可以用作網路識別符號,並且通常與 Headless Service
結合使用。Headless Service
沒有 Cluster IP
,直接暴露 Pod 的 IP 地址或 DNS 名稱。解析 Headless Service
的名稱時,會返回所有 Pod 的 IP 地址或 DNS 名稱。
此外,StatefulSet 在 Headless Service 的基礎上為每個 Pod 建立了一個 DNS 域名,這些域名通常形如
StatefulSet
支援持久化儲存,它可以使用多種持久化儲存型別,包括本地儲存、網路檔案系統(NFS)等。
二、引入"有狀態"需求
1、管理無狀態服務的 Deployment 實現了什麼
首先它支援定義一組 Pod 的期望數量,Controller
會為我們維持 Pod 的數量在期望的版本以及期望的數量;
第二它支援配置 Pod 釋出方式,配置完成後 Controller
會按照我們給出的策略來更新 Pod,同時在更新的過程中,也會保證不可用 Pod 數量在我們定義的範圍內;
第三,如果我們在釋出的過程中遇到問題,Deployment
也支援一鍵來回滾。
簡單來說,Deployment 認為:它管理的所有相同版本的 Pod 都是一模一樣的副本。也就是說,在 Deployment Controller
看來,所有相同版本的 Pod,不管是裡面部署的應用還是行為,都是完全相同的。
1.1、建立 Deployment
cat >> web-app.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
annotations:
deployment.kubernetes.io/revision: "1"
spec:
containers:
- name: web-server
image: nginx:1.15.0
ports:
- containerPort: 80
EOF
kubectl apply -f web-app.yaml --record=true
1.2、驗證 Pod 數量
透過 Deployment
控制器,我們可以驗證 Pod 的數量是否維持在期望的數量(3 個)。
kubectl get pods -l app=web-app
1.3、配置更新策略(更新映象版本)
kubectl set image deploy web-app web-server=nginx:1.16.0 --record=true
1.4、觀察更新過程
kubectl rollout status deploy web-app
1.5、驗證更新後 Pod 的狀態
kubectl get pods -l app=web-app -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[*].image,STATUS:.status.phase'
1.6、回滾 Deployment
以下就是回滾到指定版本的流程:
檢視歷史版本資訊 --> 檢視指定版本詳細資訊 --> 回滾到指定版本 --> 驗證資訊
kubectl rollout history deploy web-app
kubectl rollout history deploy web-app --revision=1
kubectl rollout undo deploy web-app --to-revision=1
kubectl get pods -l app=web-app -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[*].image,STATUS:.status.phase'
以上就是管理無狀態服務的
Deployment
實現了什麼功能展示
2、新需求分析
以下需求來自於一些有狀態應用:
- Pod 之間並非相同的副本,每一個 Pod 都有一個獨立標識
- Pod 獨立標識要能夠對應到一個固定的網路標識,並在釋出升級後繼續保持
- 每一個 Pod 有一塊獨立的儲存盤,並在釋出升級後還能繼續掛載原有的盤(保留資料)
這些有狀態應用的需求都是 Deploment
無法滿足的,因此引入了管理有狀態應用的 StatefulSet
三、StatefulSet:面向有狀態應用管理的控制器
- 首先,每個 Pod 會有 Order 序號,會按照序號來建立,刪除和更新 Pod;
- 其次,透過配置一個
headless Service
,使每個 Pod 有一個唯一的網路標識 (hostname
); - 第三,透過配置 pvc 模板,就是
pvc template
,使每個 Pod 有一塊或者多塊 pv 儲存盤; - 最後,支援一定數量的灰度釋出。比如現在有三個副本的
StatefulSet
,我們可以指定只升級其中的一個或者兩個,更甚至是三個到新版本。透過這樣的方式,來達到灰度升級的目的。
1、建立 Service、Statefulset
cat >>service-nginx.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None
ports:
- name: web
port: 80
selector:
app: nginx
EOF
這是一個 Service
的配置,我們透過配置 headless Service
,其實想要達到的目標是:期望 StatefulSet
裡面的 Pod 有獨立的網路標識。這裡的 Service name
叫 nginx。
cat >> nginx-web.yaml << EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.0
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www-storage
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
EOF
這是一個 StatefulSet
的配置,在 spec 中有個 serviceName
也叫 nginx。透過這個 serviceName
來指定這個 StatefulSet
要對應哪一個 Service
。
這個 spec 中還定義了 selector
和 template
。selector
是一個標籤選擇器,selector
定義的標籤選擇邏輯,必須匹配 template
中 metadata
中 labels
包含 app: nginx
。在 template
中定義一個 nginx container
,這個 container
用的 image 版本是 1.16.0 版本,對外暴露的 80 埠作為一個 web 服務。
最後,template.spec
裡面定義了一個 volumeMounts
,這個 volumeMounts
並不是來源於 spec 中的一個 Volumes,而是來自於 volumeClaimTemplates
,也就是 pvc 模板。我們在 pvc 模板中定義了一個叫 www-storage
的 pvc 名稱。這個 pvc 名稱,我們也會寫到 volumeMounts
作為一個 volume name
,掛載到 /usr/share/nginx/html
這個目錄下。透過這樣的方式來達到每個 Pod 都有獨立的一個 pvc,並且掛載到容器中對應目錄的一個需求。
kubectl apply -f service-nginx.yaml -f nginx-web.yaml
2、Service、StatefulSet 狀態
kubectl get svc nginx
kubectl get endpoints nginx
kubectl get sts nginx-web
透過將上文中的兩個物件建立之後,我們可以透過 get 命令可以看到 Service nginx
資源已經建立成功。
同時可以透過檢視 endpoints
看到,這個後端已經註冊了三個 IP 和埠,這三個 IP 對應了 Pod 的 IP,埠對應了之前 spec 中配置的 80 埠。
最後透過 get sts (StatefulSet 縮寫) nginx-web
。從結果可以看到有一列 READY,值為 3/3。分母 3 是 StatefulSet
中期望的數量,而分子 3 表示 Pod 已經達到期望 READY 的狀態數量。
3、Pod、PVC 狀態
kubectl get pods -l app=nginx
kubectl get pvc
透過 get pvc 可以看到 NAME 那一列名稱,字首為 www-storage
,中間是 nginx-web
,字尾是一個序號。透過分析可以知道 www-storage
是 volumeClaimTemplates
中定義的 name,中間為 StatefulSet
定義的 name,末尾的序號對應著 Pod 的序號,也就是三個 PVC 分別被三個 Pod 繫結。透過這樣一種方式,達到不同的 Pod 享有不同的 PVC;PVC 也會繫結相應的一個 PV, 來達到不同的 Pod 繫結不同 PV 的目的。
4、Pod 的版本控制
不同於 Deployment
使用 ReplicaSet
來管理版本和維持副本數,StatefulSet controller
直接管理下屬的 Pod ,而 Pod 中用一個 label 來標識版本
kubectl get pods -L controller-revision-hash
之前我們學到 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 版本,可以看到字尾是6b558f6465。這裡先記錄一下,接下來會做 Pod 升級,再來看一下 controller-revision-hash
會不會發生改變。
5、更新映象
kubectl set image statefulset nginx-web nginx=nginx:1.15.0
6、檢視新版本的狀態
kubectl get pods -L controller-revision-hash
kubectl get sts nginx-web -o json | jq '.status'
透過 get pod 命令查詢 Revision hash
,可以看到三個 Pod 後面的 controller-revision-hash
都已經升級到了新的 Revision hash
,字尾由6b558f6465 變成了 84d64959d6。透過這三個 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 已經升級到了所需要的版本。
7、如何檢視 Pod 是否複用了之前的網路標識
其實 headless Service
配置的 hostname
只是和 Pod name
掛鉤的,所以只要升級後的 Pod 名稱和舊的 Pod 名稱相同,那麼就可以沿用之前 Pod 使用的網路標識。
8、如何檢視 Pod 是否複用了之前的儲存盤
kubectl get pvc
關於儲存盤,可以檢視 PVC 的狀態,它們的建立時間一直沒有改變,還是第一次建立 Pod 時的時間,所以現在升級後的 Pod 使用的還是舊 Pod 中使用的 PVC。
kubectl get pods nginx-web-0 -o yaml
或者可以檢視其中的某一個 Pod,這個 Pod 裡面同樣有個宣告的 volumes,這個 persistentVolumeClaim
裡的名稱 www-storage-nginx-web-0
,對應著 PVC 列表中看到的序號為 0 的 PVC,之前是被舊的 Pod 所使用。升級過程中 Controller 刪除舊 Pod,並且建立了一個同名的新 Pod,新的 Pod 仍然複用了舊 Pod 所使用的 PVC。
透過這種方式來達到升級前後,網路儲存都能複用的目的。
四、架構設計
1、管理模式
StatefulSet
一般會建立三種型別的資源
1.1、ControllerRevision
透過這個資源,StatefulSet 可以很方便地管理不同版本的 template 模板。
kubectl get controllerrevision -l app=nginx
kubectl get pods -l app=nginx --show-labels
比如上面例子中的 container nginx
,在建立之初擁有的第一個 template 版本,會建立一個對應的 ControllerRevision
。而當修改了 image 版本之後,StatefulSet Controller
會建立一個新的 ControllerRevision
,可以理解為每一個 ControllerRevision
對應了每一個版本的 Template
,也對應了每一個版本的 ControllerRevision hash
。其實在 Pod label 中定義的 ControllerRevision hash
,就是 ControllerRevision
的名字。透過這個資源 StatefulSet Controller
來管理不同版本的 template
資源。
1.2、PVC
如果在 StatefulSet 中定義了 volumeClaimTemplates,StatefulSet 會在建立 Pod 之前,先根據這個模板建立 PVC,並把 PVC 加到 Pod volume 中。
如果使用者在 spec 的 pvc 模板中定義了 volumeClaimTemplates
,StatefulSet
在建立 Pod 之前,根據模板建立 PVC,並加到 Pod 對應的 volume 中。當然也可以在 spec 中不定義 pvc template
,那麼所建立出來的 Pod 就不會掛載單獨的一個 pv。
1.3、Pod
StatefulSet 按照順序建立、刪除、更新 Pod,每個 Pod 有唯一的序號。
2、OwnerReference
StatefulSet Controller
是 Owned 三個資源:ControllerRevision
、Pod
、PVC
。
這裡不同的地方在於,當前版本的 StatefulSet
只會在 ControllerRevision
和 Pod
中新增 OwnerReference
,而不會在 PVC 中新增 OwnerReference
。擁有 OwnerReference
的資源,在管理的這個資源進行刪除的預設情況下,會關聯級聯刪除下屬資源。因此預設情況下刪除 StatefulSet
之後,StatefulSet
建立的 ControllerRevision
和 Pod 都會被刪除,但是 PVC 因為沒有寫入 OwnerReference
,PVC 並不會被級聯刪除。
2.1、刪除 StatefulSet
kubectl delete sts nginx-web
kubectl get pods -l app=nginx
kubectl get pvc
kubectl get controllerrevision -l app=nginx
可以看到由 StatefulSet
建立的 ControllerRevision
、Pod
均被刪除,但是 PVC
未被刪除
3、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
達到了管理有狀態應用的能力。
4、擴容能力
假設 StatefulSet
初始配置 replicas 為 1,有一個 Pod0。那麼將 replicas
從 1 修改到 3 之後,其實我們是先建立 Pod1,預設情況是等待 Pod1 狀態 READY 之後,再建立 Pod2。
透過上圖可以看到每個 StatefulSet 下面的 Pod 都是從序號 0 開始建立的。因此一個 replicas
為 N 的 StatefulSet
,它建立出來的 Pod 序號為 [0,N),也就是當 N>0 的時候,序號為 0 到 N-1。
5、擴縮容管理策略
在 StatefulSet.spec
中,有一個欄位名為 podManagementPolicy
,可選策略為 OrderedReady
和 Parallel
,預設為前者
OrderedReady
:擴縮容按照 order 順序執行。擴容時,必須前面序號的 Pod 都 Ready 了才能擴下一個,縮容時按照倒序刪除
Parallel
:並行擴縮容,不需要等前面的 Pod Ready/刪除後再處理下一個
上面建立的例子,沒有在 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。
6、釋出模擬
假設這裡的 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 建立出來。
五、欄位分析
git clone git@github.com:kubernetes/kubernetes.git
1、spec 欄位解析
- Replica 主要是期望的數量;
- Selector 是事件選擇器,必須匹配
spec.template.metadata.labels
中定義的條件; - Template:Pod 模板,定義了所要建立的 Pod 的基礎資訊模板;
- VolumeClaimTemplates:PVC 模板列表,如果在 spec 中定義了這個,PVC 會先於 Pod 模板 Template 進行建立。在 PVC 建立完成後,把建立出來的 PVC name 作為一個 volume 注入到根據 Template 建立出來的 Pod 中。
- 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
是不能被刪除的。
2、升級策略欄位解析
可以看到 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 的數量。不是灰度新版本的數量
舉個例子:假設當前有個 replicas
為 10 的 StatefulSet
,當我們更新版本的時候,如果 Partition
是 8,並不是表示要把 8 個 Pod 更新為新版本,而是表示需要保留 8 個 Pod 為舊版本,只更新 2 個新版本作為灰度。當 Replica
為 10 的時候,配置 Partition 為 8 的時候,其實還是保留 [0,8) 這 8 個 Pod 為舊版本,只有 [8,10) 進入新版本。
總結一下,假設 replicas=N
,Partition=M (M<N)
,則最終舊版本 Pod 為 [0,M) ,新版本 Pod 為 [M,N)。透過這樣一個 Partition
的方式來達到灰度升級的目的,這是目前 Deployment
所不支援的。