在k8s中工作負載資源StatefulSet用於管理有狀態應用。

什麼是無狀態?

組成一個應用的pod是對等的,它們之前沒有關聯和依賴關係,不依賴外部儲存。

即我們上篇小作文中deployment建立的nginx pod ,他們是完全一樣的,任何一個pod 被移除後依然可以正常工作。由於不依賴外部儲存,它們可以被輕易的排程到任何 node 上。

什麼是有狀態?

顯然無狀態的反面就是有狀態了,pod之間可能包含主從、主備的相互依賴關係,甚至對啟動順序也有要求。更關鍵的是這些pod 需要外部儲存,一旦pod被清除或排程後,怎麼把pod 和原來的外部資料聯絡起來?這就是StatefulSet厲害的地方。

StatefulSet將這些狀態應用進行記錄,在需要的時候恢復。


StatefulSet如何展開這些工作?

一、維護應用拓撲狀態

通過dns記錄為 pod 分配叢集內唯一、穩定的網路標識。即只要保證pod 的名稱不變,pod被排程到任何節點或者ip如何變更都能被找到。

在 k8s 中Service用來來將一組 Pod 暴露給外界訪問的一種機制。當建立的service 中clusterIP為None 時(headless 無頭服務), 不會進行負載均衡,也不會為該服務分配叢集 IP。僅自動配置 DNS。

這樣我們叢集中的 一個pod 將被繫結到一條DNS記錄:

<pod-name>.<svc-name>.<namespace>.svc.cluster.local

通過解析這個地址就能找到pod的IP 。

下面我們建立一個headless service,將clusterIP配置為 None

#headless-service.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
ports:
- name: nginx-service-port
port: 80
targetPort: 9376
clusterIP: None
selector:
app: nginx

這個service將會繫結 app=nginx標籤的pod,我們通過kubectl apply -f headless-service.yml應用service 並通過get 檢視:

$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18d
nginx-headless ClusterIP None <none> 80/TCP 4h48m

nginx-headless 這個headless service建立成功了。接著我們建立一個StatefulSet:

#nginx-statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
serviceName: "nginx-headless"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-web
image: nginx:1.17
ports:
- containerPort: 82

nginx-statefulset 將會繫結我們前面的service nginx-headless並建立三個nginx pod。

我們檢視建立的pod ,StatefulSet 中的每個 Pod 根據 StatefulSet 的名稱和 Pod 的序號派生出它的主機名。同時statefulset創建出來的pod 名稱以$(StatefulSet name)-$(order)開始編號。

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 18s
nginx-statefulset-1 1/1 Running 0 15s
nginx-statefulset-2 1/1 Running 0 12s $ kubectl exec nginx-statefulset-0 -- sh -c hostname
nginx-statefulset-0

其實他們的建立順序也是從0-2,當我們刪除這些pod時,statefulset 馬上重建出相同名稱的Pod 。

我們通過statefulset 的event可以觀測到這個過程:

$ kubectl describe  nginx-statefulset
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 7m43s statefulset-controller create Pod nginx-statefulset-0 in StatefulSet nginx-statefulset successful
Normal SuccessfulCreate 7m40s statefulset-controller create Pod nginx-statefulset-1 in StatefulSet nginx-statefulset successful
Normal SuccessfulCreate 7m37s statefulset-controller create Pod nginx-statefulset-2 in StatefulSet nginx-statefulset successful

現在我們來看一下 pod 是否存在於 DNS 記錄中:

kubectl run -it --image busybox busybox --rm /bin/sh

執行一個一次性 pod busybox ,接著使用 ping 命令查詢之前提到的規則構建名稱nginx-statefulset-0.nginx-headless.default.svc.cluster.local

解析的IP與如下nginx-statefulset-0相符。

這樣我們使用pod名稱通過DNS就可以找到這個pod 再加上StatefulSet可以按順序創建出不變名稱的 pod ,即一個應用通過StatefulSet準確維護其拓撲狀態


二、維護應用儲存狀態

k8s為應對應用的資料儲存需求提供了卷的概念(volume)以及提供持久化儲存的PVC( PersistentVolumeClaim)PV( PersistentVolume)當一個pod 和 PVC繫結後,即使pod 被移除,PVC和PV仍然保留在叢集中,pod 再次被建立後會自動繫結到之前的PVC。他們看起來是這樣的:

