前言: 昨天我们让一个新 Pod 重新挂上旧 PVC ,预览文件没有丢。今天需求变了:同一个服务要跑两个副本,而且每个副本都要保存自己的工作记录。正常情况下,一个副本重建后应该只拿回自己的文件;我第一反应却是把昨天那份 PVC 配置复制两遍。翻到 Kubernetes 的 StatefulSet 文档后,才发现这里缺的不只是一块盘,还有"这块盘原来属于谁"的身份。于是我想弄清楚两件事:
text
没有 StatefulSet 时,多个副本怎么找回各自的文件?
用了 StatefulSet 后,副本重建和缩容又会留下什么问题?
⚠️ Tips: 这篇只用假数据演示副本与存储的对应关系,不搭数据库,也不讨论主从选举。
一. 两个副本都能写,重建后该读谁的
-
假设图片处理服务分成两个工作副本,各自把尚未处理完的任务记录写在
/data。第一个写了worker-0,第二个写了worker-1。单个 Pod 用 PVC 保存文件的事昨天已经试过,但现在我们更关心:删掉第一个副本后,补出来的实例能不能准确拿回第一份记录。 -
如果两个副本都引用同一个 PVC ,它们看到的是同一份存储,不会自动分成两个"私有抽屉"。而且能否同时挂载,还受访问模式、节点位置和存储后端约束。把
ReadWriteOnce理解成"每个 Pod 自动分一份盘",这个想法本身就错了。

图里重点看两条线最后落到同一份声明,副本自己的身份还没有进入存储关系。
二. 我先试着用名字给它们分盘
-
最粗暴的办法是手动建两份 PVC ,再写两个 Pod 清单,让甲用甲的盘、乙用乙的盘。这样确实能对应上,但副本从两个扩到三个,就得继续手动加清单和声明。少改一个名字,文件就可能接错。
-
稍微省事一点的想法,是交给 Deployment 维持两个副本,再设法按副本名字选盘。问题在于 Deployment 关心的是"始终有两个可用实例",新旧 Pod 可以互换;它创建的 Pod 名字也不是适合拿来长期认领存储的固定编号。昨天那种"手动让新 Pod 引用旧 PVC"的方法,到了多副本就开始别扭了。
| 管理办法 | 能否区分副本的文件 | 扩容时要做什么 |
|---|---|---|
| 两份手写 Pod 与 PVC | 可以,靠人维护映射 | 再补清单和存储声明 |
| Deployment 加共享 PVC | 不能自动分成两份 | 还要处理挂载与并发写入 |
| 稳定编号加独立 PVC | 可以按编号对应 | 由控制器创建新副本及声明 |

这张图的红色断点在"新实例是谁":光有两份盘,还缺一个稳定的认领方式。
三. 给副本一张固定工牌
-
这时候 StatefulSet 才真正派上用场。可以把两个副本想成轮班员工:员工会离职再补人,但岗位工牌始终是 0 号和 1 号。工牌对应各自的文件柜,新员工上岗时拿原岗位的柜子。这里的"工牌"是稳定的序号身份,"文件柜领用单"就是每个副本自己的 PVC。
-
在默认编号下,名为
notes的 StatefulSet 会管理notes-0和notes-1。volumeClaimTemplates声明的是"每个副本需要什么存储",不是一个供大家共同使用的 PVC ;控制器会分别生成data-notes-0、data-notes-1。Pod 重建时,编号与对应的声明仍然能配上。
text
notes-0 → data-notes-0 → 第一份文件
notes-1 → data-notes-1 → 第二份文件
- 清单里还要有一个无头 Service ,并让
serviceName指向它。这是副本稳定网络身份所需的配套对象;本文实验主要观察存储对应关系,不把"有固定名字"误说成"应用已经完成数据复制或故障切换"。

