K8s之StatefulSet控制器使用實例
一、StatefulSet 是什么?
StatefulSet 是 Kubernetes 中專門用于管理有狀態(tài)應(yīng)用(Stateful Applications) 的工作負(fù)載控制器(Controller)。與 Deployment 等無狀態(tài)控制器不同,StatefulSet 為需要穩(wěn)定身份標(biāo)識、持久化存儲和有序生命周期管理的分布式系統(tǒng)提供了原生支持。
1.1 什么是有狀態(tài)應(yīng)用(Stateful)?
定義:應(yīng)用的實例具有獨特的身份和記憶,依賴本地持久化的數(shù)據(jù)或維護的上下文狀態(tài),不同實例處理相同輸入可能產(chǎn)生不同結(jié)果。
簡單理解:就像一個有記憶的人,記得過去發(fā)生的事情,并且這些記憶會影響他未來的行為。
核心特征:
- 身份唯一性:每個實例有固定 ID,集群中角色不同(主/從/分片)
- 數(shù)據(jù)持久化:本地存儲包含業(yè)務(wù)數(shù)據(jù)、配置狀態(tài)或會話信息
- 拓?fù)涿舾?/strong>:實例間存在固定的通信關(guān)系(如主從復(fù)制)
- 生命周期復(fù)雜:擴縮容需考慮數(shù)據(jù)重分布,故障恢復(fù)需保證數(shù)據(jù)一致性
典型例子:
- MySQL/PostgreSQL 數(shù)據(jù)庫(存儲業(yè)務(wù)數(shù)據(jù))
- Redis/Memcached 緩存(雖然數(shù)據(jù)可重建,但通常視為有狀態(tài))
- Kafka/RabbitMQ 消息隊列(消息持久化)
- ZooKeeper/etcd 協(xié)調(diào)服務(wù)(維護集群元數(shù)據(jù))
- Elasticsearch(索引數(shù)據(jù)分片)
1.2 什么是無狀態(tài)應(yīng)用(Stateless)?
定義:應(yīng)用的每個實例都是完全對等且可互換的,不依賴本地存儲的任何歷史信息或上下文,任何請求都可以由任意實例處理,結(jié)果完全相同。
簡單理解:就像一個"金魚記憶"的人,每3秒都忘記之前發(fā)生的事,每次都是全新的開始。
核心特征:
- 無本地依賴:不保存會話、不緩存用戶數(shù)據(jù)、不記錄處理歷史
- 即拋即用:實例可以隨時創(chuàng)建或銷毀,用戶無感知
- 水平擴展簡單:增加實例即可提升性能,無需數(shù)據(jù)同步
- 故障自愈快:實例故障后立即重建,無需數(shù)據(jù)恢復(fù)
典型例子:
- Nginx/Apache 反向代理(僅轉(zhuǎn)發(fā)請求)
- REST API 服務(wù)(每次請求攜帶完整認(rèn)證信息)
- 靜態(tài)資源服務(wù)器
- 函數(shù)計算(Serverless)
1.3 關(guān)鍵區(qū)別對比
| 維度 | 無狀態(tài)(Stateless) | 有狀態(tài)(Stateful) |
|---|---|---|
| 數(shù)據(jù)存儲 | 不保存數(shù)據(jù),或僅使用臨時緩存(emptyDir) | 必須持久化數(shù)據(jù)到磁盤(數(shù)據(jù)庫、日志、配置) |
| 實例身份 | 無身份,完全對等,隨機命名 | 有唯一固定身份(ID、hostname、角色) |
| 請求處理 | 任意實例可處理任意請求,結(jié)果一致 | 特定請求必須由特定實例處理(如分片查詢) |
| 擴縮容 | 秒級擴縮容,簡單增加/減少副本數(shù) | 需數(shù)據(jù)遷移、重新分片、集群拓?fù)渥兏?/td> |
| 故障影響 | 實例故障=服務(wù)短暫降級,重建即可 | 實例故障=可能數(shù)據(jù)丟失,需復(fù)雜恢復(fù)流程 |
| 網(wǎng)絡(luò)依賴 | 僅需入口負(fù)載均衡 | 需實例間直接通信(如復(fù)制、選舉、心跳) |
| K8s 控制器 | Deployment、ReplicaSet、DaemonSet | StatefulSet、Operator |
| 存儲需求 | 無需 PVC,或使用共享只讀存儲 | 必須獨立 PVC,ReadWriteOnce 模式 |
1.4 StatefulSet 的核心特征
- 穩(wěn)定的網(wǎng)絡(luò)標(biāo)識:為每個 Pod 分配唯一、有序的序號(如
web-0,web-1),并提供基于 DNS 的服務(wù)發(fā)現(xiàn) - 持久化存儲管理:通過
volumeClaimTemplates自動為每個 Pod 創(chuàng)建獨立的 PVC,實現(xiàn)存儲與 Pod 生命周期解耦 - 有序生命周期管理:嚴(yán)格保證 Pod 的創(chuàng)建、刪除、擴縮容和滾動更新的順序性
- 優(yōu)雅狀態(tài)管理:支持優(yōu)雅終止(Graceful Termination)和啟動后鉤子(Post-start),確保狀態(tài)一致性
二、StatefulSet 和 Deployment 的區(qū)別
| 對比維度 | Deployment(無狀態(tài)應(yīng)用) | StatefulSet(有狀態(tài)應(yīng)用) |
|---|---|---|
| Pod 名字 | 隨機生成,如 web-abc123、web-def456每次重建名字都變 | 固定序號,如 web-0、web-1、web-2重建后名字不變 |
| 網(wǎng)絡(luò)身份 | 只有 IP 會變,像個"流浪漢" 其他 Pod 很難找到它 | 固定的網(wǎng)絡(luò)標(biāo)識:pod-name.service-name其他 Pod 總能找到它 |
| 數(shù)據(jù)存儲 | 一般不存數(shù)據(jù),或都用共享存儲 Pod 掛了數(shù)據(jù)丟了無所謂 | 每人一個獨立存儲空間 Pod 掛了數(shù)據(jù)還在,重建后自動掛載 |
| 啟動順序 | 大家一起上,誰先啟動都行 像餐廳所有窗口同時開 | 排隊啟動:0號先上,準(zhǔn)備好后1號再上 像銀行柜臺一個一個開 |
| 關(guān)閉順序 | 隨便關(guān),關(guān)哪個都行 | 從后往前關(guān):最后一個先關(guān),依次往前 保證主節(jié)點最后退出 |
| 擴縮容 | 想擴就擴,想縮就縮 新 Pod 和老 Pod 沒區(qū)別 | 按序號擴縮:擴容從下一個序號開始 縮容從最大序號開始刪 |
| 更新方式 | 可以一次性全更新 或者滾動更新,順序無所謂 | 從后往前更新 支持暫停在某個版本(partition) |
| 適用場景 | Web服務(wù)、API網(wǎng)關(guān) 前端應(yīng)用、計算任務(wù) | 數(shù)據(jù)庫(MySQL、Redis) 消息隊列(Kafka、ZooKeeper) |
2.1 詳解:Pod 身份與網(wǎng)絡(luò)標(biāo)識
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 命名規(guī)則 | 隨機哈希后綴nginx-7564c8f6b4-9xv5r | 固定有序序號web-0, web-1, web-2 |
| Hostname 穩(wěn)定性 | 每次重建都變化 | 永久固定,與序號綁定 |
| DNS 解析 | 通過 Service 解析到隨機 Pod IP | 支持直接 Pod DNS 解析:<pod-name>.<service-name>.<namespace>.svc.cluster.local |
| Headless Service | 可選,通常不需要 | 必須,用于提供穩(wěn)定網(wǎng)絡(luò)標(biāo)識 |
| 網(wǎng)絡(luò)身份示例 | 無固定身份 | mysql-0.mysql.default.svc.cluster.local 始終指向 mysql-0 |
關(guān)鍵差異說明:
- Deployment:Service 通過
ClusterIP做負(fù)載均衡,請求隨機分發(fā)到后端 Pod,Pod 重建后 IP 和名稱都變 - StatefulSet:必須配合
clusterIP: None的 Headless Service,DNS 直接解析到 Pod IP,且 Pod 重建后 DNS 記錄保持不變
2.2 詳解:存儲與數(shù)據(jù)管理
| 特性 | Deployment | StatefulSet |
|---|---|---|
| 存儲定義方式 | 在 Pod 模板中直接定義 volumes 或引用現(xiàn)有 PVC | 使用 volumeClaimTemplates 動態(tài)為每個 Pod 創(chuàng)建獨立 PVC |
| PVC 與 Pod 關(guān)系 | 多個 Pod 可共享同一個 PVC(需支持多掛載) 或每個 Pod 手動掛載相同 PVC | 每個 Pod 獨占一個 PVC,一對一綁定,命名規(guī)則:<pvc-name>-<statefulset-name>-<ordinal> |
| 數(shù)據(jù)持久性 | Pod 刪除,數(shù)據(jù)可能丟失(取決于卷類型) | Pod 刪除,PVC 保留,數(shù)據(jù)持久化;新 Pod 自動掛載原 PVC |
| 存儲擴容 | 需手動修改 PVC | 需手動修改 PVC,或依賴 StorageClass 支持在線擴容 |
| 數(shù)據(jù)隔離性 | 低(共享存儲)或手動管理 | 高(自動隔離,每個 Pod 獨立存儲) |
存儲綁定示例:
# StatefulSet 會自動創(chuàng)建: # PVC: disk-ssd-web-0 → PV: pv-001 (綁定) # PVC: disk-ssd-web-1 → PV: pv-002 (綁定) # 即使 web-0 被刪除重建,disk-ssd-web-0 仍會重新掛載到原 PV
2.3 詳解:部署與擴縮容行為
| 操作 | Deployment | StatefulSet |
|---|---|---|
| 創(chuàng)建順序 | 并行創(chuàng)建,所有 Pod 同時啟動 | 串行創(chuàng)建,按序號 0→1→2→…,前一個 Ready 后才創(chuàng)建下一個 |
| 擴容行為 | 并行擴容,新 Pod 立即創(chuàng)建 | 按序號遞增順序創(chuàng)建,保證集群拓?fù)渲鸩綌U展 |
| 縮容行為 | 隨機刪除 Pod | 按序號遞減刪除(先刪 N-1,再刪 N-2…),保證有序縮減 |
| 滾動更新 | 隨機替換,可設(shè)置 maxSurge/maxUnavailable | 逆序替換(先更新 N-1,最后更新 0),可設(shè)置 partition 進行灰度發(fā)布 |
| 更新策略 | RollingUpdate(默認(rèn))、Recreate | RollingUpdate(默認(rèn))、OnDelete(手動刪除后重建) |
有序性意義:
- 主從架構(gòu):先啟動主節(jié)點(0),再啟動從節(jié)點(1,2…),避免從節(jié)點找不到主而崩潰
- 數(shù)據(jù)安全:縮容時先移除高序號節(jié)點,避免破壞集群的法定人數(shù)(Quorum)
- 灰度發(fā)布:通過
partition控制只更新部分節(jié)點,如partition: 2表示只更新序號 ≥2 的 Pod
2.4 運維與故障處理對比
| 場景 | Deployment | StatefulSet |
|---|---|---|
| Pod 故障重建 | 立即創(chuàng)建新 Pod,隨機命名,無狀態(tài)恢復(fù) | 按原序號重建,掛載原 PVC,自動恢復(fù)數(shù)據(jù)和身份 |
| 節(jié)點遷移 | 快速調(diào)度到新節(jié)點 | 需等待原 PVC 在目標(biāo)節(jié)點可用(依賴存儲拓?fù)洌?/td> |
| 版本回滾 | kubectl rollout undo 快速回滾 | 需手動控制,逆序回滾,需關(guān)注數(shù)據(jù)兼容性 |
| 數(shù)據(jù)備份 | 通常無需備份 Pod 級數(shù)據(jù) | 必須制定備份策略,PVC 獨立存在需單獨管理 |
| 監(jiān)控重點 | 整體吞吐量、錯誤率 | 單節(jié)點狀態(tài)、存儲容量、復(fù)制延遲、集群拓?fù)渫暾?/td> |
三、StatefulSet 的使用 - 實例
部署一個Nginx StatefulSet
- 先創(chuàng)建一個nginx的namespace
kubectl create ns nginx
3.1 檢查是否有 storageclass 資源
kubectl get storageclass -A
如果有資源(任何都行,只要不提示No resources found就行),在 nginx-StatefulSet.yaml中的storageClassName請?zhí)顚懗捎械?,如果沒有,可按照如下添加storageclass;
可地址直接安裝:kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
如果地址拉取不到可使用如下:
vi local-path-storage.yaml
apiVersion: v1
kind: Namespace
metadata:
name: local-path-storage
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: local-path-provisioner-role
namespace: local-path-storage
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: local-path-provisioner-role
rules:
- apiGroups: [""]
resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: local-path-provisioner-bind
namespace: local-path-storage
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: local-path-provisioner-bind
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: local-path-provisioner
namespace: local-path-storage
spec:
replicas: 1
selector:
matchLabels:
app: local-path-provisioner
template:
metadata:
labels:
app: local-path-provisioner
spec:
serviceAccountName: local-path-provisioner-service-account
containers:
- name: local-path-provisioner
image: rancher/local-path-provisioner:v0.0.35
imagePullPolicy: IfNotPresent
command:
- local-path-provisioner
- --debug
- start
- --config
- /etc/config/config.json
volumeMounts:
- name: config-volume
mountPath: /etc/config/
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONFIG_MOUNT_PATH
value: /etc/config/
volumes:
- name: config-volume
configMap:
name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}
setup: |-
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
teardown: |-
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
helperPod.yaml: |-
apiVersion: v1
kind: Pod
metadata:
name: helper-pod
spec:
priorityClassName: system-node-critical
tolerations:
- key: node.kubernetes.io/disk-pressure
operator: Exists
effect: NoSchedule
containers:
- name: helper-pod
image: busybox
imagePullPolicy: IfNotPresent
在進行創(chuàng)建
kubectl apply -f local-path-storage.yaml
查看是否創(chuàng)建成功
kubectl get storageclass
看到如下內(nèi)容即可