這裡我們以討論statefulset持久化儲存為主,對於k8s儲存本身不瞭解的同學可以參考k8s官方文件儲存章節storage

首先我們建立儲存目錄 /data/volumes/ 以及一個本地的local型別(使用節點上的檔案或目錄來模擬網路附加儲存)的PV:

#pv-local.yml

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /data/volumes/
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- minikube

PV是叢集中的一塊儲存,它聲明瞭後端使用的真實儲存,通常會由K8S管理員建立。我們在pv-local中聲明瞭後端儲存型別為local掛載到目錄 /data/volumes/ , 儲存卷類名為local-storage,1Gb容量,訪問模式ReadWriteMany -- 卷可以被多個個節點以讀寫方式掛載。親和的節點為minikube

我們通過get來檢視這個PV:

$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWX Delete Available local-storage 25m

此時PV的狀態為available,還未與任何PVC繫結。我們通過建立PV使叢集得到了一塊儲存資源,但此時還不屬於你的應用,我們需要通過PVC去構建一個使用它的”通道“。

#app1-pvc.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app1-pvc
spec:
storageClassName: local-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi

現在我們開闢好一個5Gb容量的儲存通道(PVC),此時PV和PVC已通過 storageClassName自動形成繫結。這樣PV和PVC的status 皆為Bound

$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWX Delete Bound default/app-pvc local-storage 25m $ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
app-pvc Bound pv-local 5Gi RWX local-storage 27m

上面我們建立好通道,接下來要在我們statefuset中繫結這個通道,才能順利使用儲存。

# nginx-statefulset.yml

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
serviceName: "nginx-headless"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: minikube
volumes:
- name: app-storage
persistentVolumeClaim:
claimName: app-pvc containers:
- name: nginx-web
image: nginx:1.17
ports:
- containerPort: 80
name: nginx-port
volumeMounts:
- mountPath: /usr/share/nginx/html
name: app-storage

與之前的statefulset相比我們在pod 模板中添加了volume 已經 volumeMounts,這樣使用這個statefulset 所建立的pod都將掛載 我們前面定義的PVC app-pvc,應用nginx-statefulset.yml後我們進入到pod 檢驗一下目錄是否被正確掛載。

$ kubectl exec -it nginx-statefulset-0 -- /bin/bash

root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
hello pv

檢視本地目錄檔案:

root@minikube:/# cat /data/volumes/index.html
hello pv

接著我們在pod 中修改index.html內容為並將pod刪除,檢驗過載後的 pod 儲存資料是否能被找回。

root@nginx-statefulset-0:/# echo "pod data" > /usr/share/nginx/html/index.html

刪除帶有標籤app=nginx的pod ,由於statefulset的控制器使pod按順序被重建:

$ kubectl delete pod -l app=nginx
pod "nginx-statefulset-0" deleted
pod "nginx-statefulset-1" deleted
pod "nginx-statefulset-2" deleted $ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 9s
nginx-statefulset-1 1/1 Running 0 6s
nginx-statefulset-2 0/1 ContainerCreating 0 3s

毫無疑問,pod 資料完好無損:

$ kubectl exec -it nginx-statefulset-0 -- /bin/bash
root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
pod data

也就是說雖然我們的pod被刪除了,但是PV已經PV依然保留在叢集中,當pod 被重建後,它依然會去找定義的claimName: app-pvc這個PVC,接著掛載到容器中。

這裡我們一個PVC 綁定了多個節點,其實可以為每一個 statefulset中的pod 建立PVC,可以自行了解。

k8s儲存可操作性非常強,這裡只在statefulset下做了簡單的演示。後續我們會對k8s儲存做更深入的瞭解。


三、總結

這篇小作文我們一起學習了k8s中工作負載資源StatefulSet是如何管理有狀態應用的,主要從維護應用拓撲狀態和儲存狀態兩個方面做了簡單介紹。這樣我們對statefulset這個工作資源有了大體瞭解:StatefulSet 與 Deployment 相比,它為每個管理的 Pod 都進行了編號,使Pod有一個穩定的啟動順序,並且是叢集中唯一的網路標識。有了標識後使用PV、PVC對儲存狀態進行維護。


希望小作文對你有些許幫助,如果內容有誤請指正。

您可以隨意轉載、修改、釋出本文,無需經過本人同意。 通過部落格閱讀:iqsing.github.io