CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
存储卷 PV、PVC、StatefulSet 直觉。
存储卷
这一课你将能做到
说清容器可写层的临时性,并用 emptyDir 实验证明「Pod 一删数据就没」
解释 PV、PVC、StorageClass 三者的分工,写出并绑定一个 PVC
完成本站核心实验:MySQL 挂 PVC,删 Pod 重建后数据仍在;并能判断本站数据库何时该放集群外
为什么要学
Pod 重建后,容器可写层默认清空。本站的课程数据、用户内容都在 MySQL 里——这和 Docker 课里「删容器丢库」是同一个风险,只是集群里多了一层「Pod 还会被调度到别的节点」。
K8s 用声明式解决持久化:PersistentVolumeClaim(PVC) 是应用「我要一块 5Gi 盘」的申领,PersistentVolume(PV) 是真实的存储块,StorageClass 定义「用哪种存储自动供给」。理解这套模型,你才能回答面试题「PV/PVC/SC 什么关系」,也才能对「本站 MySQL 到底放哪」做出专业判断。
先建立图像
Pod 声明 volumeMount(挂到容器某路径)
→ volumes 引用 PVC
→ PVC 绑定 PV(静态创建或 StorageClass 动态供给)
→ 云盘 / 本地路径 / NFS / Ceph …
选型直觉:
无状态 API(backend/platform):Deployment,不需要 PVC
有状态数据(MySQL):StatefulSet + 每副本独立 PVC,或干脆用托管库
临时缓存/共享临时文件:emptyDir(Pod 删即失)
对象 角色 人话 StorageClass 存储供给的「模板」 「要哪种货、怎么造」 PVC 应用的存储申领 「提货单:我要 5Gi、可读写」 PV 真实存储块 「仓库货位」
访问模式 缩写 含义 ReadWriteOnce RWO 单节点读写(数据库最常用) ReadOnlyMany ROX 多节点只读 ReadWriteMany RWX 多节点读写(NFS 类,共享文件)
跟着做
步骤 1:先证明「默认不持久」
pod-emptydir.yaml:
apiVersion: v1
kind: Pod
metadata:
name: cache-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["/bin/sh", "-c", "echo v1 > /data/note.txt; sleep 3600"]
volumeMounts:
- name: scratch
mountPath: /data
volumes:
- name: scratch
emptyDir: {} # 生命周期 = Pod 的临时盘
kubectl apply -f pod-emptydir.yaml
kubectl exec cache-demo -- cat /data/note.txt # 输出 v1
kubectl delete pod cache-demo
kubectl apply -f pod-emptydir.yaml
kubectl exec cache-demo -- cat /data/note.txt # 又是新的一次写入
emptyDir 适合:临时缓存、同 Pod 容器间共享文件。绝不能放数据库数据 。
步骤 2:看集群的 StorageClass 并申请 PVC minikube 自带名为 standard 的默认供给器(云集群是对应云盘的 SC)。申请一块盘:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
# storageClassName: standard # 不写则用默认 SC;云上需显式指定
kubectl apply -f pvc-mysql.yaml
kubectl get pvc mysql-pvc
STATUS 应从 Pending 变为 Bound。若一直 Pending:没有可用 SC 或容量/模式无人能满足(见卡点急救)。
步骤 3:MySQL 挂上 PVC deploy-mysql.yaml(沿用第 7 课的 db-secret):
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1 # 单实例演示;多副本 MySQL 不能这样硬凑
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
ports:
- containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql # 数据目录
volumes:
- name: data
persistentVolumeClaim:
claimName: mysql-pvc
kubectl apply -f deploy-mysql.yaml
kubectl get pods -l app=mysql -w # 等到 Ready(首次初始化较慢)
步骤 4:核心实验——删 Pod,数据还在 kubectl exec -it deploy/mysql -- mysql -uroot -p"change-me-demo" \
-e "CREATE DATABASE IF NOT EXISTS k8stest; USE k8stest; CREATE TABLE t(id INT); INSERT INTO t VALUES (42);"
模拟 Pod 故障(数据库容器崩溃、节点驱逐都会导致重建):
kubectl delete pod -l app=mysql
kubectl get pods -l app=mysql -w # 等新 Pod Ready
kubectl exec deploy/mysql -- mysql -uroot -p"change-me-demo" \
-e "SELECT * FROM k8stest.t;"
应输出 42——持久化靠 PVC 而不是容器层,这就是「删 Pod 数据还在」的验收标准。
步骤 5:看 PV 与回收策略 kubectl get pv
kubectl get pv <你的pv名> -o yaml | findstr /i "persistentVolumeReclaimPolicy"
Delete(动态供给默认):PVC 删除时 PV 与云盘一起删——删 PVC 前想三遍
Retain:PV 保留,需人工处理——重要数据建议此模式
kubectl delete pvc mysql-pvc # 演示完可删(Retain 模式下 PV 会留为 Released)
步骤 6:StatefulSet 直觉与本站选型 记下结论(细节进阶再展开):StatefulSet 为每个副本提供稳定主机名(mysql-0/1/2)与独立 PVC,适合主从/集群拓扑;Deployment 的多副本共享模板,不适合有状态集合。
backend / platform → Deployment(无 PVC)
MySQL、Redis → 集群外 Compose 或云托管,DB_HOST/REDIS_HOST 指向可达地址
API 的发布与回滚熟练后,再评估是否把库迁进集群(迁入前先落备份演练)
讲清楚要点
容器层易失,有状态目录必须外置 :PVC、托管库二选一,没有第三条路。
PVC 与 Pod 生命周期解耦 :可以删 Pod 保数据,也可以把盘重新挂到新 Pod;这正是「数据库容器化」的立足点。
RWO ≠ 单副本,而是单节点 :同一节点上多个 Pod 可以共享一块 RWO 盘;跨节点多副本挂同一 RWO 盘会调度失败(Multi-Attach error)。
多副本同时写一块盘 ≠ 高可用 :RWX 共享写或单盘多挂都不解决复制问题;MySQL 高可用要靠主从/MGR 等复制拓扑。
StorageClass 因集群而异 :同一份 PVC YAML 在 minikube 能绑、到云上要核对 SC 名与参数(性能等级、加密、备份)。
上集群前先问备份 :本站核心资产在库里,「有没有验证过的恢复流程」比「有没有用上 StatefulSet」重要得多。
你可能会懵的地方
emptyDir、hostPath、PVC 怎么选?
emptyDir:Pod 级临时盘(缓存/中转);hostPath:节点本地路径(演示方便,生产慎用——调度绑死节点、节点故障数据即失);PVC:跨 Pod 存活的正式持久化。生产数据一律 PVC 或托管。
Deployment 能挂 PVC 吗?
能。单副本(如本课 MySQL 演示)没问题;多副本挂同一 RWO PVC 会踩调度坑。真正的多副本有状态应用用 StatefulSet + volumeClaimTemplates。
PVC 为什么要和 Pod 分开?
解耦后存储独立于计算:应用发布、扩缩、迁移都不动数据;这也是「应用无状态化 + 状态外置」架构的 K8s 表达。
删了 Deployment,数据还在吗?
在。Deployment/Pod 删除不影响 PVC/PV;但要小心 PVC 自身的删除策略与 PV 回收策略(步骤 5)。
和 Docker named volume 比多了什么?
同属「数据外置」,但 K8s 增加了申领模型(PVC)、供给模板(SC)、跨节点调度约束与回收策略——存储第一次成为「可编排对象」。
错法对照 错法 问题 正解 数据库只写容器可写层 Pod 重建即丢库 PVC 或托管库 多副本 MySQL 挂同一块 RWO 盘 调度失败或数据腐坏 一实例一盘 + 官方复制拓扑 只做备份从不演练恢复 出事才知道备份不可用 定期恢复演练(核心!) 把所有服务都改 StatefulSet 过度设计 无状态服务一律 Deployment 生产依赖 hostPath 节点故障即丢数据 云盘 CSI / 网络存储
卡点急救
PVC 一直 Pending :kubectl describe pvc 看事件——没有默认 StorageClass(补 storageClassName)、请求容量/模式无 PV 能满足、云集群配额不足。
Pod 起不来报 Multi-Attach error :RWO 盘被另一节点占用。kubectl get pods -o wide 看新旧 Pod 节点分布;删旧 Pod 等其完全退出,或改用支持跨节点的存储。
「数据丢了」 :先回忆用的是哪类卷——emptyDir/hostPath 随 Pod/节点消亡属预期;PVC 场景查 PVC 是否被误删、PV 回收策略是否为 Delete。教训:重要 PV 一律 Retain。
面试常问
PV、PVC、StorageClass 三者的关系?
PVC 是用户侧的存储申领(容量+访问模式),PV 是集群侧的真实存储块,两者通过绑定关联;StorageClass 定义动态供给模板,PVC 指定 SC 后由供应器自动创建 PV 并绑定。类比:PVC 提货单、PV 仓库货位、SC 供货流程。运维管 PV/SC,开发只管 PVC——职责分离。
accessModes 的 RWO/ROX/RWX 怎么理解?
描述的是「节点级挂载方式」而非 Pod 数:RWO 单节点读写(数据库典型);ROX 多节点只读(配置、模型文件分发);RWX 多节点读写(NFS 共享文件)。注意 RWO 限制的是节点不是副本数,同一节点的多 Pod 可共享一块 RWO 盘;而多副本同时写需要的是复制机制,不是 RWX。
为什么生产数据库经常不放 K8s 集群里?
三个理由:① 存储与故障域——数据库要稳定低延迟 IO 与独立故障隔离;② 运维复杂度——主从切换、备份恢复、版本升级在集群内更复杂;③ 云托管(RDS 等)自带高可用、备份、监控。合理姿势:无状态 API 上集群,数据层托管或集群外,二者用 Service DNS/ExternalName 打通——这也是中小团队的性价比最优解。
自测三问
PVC 一直 Pending,你按什么顺序排查?
为什么「三个副本的 Deployment + 一块 MySQL 数据盘」是错误模型?
本站若 API 上 K8s、MySQL 仍在 Compose 卷里,这算不算合理过渡?要补什么?
小结一句话 PVC 把持久盘申领进集群,PV/SC 负责供给与回收;无状态服务走 Deployment、有状态数据走 PVC/StatefulSet/托管——下一课用 Ingress 把 HTTP 入口收拢,对标现网 Nginx 反代。
延伸