這篇博客主要探討StatefulSet如何通過Headless Service維持Pod的拓撲狀態
一、StatefulSets
StatefulSet 是用來管理有狀態應用的工作負載 API 對象。
StatefulSet 用來管理 Deployment 和擴展一組 Pod,並且能爲這些 Pod 提供序號和唯一性保證。
和 Deployment 相同的是,StatefulSet 管理了基於相同容器定義的一組 Pod。但和 Deployment 不同的是,StatefulSet 爲它們的每個 Pod 維護了一個固定的 ID。這些 Pod 是基於相同的聲明來創建的,但是不能相互替換:無論怎麼調度,每個 Pod 都有一個永久不變的 ID。
StatefulSet 和其他控制器使用相同的工作模式。你在 StatefulSet 對象 中定義你期望的狀態,然後 StatefulSet 的 控制器 就會通過各種更新來達到那種你想要的狀態。
StatefulSets 對於需要滿足以下一個或多個需求的應用程序很有價值:
- 穩定的、唯一的網絡標識符。
- 穩定的、持久的存儲。
- 有序的、優雅的部署和縮放。
- 有序的、自動的滾動更新。
在上面,穩定意味着 Pod 調度或重調度的整個過程是有持久性的。如果應用程序不需要任何穩定的標識符或有序的部署、刪除或伸縮,則應該使用由一組無狀態的副本控制器提供的工作負載來部署應用程序,比如 Deployment 或者 ReplicaSet 可能更適用於您的無狀態應用部署需要。
給定 Pod 的存儲必須由 PersistentVolume 驅動 基於所請求的 storage class 來提供,或者由管理員預先提供。
刪除或者收縮 StatefulSet 並不會刪除它關聯的存儲卷。這樣做是爲了保證數據安全,它通常比自動清除 StatefulSet 所有相關的資源更有價值。
StatefulSet 當前需要 headless 服務 來負責 Pod 的網絡標識。您需要負責創建此服務。
當刪除 StatefulSets 時,StatefulSet 不提供任何終止 Pod 的保證。爲了實現 StatefulSet 中的 Pod 可以有序和優雅的終止,可以在刪除之前將 StatefulSet 縮放爲 0。
在默認 Pod 管理策略(OrderedReady) 時使用 滾動更新,可能進入需要 人工干預 才能修復的損壞狀態。
StatefulSet將應用狀態抽象成了兩種情況:
- 拓撲狀態:應用實例必須按照某種順序啓動。新創建的Pod必須和原來Pod的網絡標識一樣
- 存儲狀態:應用的多個實例分別綁定了不同存儲數據。
StatefulSet給所有的Pod進行了編號,編號規則是:$(statefulset名稱)-$(序號)
,從0開始。
Pod被刪除後重建,重建Pod的網絡標識也不會改變,Pod的拓撲狀態按照Pod的“名字+編號”的方式固定下來,並且爲每個Pod提供了一個固定且唯一的訪問入口,即Pod對應的DNS記錄,同時重建時保證每個pod掛載到原來的捲上。
二、StatefulSet通過Headless Service維持Pod的拓撲狀態
首先創建Headless service:
[root@server1 pv]# mkdir statefulset
[root@server1 pv]# cd statefulset/
[root@server1 statefulset]# vim service.yaml
[root@server1 statefulset]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
創建服務:
[root@server1 statefulset]# kubectl apply -f service.yaml
service/nginx created
[root@server1 statefulset]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
myservice ClusterIP 10.101.31.155 <none> 80/TCP 14d
nginx ClusterIP None <none> 80/TCP 13s
可以看出創建了一個名爲nginx的無頭服務(Headless service)。
創建使用StatefulSet控制器的pod:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web created
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 31m
web-0 1/1 Running 0 29s
web-1 1/1 Running 0 26s
可以看出創建了兩個名爲web-0
和web-1
的pod,service也會由兩個endpoint:
其中pod名稱中的0和1是pod的唯一標識。
當我們將replicas改爲1時,web-1將會被回收:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 1
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 33m
web-0 1/1 Running 0 2m12s
而當我們將replicas改爲0時,所有pod將會被回收:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 0
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 34m
之後再次重建pod:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 2
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m
web-0 0/1 ContainerCreating 0 2s
[root@server1 statefulset]# kubectl get pod
^[[3~NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m
web-0 1/1 Running 0 9s
web-1 1/1 Running 0 7s
[root@server1 statefulset]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m 10.244.1.81 server2 <none> <none>
web-0 1/1 Running 0 28s 10.244.2.72 server3 <none> <none>
web-1 1/1 Running 0 26s 10.244.1.83 server2 <none> <none>
可以看出重新創建時是先創建web-0再創建web-1,而刪除的時候是先刪除web-1,再刪除web-0.
但是需要注意的是雖然名稱標識沒有變,但是pod的ip發生了變化。
現在可以直接訪問服務:
也可以解析地址:
也可以使用nslookup web-1.nginx
的方式解析和訪問到:
PV和PVC的設計,使得StatefulSet對存儲狀態的管理成爲了可能,現在爲以上這些pod添加存儲:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: managed-nfs-storage #分配器名稱
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web created
[root@server1 statefulset]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 85m 10.244.1.81 server2 <none> <none>
test 1/1 Running 2 48m 10.244.2.73 server3 <none> <none>
web-0 1/1 Running 0 25s 10.244.1.84 server2 <none> <none>
web-1 1/1 Running 0 20s 10.244.2.74 server3 <none> <none>
StatefulSet還會爲每一個Pod分配並創建一個同樣編號的PVC。這樣,kubernetes就可以通過Persistent Volume機制爲這個PVC綁定對應的PV,從而保證每一個Pod都擁有一個獨立的Volume:
[root@server1 statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO managed-nfs-storage 2m30s
www-web-1 Bound pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO managed-nfs-storage 2m25s
[root@server1 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 2m28s
pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO Delete Bound default/www-web-0 managed-nfs-storage 2m33s
可以往這些存儲裏寫入測試頁面:
[root@server1 statefulset]# ls /nfs/
default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a
default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6
[root@server1 statefulset]#
[root@server1 statefulset]# echo web-0 > /nfs/default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a/index.html
[root@server1 statefulset]# echo web-1 > /nfs/default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6/index.html
測試解析和訪問:
[root@server1 statefulset]# kubectl attach test -it
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx
Address 1: 10.244.1.84 web-0.nginx.default.svc.cluster.local
Address 2: 10.244.2.74 10-244-2-74.myservice.default.svc.cluster.local
可以看出解析成功。
/ # curl nginx
web-1
/ # curl nginx
web-1
/ # curl nginx
web-0
/ # curl nginx
web-0
/ # curl nginx
web-1
/ # curl nginx
web-0
/ # curl web-1.nginx #訪問特定pod
web-1
/ # curl web-0.nginx
web-0
訪問可以成功負載。
接下來我們將replicas設置爲3,再次進行測試:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
修改:
replicas: 3
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 90m
test 1/1 Running 4 53m
web-0 1/1 Running 0 5m14s
web-1 1/1 Running 0 5m9s
web-2 1/1 Running 0 9s
service由三個endpoint:
對應的pv當然也會創建:
[root@server1 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 5m35s
pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO Delete Bound default/www-web-0 managed-nfs-storage 5m40s
pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274 100Mi RWO Delete Bound default/www-web-2 managed-nfs-storage 35s
寫入測試頁面並訪問:
[root@server1 statefulset]# ls /nfs/
default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a
default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6
default-www-web-2-pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274
[root@server1 statefulset]# echo web-2 > /nfs/default-www-web-2-pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274/index.html
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl attach test -it
Defaulting container name to test.
Use 'kubectl describe pod/test -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
/ # nslookup web-2.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-2.nginx
Address 1: 10.244.1.85 web-2.nginx.default.svc.cluster.local
/ # curl web-2.nginx
web-2
/ # curl web-1.nginx
web-1
/ # curl web-0.nginx
web-0
可以看出成功解析且訪問。
而當我們將replicas改爲0後,StatefulSet會將pod從2 ,1,0有序刪除。
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 0
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 94m
test 1/1 Running 5 57m
刪除後進行pod的重建:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 3
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
重鍵後測試訪問:
[root@server1 statefulset]# kubectl attach test -it
/ # curl web-0.nginx
web-0
/ # curl web-1.nginx
web-1
/ # curl web-2.nginx
web-2
依然可以成功訪問,這就驗證了StatefulSet 控制器的特性。
最後將replicas改爲0刪除所有pod。
以上的一系列操作都是通過更改部署文件實現的,當然也可以使用命令的方式:
- kubectl 彈縮
首先,想要彈縮的StatefulSet. 需先清楚是否能彈縮該應用.
$ kubectl get statefulsets <stateful-set-name>
- 改變StatefulSet副本數量:
$ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
- 如果StatefulSet開始由 kubectl apply 或 kubectl create --save-config
創建,更新StatefulSet manifests中的 .spec.replicas, 然後執行命令 kubectl apply:
$ kubectl apply -f <stateful-set-file-updated>
- 也可以通過命令 kubectl edit 編輯該字段:
$ kubectl edit statefulsets <stateful-set-name>
- 使用 kubectl patch:
$ kubectl patch statefulsets <stateful-set-name> -p '{"spec":{"replicas":<new-replicas>}}'