在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