注意两条绿色线没有交叉:重建的是实例,0 号对应的声明仍是 0 号那份。
四. 删除 0 号副本,再看两份文件
- 我在本机单节点 OrbStack Kubernetes 测试集群使用默认
local-pathStorageClass 。仓库同目录的demo.yaml放了无头 Service 、两个副本的 StatefulSet 和一份卷声明模板。先在独立命名空间创建它们,等两个副本都就绪,再分别写入不同内容。
bash
kubectl create namespace blog-stateful-lab
kubectl -n blog-stateful-lab apply -f demo.yaml
kubectl -n blog-stateful-lab rollout status statefulset/notes --timeout=180s
kubectl -n blog-stateful-lab get pod,pvc
kubectl -n blog-stateful-lab exec notes-0 -- sh -c 'echo worker-0 > /data/owner.txt'
kubectl -n blog-stateful-lab exec notes-1 -- sh -c 'echo worker-1 > /data/owner.txt'
- 然后只删
notes-0,由控制器补出同名副本。等它就绪后,分别读取两份文件。真正要看的不是"两个 Pod 都变绿了",而是 0 号依旧读到worker-0,1 号依旧读到worker-1。
bash
kubectl -n blog-stateful-lab delete pod notes-0 --wait=true
until kubectl -n blog-stateful-lab get pod notes-0 >/dev/null 2>&1; do sleep 1; done
kubectl -n blog-stateful-lab wait --for=condition=Ready pod/notes-0 --timeout=180s
kubectl -n blog-stateful-lab exec notes-0 -- cat /data/owner.txt
kubectl -n blog-stateful-lab exec notes-1 -- cat /data/owner.txt
kubectl -n blog-stateful-lab get pvc
- 这里先等新 Pod 对象出现,再等待就绪,避免刚删除后立刻查询遇到
NotFound。这组命令只演示本机单节点、顺序重建的存储对应关系。若 PVC 一直Pending,先查默认 StorageClass 是否能供给卷;若 Pod 卡在挂载阶段,再查事件。实验结束可运行kubectl delete namespace blog-stateful-lab;清理前先确认命名空间里没有真实数据。

这张图重点看删除前后的编号与声明名称,两个文件没有发生交换。
五. 身份稳定,也不等于数据高可用
-
StatefulSet 管理的是副本身份和声明关系,不会替应用做数据复制。若两个副本原本各写各的文件,0 号挂掉时,1 号不会凭空拥有 0 号的数据。是否能跨节点重新挂载同一卷,还要看存储后端;本机
local-path实验不能证明跨节点恢复。 -
还有一个容易踩的坑:默认情况下,缩容或删除 StatefulSet 不会自动删除由卷声明模板创建的 PVC 。这是为了避免误删数据,却也意味着缩容后的存储可能继续占空间。新版本支持用
persistentVolumeClaimRetentionPolicy调整行为,但要先核对集群版本和保留策略,再决定是否开启自动删除。别把"删掉工作岗位"当成"顺便清空文件柜"。 -
有了固定名字,也不要把 StatefulSet 当成所有多副本服务的默认答案。如果只是无状态图片接口,Deployment 让副本可互换,反而更贴合需求。只有每个副本确实需要保留自己的身份或存储时,固定编号才有价值。

橙色卡片说的是额外条件;绿色只表示"能找回原声明",不代表数据已经有备份。
六. 总结:先问副本能不能互换
- 回到两个图片处理副本,如果它们只处理请求、不保存各自的长期状态,用 Deployment 就好。若每个副本都要认领自己的工作记录,StatefulSet 的固定编号加
volumeClaimTemplates才能把"谁对应哪份存储"交给控制器。它延续了昨天的 PVC 思路,解决的是多副本映射,而不是重新讲一遍单个 Pod 怎样保存文件。
text
副本完全可互换:先看 Deployment
副本要认领自己的存储:看 StatefulSet 与 volumeClaimTemplates
需要故障后读到完整数据:再设计存储后端、复制和备份

下次扩容一个有状态服务,先问"新副本需要一份新存储,还是要共享旧数据"。想清楚这一点,再去写副本数,比直接把 replicas 改大更稳妥。
本文参考: StatefulSet 概念 · StatefulSet 基础教程