3.2 創(chuàng)建Headless Service
Headless Service(clusterIP: None)會為每個 Pod 創(chuàng)建獨立的 DNS 記錄:
vi nginx-Headless.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: nginx
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
clusterIP: None
sessionAffinity: None
ports:
- name: web
port: 80
protocol: TCP3.3 創(chuàng)建StatefulSet
vi nginx-StatefulSet.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
namespace: nginx
spec:
serviceName: nginx
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
initContainers:
- name: init-html
image: busybox
command:
- sh
- -c
- |
# 檢查是否已初始化
if [ ! -f /mnt/html/.initialized ]; then
echo "First boot - creating initial content"
echo "Welcome to nginx - $(hostname) - $(date)" > /mnt/html/index.html
echo "This is a test page" >> /mnt/html/index.html
touch /mnt/html/.initialized # 創(chuàng)建標(biāo)記文件
else
echo "Already initialized, keeping existing content"
fi
volumeMounts:
- name: www
mountPath: /mnt/html
containers:
- name: nginx
image: nginx:1.24
imagePullPolicy: IfNotPresent
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
livenessProbe:
initialDelaySeconds: 10
periodSeconds: 10
tcpSocket:
port: 80
readinessProbe:
initialDelaySeconds: 10
periodSeconds: 10
httpGet:
path: /
port: 80
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "local-path" # 根據(jù)storageclass集群修改
resources:
requests:
storage: 1Gi3.4 使用StatefulSet部署nginx服務(wù)
- 部署nginx Headless Service
kubectl apply -f nginx-Headless.yaml
- 查看是否創(chuàng)建成功
kubectl get svc -n nginx

