K8s之StatefulSet控制器使用實(shí)例
一、StatefulSet 是什么?
StatefulSet 是 Kubernetes 中專門用于管理有狀態(tài)應(yīng)用(Stateful Applications) 的工作負(fù)載控制器(Controller)。與 Deployment 等無(wú)狀態(tài)控制器不同,StatefulSet 為需要穩(wěn)定身份標(biāo)識(shí)、持久化存儲(chǔ)和有序生命周期管理的分布式系統(tǒng)提供了原生支持。
1.1 什么是有狀態(tài)應(yīng)用(Stateful)?
定義:應(yīng)用的實(shí)例具有獨(dú)特的身份和記憶,依賴本地持久化的數(shù)據(jù)或維護(hù)的上下文狀態(tài),不同實(shí)例處理相同輸入可能產(chǎn)生不同結(jié)果。
簡(jiǎn)單理解:就像一個(gè)有記憶的人,記得過(guò)去發(fā)生的事情,并且這些記憶會(huì)影響他未來(lái)的行為。
核心特征:
- 身份唯一性:每個(gè)實(shí)例有固定 ID,集群中角色不同(主/從/分片)
- 數(shù)據(jù)持久化:本地存儲(chǔ)包含業(yè)務(wù)數(shù)據(jù)、配置狀態(tài)或會(huì)話信息
- 拓?fù)涿舾?/strong>:實(shí)例間存在固定的通信關(guān)系(如主從復(fù)制)
- 生命周期復(fù)雜:擴(kuò)縮容需考慮數(shù)據(jù)重分布,故障恢復(fù)需保證數(shù)據(jù)一致性
典型例子:
- MySQL/PostgreSQL 數(shù)據(jù)庫(kù)(存儲(chǔ)業(yè)務(wù)數(shù)據(jù))
- Redis/Memcached 緩存(雖然數(shù)據(jù)可重建,但通常視為有狀態(tài))
- Kafka/RabbitMQ 消息隊(duì)列(消息持久化)
- ZooKeeper/etcd 協(xié)調(diào)服務(wù)(維護(hù)集群元數(shù)據(jù))
- Elasticsearch(索引數(shù)據(jù)分片)
1.2 什么是無(wú)狀態(tài)應(yīng)用(Stateless)?
定義:應(yīng)用的每個(gè)實(shí)例都是完全對(duì)等且可互換的,不依賴本地存儲(chǔ)的任何歷史信息或上下文,任何請(qǐng)求都可以由任意實(shí)例處理,結(jié)果完全相同。
簡(jiǎn)單理解:就像一個(gè)"金魚記憶"的人,每3秒都忘記之前發(fā)生的事,每次都是全新的開始。
核心特征:
- 無(wú)本地依賴:不保存會(huì)話、不緩存用戶數(shù)據(jù)、不記錄處理歷史
- 即拋即用:實(shí)例可以隨時(shí)創(chuàng)建或銷毀,用戶無(wú)感知
- 水平擴(kuò)展簡(jiǎn)單:增加實(shí)例即可提升性能,無(wú)需數(shù)據(jù)同步
- 故障自愈快:實(shí)例故障后立即重建,無(wú)需數(shù)據(jù)恢復(fù)
典型例子:
- Nginx/Apache 反向代理(僅轉(zhuǎn)發(fā)請(qǐng)求)
- REST API 服務(wù)(每次請(qǐng)求攜帶完整認(rèn)證信息)
- 靜態(tài)資源服務(wù)器
- 函數(shù)計(jì)算(Serverless)
1.3 關(guān)鍵區(qū)別對(duì)比
| 維度 | 無(wú)狀態(tài)(Stateless) | 有狀態(tài)(Stateful) |
|---|---|---|
| 數(shù)據(jù)存儲(chǔ) | 不保存數(shù)據(jù),或僅使用臨時(shí)緩存(emptyDir) | 必須持久化數(shù)據(jù)到磁盤(數(shù)據(jù)庫(kù)、日志、配置) |
| 實(shí)例身份 | 無(wú)身份,完全對(duì)等,隨機(jī)命名 | 有唯一固定身份(ID、hostname、角色) |
| 請(qǐng)求處理 | 任意實(shí)例可處理任意請(qǐng)求,結(jié)果一致 | 特定請(qǐng)求必須由特定實(shí)例處理(如分片查詢) |
| 擴(kuò)縮容 | 秒級(jí)擴(kuò)縮容,簡(jiǎn)單增加/減少副本數(shù) | 需數(shù)據(jù)遷移、重新分片、集群拓?fù)渥兏?/td> |
| 故障影響 | 實(shí)例故障=服務(wù)短暫降級(jí),重建即可 | 實(shí)例故障=可能數(shù)據(jù)丟失,需復(fù)雜恢復(fù)流程 |
| 網(wǎng)絡(luò)依賴 | 僅需入口負(fù)載均衡 | 需實(shí)例間直接通信(如復(fù)制、選舉、心跳) |
| K8s 控制器 | Deployment、ReplicaSet、DaemonSet | StatefulSet、Operator |
| 存儲(chǔ)需求 | 無(wú)需 PVC,或使用共享只讀存儲(chǔ) | 必須獨(dú)立 PVC,ReadWriteOnce 模式 |
1.4 StatefulSet 的核心特征
- 穩(wěn)定的網(wǎng)絡(luò)標(biāo)識(shí):為每個(gè) Pod 分配唯一、有序的序號(hào)(如
web-0,web-1),并提供基于 DNS 的服務(wù)發(fā)現(xiàn) - 持久化存儲(chǔ)管理:通過(guò)
volumeClaimTemplates自動(dòng)為每個(gè) Pod 創(chuàng)建獨(dú)立的 PVC,實(shí)現(xiàn)存儲(chǔ)與 Pod 生命周期解耦 - 有序生命周期管理:嚴(yán)格保證 Pod 的創(chuàng)建、刪除、擴(kuò)縮容和滾動(dòng)更新的順序性
- 優(yōu)雅狀態(tài)管理:支持優(yōu)雅終止(Graceful Termination)和啟動(dòng)后鉤子(Post-start),確保狀態(tài)一致性
二、StatefulSet 和 Deployment 的區(qū)別
| 對(duì)比維度 | Deployment(無(wú)狀態(tài)應(yīng)用) | StatefulSet(有狀態(tài)應(yīng)用) |
|---|---|---|
| Pod 名字 | 隨機(jī)生成,如 web-abc123、web-def456每次重建名字都變 | 固定序號(hào),如 web-0、web-1、web-2重建后名字不變 |
| 網(wǎng)絡(luò)身份 | 只有 IP 會(huì)變,像個(gè)"流浪漢" 其他 Pod 很難找到它 | 固定的網(wǎng)絡(luò)標(biāo)識(shí):pod-name.service-name其他 Pod 總能找到它 |
| 數(shù)據(jù)存儲(chǔ) | 一般不存數(shù)據(jù),或都用共享存儲(chǔ) Pod 掛了數(shù)據(jù)丟了無(wú)所謂 | 每人一個(gè)獨(dú)立存儲(chǔ)空間 Pod 掛了數(shù)據(jù)還在,重建后自動(dòng)掛載 |
| 啟動(dòng)順序 | 大家一起上,誰(shuí)先啟動(dòng)都行 像餐廳所有窗口同時(shí)開 | 排隊(duì)啟動(dòng):0號(hào)先上,準(zhǔn)備好后1號(hào)再上 像銀行柜臺(tái)一個(gè)一個(gè)開 |
| 關(guān)閉順序 | 隨便關(guān),關(guān)哪個(gè)都行 | 從后往前關(guān):最后一個(gè)先關(guān),依次往前 保證主節(jié)點(diǎn)最后退出 |
| 擴(kuò)縮容 | 想擴(kuò)就擴(kuò),想縮就縮 新 Pod 和老 Pod 沒區(qū)別 | 按序號(hào)擴(kuò)縮:擴(kuò)容從下一個(gè)序號(hào)開始 縮容從最大序號(hào)開始刪 |
| 更新方式 | 可以一次性全更新 或者滾動(dòng)更新,順序無(wú)所謂 | 從后往前更新 支持暫停在某個(gè)版本(partition) |
| 適用場(chǎng)景 | Web服務(wù)、API網(wǎng)關(guān) 前端應(yīng)用、計(jì)算任務(wù) | 數(shù)據(jù)庫(kù)(MySQL、Redis) 消息隊(duì)列(Kafka、ZooKeeper) |
2.1 詳解:Pod 身份與網(wǎng)絡(luò)標(biāo)識(shí)
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 命名規(guī)則 | 隨機(jī)哈希后綴nginx-7564c8f6b4-9xv5r | 固定有序序號(hào)web-0, web-1, web-2 |
| Hostname 穩(wěn)定性 | 每次重建都變化 | 永久固定,與序號(hào)綁定 |
| DNS 解析 | 通過(guò) Service 解析到隨機(jī) Pod IP | 支持直接 Pod DNS 解析:<pod-name>.<service-name>.<namespace>.svc.cluster.local |
| Headless Service | 可選,通常不需要 | 必須,用于提供穩(wěn)定網(wǎng)絡(luò)標(biāo)識(shí) |
| 網(wǎng)絡(luò)身份示例 | 無(wú)固定身份 | mysql-0.mysql.default.svc.cluster.local 始終指向 mysql-0 |
關(guān)鍵差異說(shuō)明:
- Deployment:Service 通過(guò)
ClusterIP做負(fù)載均衡,請(qǐng)求隨機(jī)分發(fā)到后端 Pod,Pod 重建后 IP 和名稱都變 - StatefulSet:必須配合
clusterIP: None的 Headless Service,DNS 直接解析到 Pod IP,且 Pod 重建后 DNS 記錄保持不變
2.2 詳解:存儲(chǔ)與數(shù)據(jù)管理
| 特性 | Deployment | StatefulSet |
|---|---|---|
| 存儲(chǔ)定義方式 | 在 Pod 模板中直接定義 volumes 或引用現(xiàn)有 PVC | 使用 volumeClaimTemplates 動(dòng)態(tài)為每個(gè) Pod 創(chuàng)建獨(dú)立 PVC |
| PVC 與 Pod 關(guān)系 | 多個(gè) Pod 可共享同一個(gè) PVC(需支持多掛載) 或每個(gè) Pod 手動(dòng)掛載相同 PVC | 每個(gè) Pod 獨(dú)占一個(gè) PVC,一對(duì)一綁定,命名規(guī)則:<pvc-name>-<statefulset-name>-<ordinal> |
| 數(shù)據(jù)持久性 | Pod 刪除,數(shù)據(jù)可能丟失(取決于卷類型) | Pod 刪除,PVC 保留,數(shù)據(jù)持久化;新 Pod 自動(dòng)掛載原 PVC |
| 存儲(chǔ)擴(kuò)容 | 需手動(dòng)修改 PVC | 需手動(dòng)修改 PVC,或依賴 StorageClass 支持在線擴(kuò)容 |
| 數(shù)據(jù)隔離性 | 低(共享存儲(chǔ))或手動(dòng)管理 | 高(自動(dòng)隔離,每個(gè) Pod 獨(dú)立存儲(chǔ)) |
存儲(chǔ)綁定示例:
# StatefulSet 會(huì)自動(dòng)創(chuàng)建: # PVC: disk-ssd-web-0 → PV: pv-001 (綁定) # PVC: disk-ssd-web-1 → PV: pv-002 (綁定) # 即使 web-0 被刪除重建,disk-ssd-web-0 仍會(huì)重新掛載到原 PV
2.3 詳解:部署與擴(kuò)縮容行為
| 操作 | Deployment | StatefulSet |
|---|---|---|
| 創(chuàng)建順序 | 并行創(chuàng)建,所有 Pod 同時(shí)啟動(dòng) | 串行創(chuàng)建,按序號(hào) 0→1→2→…,前一個(gè) Ready 后才創(chuàng)建下一個(gè) |
| 擴(kuò)容行為 | 并行擴(kuò)容,新 Pod 立即創(chuàng)建 | 按序號(hào)遞增順序創(chuàng)建,保證集群拓?fù)渲鸩綌U(kuò)展 |
| 縮容行為 | 隨機(jī)刪除 Pod | 按序號(hào)遞減刪除(先刪 N-1,再刪 N-2…),保證有序縮減 |
| 滾動(dòng)更新 | 隨機(jī)替換,可設(shè)置 maxSurge/maxUnavailable | 逆序替換(先更新 N-1,最后更新 0),可設(shè)置 partition 進(jìn)行灰度發(fā)布 |
| 更新策略 | RollingUpdate(默認(rèn))、Recreate | RollingUpdate(默認(rèn))、OnDelete(手動(dòng)刪除后重建) |
有序性意義:
- 主從架構(gòu):先啟動(dòng)主節(jié)點(diǎn)(0),再啟動(dòng)從節(jié)點(diǎn)(1,2…),避免從節(jié)點(diǎn)找不到主而崩潰
- 數(shù)據(jù)安全:縮容時(shí)先移除高序號(hào)節(jié)點(diǎn),避免破壞集群的法定人數(shù)(Quorum)
- 灰度發(fā)布:通過(guò)
partition控制只更新部分節(jié)點(diǎn),如partition: 2表示只更新序號(hào) ≥2 的 Pod
2.4 運(yùn)維與故障處理對(duì)比
| 場(chǎng)景 | Deployment | StatefulSet |
|---|---|---|
| Pod 故障重建 | 立即創(chuàng)建新 Pod,隨機(jī)命名,無(wú)狀態(tài)恢復(fù) | 按原序號(hào)重建,掛載原 PVC,自動(dòng)恢復(fù)數(shù)據(jù)和身份 |
| 節(jié)點(diǎn)遷移 | 快速調(diào)度到新節(jié)點(diǎn) | 需等待原 PVC 在目標(biāo)節(jié)點(diǎn)可用(依賴存儲(chǔ)拓?fù)洌?/td> |
| 版本回滾 | kubectl rollout undo 快速回滾 | 需手動(dòng)控制,逆序回滾,需關(guān)注數(shù)據(jù)兼容性 |
| 數(shù)據(jù)備份 | 通常無(wú)需備份 Pod 級(jí)數(shù)據(jù) | 必須制定備份策略,PVC 獨(dú)立存在需單獨(dú)管理 |
| 監(jiān)控重點(diǎn) | 整體吞吐量、錯(cuò)誤率 | 單節(jié)點(diǎn)狀態(tài)、存儲(chǔ)容量、復(fù)制延遲、集群拓?fù)渫暾?/td> |
三、StatefulSet 的使用 - 實(shí)例
部署一個(gè)Nginx StatefulSet
- 先創(chuàng)建一個(gè)nginx的namespace
kubectl create ns nginx
3.1 檢查是否有 storageclass 資源
kubectl get storageclass -A
如果有資源(任何都行,只要不提示No resources found就行),在 nginx-StatefulSet.yaml中的storageClassName請(qǐng)?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
在進(jìn)行創(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)會(huì)為每個(gè) Pod 創(chuàng)建獨(dú)立的 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 驗(yàn)證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)識(shí):
[root@k8s-master StatefulSet]# kubectl exec -n nginx nginx-0 -- hostname nginx-0 # 通過(guò)DNS解析訪問(wèn)(這里需要注意,指定nginx部署的ns,要不然會(huì)報(bào)無(wú)法解析) [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 "測(cè)試失敗" 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
- 獨(dú)立的持久化存儲(chǔ):
# 查看自動(dòng)創(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會(huì)自動(dòng)重建,查看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ù)了,可能是因?yàn)閯?chuàng)建pod的時(shí)候會(huì)直接寫入默認(rèn)的配置,導(dǎo)致把后來(lái)修改的數(shù)據(jù)重寫了。
這里我在創(chuàng)建nginx-StatefulSet的時(shí)候是先手動(dòng)添加數(shù)據(jù)了,因?yàn)槟J(rèn)沒有index.html,會(huì)導(dǎo)致啟動(dòng)失敗,所以需要先寫入數(shù)據(jù)才可以;并且加了個(gè)判斷,如果存在這個(gè)文件那么就不會(huì)寫入數(shù)據(jù),這樣就不會(huì)在重建pod的時(shí)候數(shù)據(jù)重寫了。
四、StatefulSet的更新策略
StatefulSet支持兩種更新策略,通過(guò)spec.updateStrategy.type 字段控制:
| 策略類型 | 行為 | 適用場(chǎng)景 |
|---|---|---|
| RollingUpdate (默認(rèn)) | 按順序從后向前滾動(dòng)更新 Pod | ? 大多數(shù)場(chǎng)景 |
| OnDelete | 手動(dòng)刪除 Pod 后才更新 | ?? 需要完全控制更新時(shí)機(jī)的場(chǎng)景 |
4.1 RollingUpdate 策略(默認(rèn))
4.1.1 基本更新寫法
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: RollingUpdate # 默認(rèn)值,可不寫
# ... 其他配置更新順序:
- 從序號(hào)最大的 Pod 開始更新(從后向前)
- 依次向前推進(jìn)(nginx-2 → nginx-1 → nginx-0)
- 每個(gè) Pod 更新成功并進(jìn)入 Ready 狀態(tài)后,才更新下一個(gè)
為什么從后往前?
- 保證主節(jié)點(diǎn)(通常是序號(hào)0)最后更新
- 對(duì)于主從架構(gòu)的應(yīng)用,先更新從節(jié)點(diǎn),最后更新主節(jié)點(diǎn)
- 減少對(duì)服務(wù)的影響
4.1.2 分區(qū)更新(Partition)-重要特性
RollingUpdate 支持 partition 參數(shù),可以控制更新的范圍:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新序號(hào) >= 2 的 Pod
# ... 其他配置分區(qū)規(guī)則:
- 序號(hào) >= partition 的 Pod 會(huì)被更新
- 序號(hào) < partition 的 Pod 保持舊版本
4.1.3 分區(qū)更新實(shí)例:
假設(shè)有 3 個(gè)副本:nginx-0, nginx-1, nginx-2
| partition 值 | 哪些 Pod 被更新 | 哪些 Pod 保持不變 |
|---|---|---|
partition: 0 | nginx-2, nginx-1, nginx-0 (全部) | 無(wú) |
partition: 1 | nginx-2, nginx-1 | nginx-0 |
partition: 2 | nginx-2 | nginx-0, nginx-1 |
partition: 3 | 無(wú) | nginx-0, nginx-1, nginx-2 (全部) |
- 場(chǎng)景1:金絲雀發(fā)布(Canary Release)
# 第一步:只更新最后一個(gè) Pod 做測(cè)試
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新 nginx-2測(cè)試 nginx-2 沒問(wèn)題后,更新其他所有的pod:
# 第二步:更新所有 Pod
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # 更新全部- 場(chǎng)景2:維護(hù)主節(jié)點(diǎn)不更新
# 主節(jié)點(diǎn)是 nginx-0,保持不更新
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 1 # 只更新 nginx-1, nginx-2- 場(chǎng)景3:分批發(fā)布
# 第1批:更新 30%(最后一個(gè))更新完需測(cè)試 partition: 2 # 第2批:更新 60%(后兩個(gè))更新完需測(cè)試 partition: 1 # 第3批:更新 100% 更新完需測(cè)試 partition: 0
4.2 OnDelete 策略(手動(dòng)觸發(fā))
- OnDelete 策略寫法
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
updateStrategy:
type: OnDelete # 手動(dòng)模式
# ... 其他配置- 行為特點(diǎn)
- K8s 不會(huì)自動(dòng)更新任何 Pod
- 只有當(dāng)你手動(dòng)刪除 Pod 時(shí),重建的 Pod 才會(huì)使用新配置
- 你可以控制每個(gè) Pod 的更新時(shí)機(jī)
- 適用場(chǎng)景
- 數(shù)據(jù)庫(kù)版本升級(jí):需要逐個(gè)節(jié)點(diǎn)手動(dòng)操作,確保數(shù)據(jù)同步
- 核心業(yè)務(wù):需要在業(yè)務(wù)低峰期逐個(gè)更新
- 需要手動(dòng)驗(yàn)證:每個(gè)節(jié)點(diǎn)更新后都要手動(dòng)驗(yàn)證
- 配合外部工具:與 Ansible、Jenkins 等工具集成
- 實(shí)際操作示例
# 1. 修改 StatefulSet 鏡像版本(但不會(huì)立即生效)
kubectl patch sts -n nginx nginx -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.25"}]}}}}'
# 2. 手動(dòng)刪除要更新的 Pod
kubectl delete pod -n nginx nginx-2 # 先更新最后一個(gè)
## 更新完成查看版本號(hào),確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
# 3. 驗(yàn)證 nginx-2 沒問(wèn)題后
kubectl delete pod -n nginx nginx-1 # 更新中間
## 更新完成查看版本號(hào),確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
# 4. 最后更新主節(jié)點(diǎn)
kubectl delete pod -n nginx nginx-0
## 更新完成查看版本號(hào),確認(rèn)是否更新成功
kubectl exec -n nginx nginx-2 -- nginx -v
- 控制器不會(huì)自動(dòng)更新Pod
- 需要手動(dòng)刪除Pod才能觸發(fā)重建更新
- 適用于需要完全控制更新時(shí)機(jī)的場(chǎng)景,如核心應(yīng)用的流量無(wú)損升級(jí)
4.3 更新策略對(duì)比
| 特性 | RollingUpdate | OnDelete |
|---|---|---|
| 自動(dòng)更新 | ? 是 | ? 否 |
| 更新順序 | 從后往前 | 由你控制 |
| 分區(qū)控制 | ? 支持 (partition) | ? 不支持 |
| 回滾方式 | 重新設(shè)置 image 或 partition | 手動(dòng)刪除重建 |
| 適用場(chǎng)景 | Web服務(wù)、緩存、一般應(yīng)用 | 數(shù)據(jù)庫(kù)、核心應(yīng)用、特殊需求 |
4.4 更新策略選擇建議
選擇 RollingUpdate 當(dāng):
? 應(yīng)用無(wú)狀態(tài)或能優(yōu)雅處理滾動(dòng)重啟
? 想要自動(dòng)化更新
? 需要金絲雀發(fā)布(配合 partition)
? 更新風(fēng)險(xiǎn)較低選擇 OnDelete 當(dāng):
? 數(shù)據(jù)庫(kù)等有狀態(tài)核心應(yīng)用
? 需要手動(dòng)干預(yù)每個(gè)節(jié)點(diǎn)的更新
? 更新風(fēng)險(xiǎn)高,需要逐個(gè)驗(yàn)證
? 有外部編排工具(Ansible、Operator)
五、StatefulSet vs. Deployment:如何選擇?
| 特性 | StatefulSet | Deployment |
|---|---|---|
| Pod命名 | 固定有序名稱(xxx-0, xxx-1) | 隨機(jī)名稱(xxx-隨機(jī)字符串) |
| 網(wǎng)絡(luò)標(biāo)識(shí) | 穩(wěn)定,Pod重建不變 | 變化,每次重建新IP |
| 存儲(chǔ) | 支持PVC模板,持久化存儲(chǔ) | 通常使用共享存儲(chǔ)或臨時(shí)存儲(chǔ) |
| 順序控制 | 有序部署、更新、刪除 | 并行操作 |
| 適用場(chǎng)景 | 數(shù)據(jù)庫(kù)、消息隊(duì)列、有狀態(tài)服務(wù) | Web服務(wù)、API網(wǎng)關(guān)、無(wú)狀態(tài)應(yīng)用 |
選擇建議:
- 應(yīng)用需要穩(wěn)定的網(wǎng)絡(luò)標(biāo)識(shí) → 選擇StatefulSet
- 應(yīng)用需要獨(dú)立的持久化存儲(chǔ) → 選擇StatefulSet
- 應(yīng)用要求有序的部署和更新 → 選擇StatefulSet
- 否則,優(yōu)先考慮Deployment或ReplicaSet
六、注意事項(xiàng)
數(shù)據(jù)安全:StatefulSet刪除時(shí)不會(huì)自動(dòng)刪除PVC,需手動(dòng)清理不再需要的存儲(chǔ)卷,避免資源浪費(fèi)
Headless Service必須:StatefulSet必須關(guān)聯(lián)Headless Service才能保證網(wǎng)絡(luò)標(biāo)識(shí)的穩(wěn)定性
存儲(chǔ)類選擇:根據(jù)性能需求選擇合適的StorageClass,生產(chǎn)環(huán)境推薦使用SSD或高性能云盤
備份策略:雖然StatefulSet保證了Pod重建后的數(shù)據(jù)持久性,但仍需建立定期備份機(jī)制,防止數(shù)據(jù)損壞或誤刪除
縮容謹(jǐn)慎:縮容StatefulSet會(huì)導(dǎo)致Pod被刪除,但PVC保留。如果希望徹底清理,需要手動(dòng)刪除對(duì)應(yīng)的PVC
七、總結(jié)
StatefulSet是Kubernetes中管理有狀態(tài)應(yīng)用的核心控制器,通過(guò)穩(wěn)定的網(wǎng)絡(luò)標(biāo)識(shí)、獨(dú)立的持久化存儲(chǔ)和有序的生命周期管理,為數(shù)據(jù)庫(kù)、消息隊(duì)列等有狀態(tài)應(yīng)用提供了完善的運(yùn)行環(huán)境。掌握StatefulSet的使用,意味著你能夠?qū)⒏嗟膫鹘y(tǒng)應(yīng)用平滑地遷移到Kubernetes平臺(tái),充分發(fā)揮容器編排的優(yōu)勢(shì)。
在實(shí)際應(yīng)用中,需要根據(jù)業(yè)務(wù)需求合理選擇更新策略和Pod管理策略,同時(shí)注意數(shù)據(jù)安全和備份,才能構(gòu)建出穩(wěn)定可靠的有狀態(tài)應(yīng)用系統(tǒng)。
到此這篇關(guān)于K8s之StatefulSet控制器的文章就介紹到這了,更多相關(guān)K8s之StatefulSet控制器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
K8s內(nèi)存溢出問(wèn)題剖析之排查與解決過(guò)程
這篇文章主要介紹了K8s內(nèi)存溢出問(wèn)題剖析之排查與解決過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-07-07
云服務(wù)器Jenkins部署Springboot項(xiàng)目及Vue項(xiàng)目的詳細(xì)過(guò)程
本文詳細(xì)介紹了如何在云服務(wù)器上使用Jenkins部署Springboot和Vue項(xiàng)目,包括創(chuàng)建Springboot項(xiàng)目并上傳到Git倉(cāng)庫(kù)、安裝Maven和配置Maven插件、安裝Gitee插件、配置Jenkins任務(wù)以及創(chuàng)建自由風(fēng)格項(xiàng)目等步驟,感興趣的朋友一起看看吧2025-02-02
k8s平臺(tái)本地?cái)?shù)據(jù)遷移整改過(guò)程
文章主要介紹了對(duì)k8s平臺(tái)默認(rèn)數(shù)據(jù)存儲(chǔ)位置進(jìn)行優(yōu)化,將數(shù)據(jù)統(tǒng)一管理并存至指定目錄和云磁盤的整個(gè)過(guò)程,包括新磁盤的創(chuàng)建掛載、配置更改生效、環(huán)境檢查、清理舊環(huán)境等步驟,強(qiáng)調(diào)在集群所有節(jié)點(diǎn)上進(jìn)行優(yōu)化,并提醒配置更改生效后建議重啟機(jī)器以確保變更生效2026-05-05
k8s中容器創(chuàng)建的全過(guò)程實(shí)踐
這篇文章主要介紹了k8s中容器創(chuàng)建的全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-07-07
Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實(shí)踐
這篇文章主要為大家介紹了Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
教你在k8s上部署HADOOP-3.2.2(HDFS)的方法
這篇文章主要介紹了k8s-部署HADOOP-3.2.2(HDFS)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04

