StatefulSet (有狀態(tài)集,縮寫為sts) 常用于部署有狀態(tài)的且需要有序啟動的應(yīng)用程序畜疾,比如在進(jìn)行 SpringCloud 項(xiàng)目容器化時不翩,Eureka 的部署是比較適合用 StatefulSet 部署方式的茶没,可以給每個 Eureka 實(shí)例創(chuàng)建一個唯一且固定的標(biāo)識符筒占,并且每個Eureka 實(shí)例無需配置多余的 Service,其余Spring Boot 應(yīng)用可以直接通過 Eureka 的 Headless Service 即可進(jìn)行注冊蛛株。
StatefulSet 基本概念
StatefulSet 主要用于管理有狀態(tài)應(yīng)用程序的工作負(fù)載 API 對象团赁。比如在生產(chǎn)環(huán)境中,可以部署 ElasticSearch 集群谨履、MongoDB 集群或者需要持久化的 RabbitMQ 集群欢摄、Redis 集群、Kafka 集群和 ZooKeeper 集群等笋粟。
和 Deployment 類似怀挠,一個 StatefulSet 也同樣管理者基于相同容器規(guī)范的 Pod析蝴。不同的是, StatefulSet 為每個 Pod 維護(hù)一個粘性標(biāo)識绿淋。這些 Pod 是根據(jù)相同的規(guī)范創(chuàng)建的闷畸,但是不可互換,每個 Pod 都有一個持久的標(biāo)識符吞滞,在重新調(diào)度時也會保留佑菩,一般格式為 StatefulSetName-Number。比如定義了一個名字是 Redis-Sentinel-0裁赠、Redis-Sentinel-1殿漠、Redis-Sentinel-2。而 StatefulSet 創(chuàng)建的 Pod 一般使用 Headless Service (無頭服務(wù))進(jìn)行通信佩捞,和普通的 Service 的區(qū)別在于 Headless Service 沒有 ClusterIP绞幌,它使用的是 Endpoint 進(jìn)行互相通信,Headless 一般的格式為:
statefulSetName-{0..N-1}.serivceName.namespace.svc.cluster.local
說明:
- serviceName:Headless Service 的名字一忱,創(chuàng)建 StatefulSet 時莲蜘,必須指定 Headless Service 名稱。
- 0..N-1 : Pod 所在的序號帘营,從 0 開始到 N-1
- statefulSetName:StatefulSet 的名字
- namespace:服務(wù)所在的命名空間
- .cluster.local : Cluster Domain (集群域)
假如公司某個項(xiàng)目需要再 Kubernetes 中部署一個主從模式的 Redis菇夸,此時使用 StatefulSet 部署就極為合適,因?yàn)?StatefulSet 啟動時仪吧,只有當(dāng)前一個容器完全啟動時,后一個容器才會被調(diào)度鞠眉,并且每個容器的標(biāo)識符是固定的薯鼠,那么就可以通過標(biāo)識符來斷定當(dāng)前 Pod 的角色。
比如用一個名為 redis-ms 的 StatefulSet 部署主從架構(gòu)的 Redis械蹋,第一個容器啟動時出皇,它的標(biāo)識符為 redis-ms-0,并且 Pod 內(nèi)主機(jī)名也為 redis-ms-0哗戈,此時就可以根據(jù)主機(jī)名來判斷郊艘,當(dāng)主機(jī)名為 redis-ms-0 的容器作為 Redis 的主節(jié)點(diǎn),其余從節(jié)點(diǎn)唯咬,那么 Slave 連接 Master 主機(jī)配置就可以使用不會更改的 Master 的 Headless Serivce纱注,此時 Redis 從節(jié)點(diǎn)(Slave)配置文件如下:
port 6379
slaveof redis-ms-0.redis-ms.public-service.svc.cluster.local 6379
tcp-backlog 511
timeout 0
tcp-keeplive 0
...
其中 redis-ms-0.redis-ms.public-service.svc.cluster.local 是 Redis Master 的 Headless Service,在同一命名空間下只需要寫 redis-ms-0.redis-ms 即可胆胰,后面的 public-service.svc.cluster.local 可以省略狞贱。
StatefulSet 注意事項(xiàng)
一般 StatefulSet 用于有以下一個或者多個需求的應(yīng)用程序:
- 需要穩(wěn)定的獨(dú)一無二的網(wǎng)絡(luò)標(biāo)識符
- 需要持久化數(shù)據(jù)
- 需要有序的、優(yōu)雅的部署和擴(kuò)展
- 需要有序的自動滾動更新
如果應(yīng)用程序不需要任何穩(wěn)定的標(biāo)識符或者有序的部署蜀涨、刪除或者擴(kuò)展瞎嬉,應(yīng)該使用無狀態(tài)的控制器部署應(yīng)用程序蝎毡,比如 Deployment 或者 ReplicaSet。
StatefulSet 是 Kubernetes 1.9 版本以前的 beta 資源氧枣,在1.5 版本之前的任何 Kubernetes 版本都沒有沐兵。
Pod 所用的存儲必須由 PersistenVolume Provisioner (持久化卷配置器)根據(jù)請求配置 StorageClass,或者由管理員預(yù)先配置便监,當(dāng)然也可以不配置存儲扎谎。
為了確保數(shù)據(jù)安全,刪除和縮放 StatefulSet 不會刪除與 StatefulSet 關(guān)聯(lián)的卷茬贵,可以手動選擇性地刪除 PVC 和PV簿透。
StatefulSet 目前使用 Headless Service (無頭服務(wù))負(fù)責(zé) Pod 的網(wǎng)絡(luò)身份和通信,需要提前創(chuàng)建此服務(wù)解藻。
刪除一個 StatefulSet 時老充,不保證對 Pod 的終止,要在 StatefulSet 中實(shí)現(xiàn) Pod 的有序和正常終止螟左,可以在刪除之前將 StatefulSet 的副本縮減為 0啡浊。
定義一個 StatefulSet 資源文件
定義一個簡單的 StatefulSet 的示例如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- port: 80
name: web
clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
其中:
- kind:Service 定義了一個名字為 Nginx 的 Headless Service,創(chuàng)建的 Service格式為 nginx-0.nginx.default.svc.cluster.local胶背,因?yàn)闆]有指定 Namespace (命名空間)巷嚣,所以默認(rèn)部署在 default。
- kind:StatefulSet 定義了一個名字為 web 的StatefulSet钳吟,replicas 表示部署 Pod 的副本數(shù)廷粒,本實(shí)例為2。
在 StatefulSet 中必須設(shè)置 Pod 選擇器(.spec.selector)用來匹配其標(biāo)簽(.spec.template.metadata.labels)红且。在1.8版本之前坝茎,如果未配置該字段(.spec.selector),將被設(shè)置為默認(rèn)值暇番。在1.8版本之后嗤放,如果為指定匹配Pod Selector,則會導(dǎo)致 StatefulSet 創(chuàng)建錯誤壁酬。
當(dāng) StatefulSet 控制器創(chuàng)建 Pod 時次酌,它會添加一個標(biāo)簽 statefulset.kubernetes.io/pod-name , 該標(biāo)簽的值為 Pod的名稱舆乔,用于匹配 Service岳服。
使用 kubectl apply
創(chuàng)建
kubectl apply -f statefulset.yaml
創(chuàng)建 busybox 驗(yàn)證
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: busybox
image: busybox:1.28.4
command:
- sleep
- "3600"
resources:
limits:
memory: "128Mi"
cpu: "500m"
restartPolicy: Always
$ kubectl exec -it busybox -- sh
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.1.0.198 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.1.0.199 web-1.nginx.default.svc.cluster.local
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox 1/1 Running 0 6m52s 10.1.0.202 docker-desktop <none> <none>
web-0 1/1 Running 0 14m 10.1.0.198 docker-desktop <none> <none>
web-1 1/1 Running 0 14m 10.1.0.199 docker-desktop <none> <none>
nslookup 命令的輸出結(jié)果中,我們可以看到希俩,在訪問 web-0.nginx 的時候派阱,最后解析
到的,正是 web-0 這個 Pod 的 IP 地址斜纪;而當(dāng)訪問 web-1.nginx 的時候贫母,解析到的則是
web-1 的 IP 地址文兑。
如果你把這兩個 StatefulSet 的 Pod 刪除掉
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
再查看這兩個Pod 的狀態(tài)變化
$ kubectl get pod -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 5s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 4s
當(dāng)我們刪除這兩個Pod 后,Kubernetes 會按照原來的編號的順序腺劣,創(chuàng)建出兩個新的Pod绿贞。依舊可以使用 web-0.nginx
和 web-1.nginx
訪問,StatefuleSet 保證了 Pod 網(wǎng)絡(luò)標(biāo)識的穩(wěn)定性
$ kubectl exec -it busybox -- sh
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.1.0.209 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.1.0.210 web-1.nginx.default.svc.cluster.local
$ kubectl get pod -l app=nginx -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 7m21s 10.1.0.209 docker-desktop <none> <none>
web-1 1/1 Running 0 7m16s 10.1.0.210 docker-desktop <none> <none>
擴(kuò)容縮容
$ kubectl scale --replicas=3 sts web
statefulset.apps/web scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 40m
web-1 1/1 Running 0 40m
web-2 1/1 Running 0 11m
$ kubectl scale --replicas=2 sts web
statefulset.apps/web scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 41m
web-1 1/1 Running 0 41m
StatefulSet 更新策略
更新策略:
rollingUpdate: 當(dāng)updateStrategy的值被設(shè)置為RollingUpdate時橘原,StatefulSet Controller會刪除并創(chuàng)建StatefulSet相關(guān)的每個Pod對象籍铁,其處理順序與StatefulSet終止Pod的順序一致,即從序號最大的Pod開始重建趾断,每次更新一個Pod拒名。
onDeleted:當(dāng)updateStrategy的值被設(shè)置為OnDelete時,StatefulSet Controller并不會自動更新StatefulSet中的Pod實(shí)例芋酌,而是需要用戶手動刪除這些Pod并觸發(fā)StatefulSet Controller創(chuàng)建新的Pod實(shí)例來彌補(bǔ)增显,因此這其實(shí)是一種手動升級模式。
Partitioned : updateStrategy也支持特殊的分區(qū)升級策略(Partitioned)脐帝,在這種模式下同云,用戶指定一個序號,StatefulSet中序號大于等于此序號的Pod實(shí)例會全部被升級堵腹,小于此序號的Pod實(shí)例則保留舊版本不變炸站,即使這些Pod被刪除、重建疚顷,也仍然保持原來的舊版本旱易。這種分區(qū)升級策略通常用于按計劃分步驟的系統(tǒng)升級過程中。
灰度發(fā)布
灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間腿堤,能夠平滑過渡的一種發(fā)布方式咒唆。
Partitioned 可以用于灰度發(fā)布
$ kubectl edit sts web
updateStrategy:
rollingUpdate:
partition: 2
type: RollingUpdate
#修改 yaml文件
image: nginx:1.23.1
$ kubectl apply -f nginx.yaml
$ kubectl get pod web-2 -oyaml | grep image
- image: nginx:1.23.1
imagePullPolicy: Always
image: nginx:1.23.1
imageID: docker-pullable://nginx@sha256:1761fb5661e4d77e107427d8012ad3a5955007d997e0f4a3d41acc9ff20467c7
$ kubectl get pod web-0 -oyaml | grep image
- image: nginx
imagePullPolicy: Always
image: nginx:latest
imageID: docker-pullable://nginx@sha256:1761fb5661e4d77e107427d8012ad3a5955007d997e0f4a3d41acc9ff20467c7
可以看出,序號大于等于2释液,都更新了,實(shí)現(xiàn)了灰度發(fā)布
級聯(lián)刪除和非級聯(lián)刪除
級聯(lián)刪除:刪除 StatefulSet 同時刪除 Pod
非級聯(lián)刪除:刪除 StatefulSet 不刪除 Pod
# 級聯(lián)刪除
$ kubectl delete sts web
statefulset.apps "web" deleted
# 非級聯(lián)刪除
$ kubectl delete sts web --cascade=false
warning: --cascade=false is deprecated (boolean value) and can be replaced with --cascade=orphan.
statefulset.apps "web" deleted
# 刪除 statefulset 后装处,再刪除pod误债,pod不會重新創(chuàng)建
$ kubectl delete pod web-0 web-1
pod "web-0" deleted
pod "web-1" deleted
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-2 1/1 Running 0 4m31s
web-3 1/1 Running 0 4m28s
web-4 1/1 Running 0 4m26s