- 部署nginx StatefulSet
kubectl apply -f nginx-StatefulSet.yaml
- 查看是否創(chuàng)建成功
# 查看nginx pvc kubectl get pvc -n nginx -w # 查看nginx pod kubectl get pod -n nginx -w # 查看nginx StatefulSet kubectl get sts -n nginx -o wide

3.5 驗證StatefulSet特性
創(chuàng)建完成后,可以觀察到以下現(xiàn)象:
- Pod有序創(chuàng)建:
[root@k8s-master StatefulSet]# kubectl get pods -n nginx -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-0 1/1 Running 0 3m37s 192.168.36.94 k8s-node1 <none> <none> nginx-1 1/1 Running 0 3m19s 192.168.36.99 k8s-node1 <none> <none> nginx-2 1/1 Running 0 3m 192.168.169.143 k8s-node2 <none> <none>
- 穩(wěn)定的網(wǎng)絡(luò)標(biāo)識:
[root@k8s-master StatefulSet]# kubectl exec -n nginx nginx-0 -- hostname nginx-0 # 通過DNS解析訪問(這里需要注意,指定nginx部署的ns,要不然會報無法解析) [root@k8s-master StatefulSet]# kubectl run -it --rm busybox --image=busybox:1.28 --restart=Never -n nginx -- nslookup nginx-0.nginx 2>/dev/null || echo "測試失敗" Server: 10.0.0.2 Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local Name: nginx-0.nginx Address 1: 192.168.36.94 nginx-0.nginx.nginx.svc.cluster.local
- 獨立的持久化存儲:
# 查看自動創(chuàng)建的PVC [root@k8s-master StatefulSet]# kubectl get pvc -n nginx NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-nginx-0 Bound pvc-67c250d5-3e78-4c69-a4ad-2bfacf84c54b 1Gi RWO local-path 5d www-nginx-1 Bound pvc-13f90069-60b5-4c93-86c3-76e1377b5098 1Gi RWO local-path 5d www-nginx-2 Bound pvc-2ba2e9e5-5794-4b6e-af5b-b940ccb414e1 1Gi RWO local-path 5d
- Pod重建后保持?jǐn)?shù)據(jù):
# 查看nginx-0 默認(rèn)的index.html數(shù)據(jù) kubectl exec -n nginx nginx-0 -- bash -c "cat /usr/share/nginx/html/index.html" # 在nginx-0中寫入數(shù)據(jù) kubectl exec -n nginx nginx-0 -- bash -c 'echo "Hello Nginx, Im's nginx-0 pod !!! " > /usr/share/nginx/html/index.html' # 查看nginx-0 修改后的index.html數(shù)據(jù) kubectl exec -n nginx nginx-0 -- bash -c "cat /usr/share/nginx/html/index.html" # 刪除nginx-0 kubectl delete pods -n nginx nginx-0 # StatefulSet會自動重建,查看nginx-o pod是否重建 kubectl get pods -n nginx -w # 重建成功再次查看nginx-0 pod容器里index.html里的內(nèi)容 kubectl exec -n nginx nginx-0 -- bash -c "cat /usr/share/nginx/html/index.html"

注意:如果數(shù)據(jù)被覆蓋為最初的數(shù)據(jù)了,可能是因為創(chuàng)建pod的時候會直接寫入默認(rèn)的配置,導(dǎo)致把后來修改的數(shù)據(jù)重寫了。
這里我在創(chuàng)建nginx-StatefulSet的時候是先手動添加數(shù)據(jù)了,因為默認(rèn)沒有index.html,會導(dǎo)致啟動失敗,所以需要先寫入數(shù)據(jù)才可以;并且加了個判斷,如果存在這個文件那么就不會寫入數(shù)據(jù),這樣就不會在重建pod的時候數(shù)據(jù)重寫了。
四、StatefulSet的更新策略
StatefulSet支持兩種更新策略,通過spec.updateStrategy.type 字段控制:
| 策略類型 | 行為 | 適用場景 |
|---|---|---|
| RollingUpdate (默認(rèn)) | 按順序從后向前滾動更新 Pod | ? 大多數(shù)場景 |
| OnDelete | 手動刪除 Pod 后才更新 | ?? 需要完全控制更新時機的場景 |
4.1 RollingUpdate 策略(默認(rèn))
4.1.1 基本更新寫法
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: RollingUpdate # 默認(rèn)值,可不寫
# ... 其他配置更新順序:
- 從序號最大的 Pod 開始更新(從后向前)
- 依次向前推進(nginx-2 → nginx-1 → nginx-0)
- 每個 Pod 更新成功并進入 Ready 狀態(tài)后,才更新下一個
為什么從后往前?
- 保證主節(jié)點(通常是序號0)最后更新
- 對于主從架構(gòu)的應(yīng)用,先更新從節(jié)點,最后更新主節(jié)點
- 減少對服務(wù)的影響
4.1.2 分區(qū)更新(Partition)-重要特性
RollingUpdate 支持 partition 參數(shù),可以控制更新的范圍:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新序號 >= 2 的 Pod
# ... 其他配置分區(qū)規(guī)則:
- 序號 >= partition 的 Pod 會被更新
- 序號 < partition 的 Pod 保持舊版本
4.1.3 分區(qū)更新實例:
假設(shè)有 3 個副本:nginx-0, nginx-1, nginx-2
| partition 值 | 哪些 Pod 被更新 | 哪些 Pod 保持不變 |
|---|---|---|
partition: 0 | nginx-2, nginx-1, nginx-0 (全部) | 無 |
partition: 1 | nginx-2, nginx-1 | nginx-0 |
partition: 2 | nginx-2 | nginx-0, nginx-1 |
partition: 3 | 無 | nginx-0, nginx-1, nginx-2 (全部) |
- 場景1:金絲雀發(fā)布(Canary Release)
# 第一步:只更新最后一個 Pod 做測試
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新 nginx-2測試 nginx-2 沒問題后,更新其他所有的pod:
# 第二步:更新所有 Pod
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # 更新全部- 場景2:維護主節(jié)點不更新
# 主節(jié)點是 nginx-0,保持不更新
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 1 # 只更新 nginx-1, nginx-2- 場景3:分批發(fā)布
# 第1批:更新 30%(最后一個)更新完需測試 partition: 2 # 第2批:更新 60%(后兩個)更新完需測試 partition: 1 # 第3批:更新 100% 更新完需測試 partition: 0
4.2 OnDelete 策略(手動觸發(fā))
- OnDelete 策略寫法
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: OnDelete # 手動模式
# ... 其他配置- 行為特點
- K8s 不會自動更新任何 Pod
- 只有當(dāng)你手動刪除 Pod 時,重建的 Pod 才會使用新配置
- 你可以控制每個 Pod 的更新時機
- 適用場景
- 數(shù)據(jù)庫版本升級:需要逐個節(jié)點手動操作,確保數(shù)據(jù)同步
- 核心業(yè)務(wù):需要在業(yè)務(wù)低峰期逐個更新
- 需要手動驗證:每個節(jié)點更新后都要手動驗證
- 配合外部工具:與 Ansible、Jenkins 等工具集成
- 實際操作示例
# 1. 修改 StatefulSet 鏡像版本(但不會立即生效)
kubectl patch sts -n nginx nginx -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.25"}]}}}}'
# 2. 手動刪除要更新的 Pod
kubectl delete pod -n nginx nginx-2 # 先更新最后一個
## 更新完成查看版本號,確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
# 3. 驗證 nginx-2 沒問題后
kubectl delete pod -n nginx nginx-1 # 更新中間
## 更新完成查看版本號,確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
# 4. 最后更新主節(jié)點
kubectl delete pod -n nginx nginx-0
## 更新完成查看版本號,確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
- 控制器不會自動更新Pod
- 需要手動刪除Pod才能觸發(fā)重建更新
- 適用于需要完全控制更新時機的場景,如核心應(yīng)用的流量無損升級
4.3 更新策略對比
| 特性 | RollingUpdate | OnDelete |
|---|---|---|
| 自動更新 | ? 是 | ? 否 |
| 更新順序 | 從后往前 | 由你控制 |
| 分區(qū)控制 | ? 支持 (partition) | ? 不支持 |
| 回滾方式 | 重新設(shè)置 image 或 partition | 手動刪除重建 |
| 適用場景 | Web服務(wù)、緩存、一般應(yīng)用 | 數(shù)據(jù)庫、核心應(yīng)用、特殊需求 |
4.4 更新策略選擇建議
選擇 RollingUpdate 當(dāng):
? 應(yīng)用無狀態(tài)或能優(yōu)雅處理滾動重啟
? 想要自動化更新
? 需要金絲雀發(fā)布(配合 partition)
? 更新風(fēng)險較低選擇 OnDelete 當(dāng):
? 數(shù)據(jù)庫等有狀態(tài)核心應(yīng)用
? 需要手動干預(yù)每個節(jié)點的更新
? 更新風(fēng)險高,需要逐個驗證
? 有外部編排工具(Ansible、Operator)
五、StatefulSet vs. Deployment:如何選擇?
| 特性 | StatefulSet | Deployment |
|---|---|---|
| Pod命名 | 固定有序名稱(xxx-0, xxx-1) | 隨機名稱(xxx-隨機字符串) |
| 網(wǎng)絡(luò)標(biāo)識 | 穩(wěn)定,Pod重建不變 | 變化,每次重建新IP |
| 存儲 | 支持PVC模板,持久化存儲 | 通常使用共享存儲或臨時存儲 |
| 順序控制 | 有序部署、更新、刪除 | 并行操作 |
| 適用場景 | 數(shù)據(jù)庫、消息隊列、有狀態(tài)服務(wù) | Web服務(wù)、API網(wǎng)關(guān)、無狀態(tài)應(yīng)用 |
選擇建議:
- 應(yīng)用需要穩(wěn)定的網(wǎng)絡(luò)標(biāo)識 → 選擇StatefulSet
- 應(yīng)用需要獨立的持久化存儲 → 選擇StatefulSet
- 應(yīng)用要求有序的部署和更新 → 選擇StatefulSet
- 否則,優(yōu)先考慮Deployment或ReplicaSet
六、注意事項
數(shù)據(jù)安全:StatefulSet刪除時不會自動刪除PVC,需手動清理不再需要的存儲卷,避免資源浪費
Headless Service必須:StatefulSet必須關(guān)聯(lián)Headless Service才能保證網(wǎng)絡(luò)標(biāo)識的穩(wěn)定性
存儲類選擇:根據(jù)性能需求選擇合適的StorageClass,生產(chǎn)環(huán)境推薦使用SSD或高性能云盤
備份策略:雖然StatefulSet保證了Pod重建后的數(shù)據(jù)持久性,但仍需建立定期備份機制,防止數(shù)據(jù)損壞或誤刪除
縮容謹(jǐn)慎:縮容StatefulSet會導(dǎo)致Pod被刪除,但PVC保留。如果希望徹底清理,需要手動刪除對應(yīng)的PVC
七、總結(jié)
StatefulSet是Kubernetes中管理有狀態(tài)應(yīng)用的核心控制器,通過穩(wěn)定的網(wǎng)絡(luò)標(biāo)識、獨立的持久化存儲和有序的生命周期管理,為數(shù)據(jù)庫、消息隊列等有狀態(tài)應(yīng)用提供了完善的運行環(huán)境。掌握StatefulSet的使用,意味著你能夠?qū)⒏嗟膫鹘y(tǒng)應(yīng)用平滑地遷移到Kubernetes平臺,充分發(fā)揮容器編排的優(yōu)勢。
在實際應(yīng)用中,需要根據(jù)業(yè)務(wù)需求合理選擇更新策略和Pod管理策略,同時注意數(shù)據(jù)安全和備份,才能構(gòu)建出穩(wěn)定可靠的有狀態(tài)應(yīng)用系統(tǒng)。
到此這篇關(guān)于K8s之StatefulSet控制器的文章就介紹到這了,更多相關(guān)K8s之StatefulSet控制器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
云服務(wù)器Jenkins部署Springboot項目及Vue項目的詳細(xì)過程
本文詳細(xì)介紹了如何在云服務(wù)器上使用Jenkins部署Springboot和Vue項目,包括創(chuàng)建Springboot項目并上傳到Git倉庫、安裝Maven和配置Maven插件、安裝Gitee插件、配置Jenkins任務(wù)以及創(chuàng)建自由風(fēng)格項目等步驟,感興趣的朋友一起看看吧2025-02-02
Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實踐
這篇文章主要為大家介紹了Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04
教你在k8s上部署HADOOP-3.2.2(HDFS)的方法
這篇文章主要介紹了k8s-部署HADOOP-3.2.2(HDFS)的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04

