7.7 存储卷与持久化
一句话
K8s 的存储只有两种语义——Pod 内临时(emptyDir,Pod 删就没)与跨 Pod 持久(PVC,独立生命周期);PVC 的关键不是 “能挂上”,而是 “绑定到哪个 PV、能不能扩容、Pod 漂移时多久能挂回来”。
五种卷,先分清生命周期
| 类型 | 生命周期 | 数据在哪 | 用途 |
|---|---|---|---|
emptyDir | Pod | 节点磁盘(或内存) | 同 Pod 共享、缓存、临时落盘 |
hostPath | 节点 | 节点目录 | 读节点信息;业务数据禁用 |
configMap / secret | 对象 | apiserver / etcd | 配置注入(见 7.4 配置与密钥 (ConfigMap / Secret)) |
| PVC | 独立 | 云盘 / NFS / 分布式存储 | 数据库、消息队列、上传文件 |
projected | Pod | 组合多个来源 | 把 SA token + config 合成一个目录 |
PV / PVC / StorageClass:三者的账
Pod ──挂载──► PVC(我要 100Gi、RWO)
│ 绑定
▼
PV(一块真实的云盘/目录)
▲ 由谁创建
StorageClass(动态供给:参数化创建 PV)
| 概念 | 是 “谁” 的抽象 | 谁创建 |
|---|---|---|
| PVC | 使用者的需求声明 | 开发者(或 StatefulSet 模板) |
| PV | 集群里的一块存储 | 管理员手工,或 StorageClass 动态创建 |
| StorageClass | 存储的模板与参数(类型、IOPS、回收策略) | 管理员 |
两个高频开关:
| 设置 | 作用 | 坑 |
|---|---|---|
volumeBindingMode: WaitForFirstConsumer | Pod 调度后再创建/绑定卷,保证卷与 Pod 同 AZ | 没有 Pod 就一直 Pending,看起来像 “卷坏了” |
reclaimPolicy: Delete / Retain | 删 PVC 后是否删底层存储 | Delete 是默认,误删 PVC = 云盘被删;数据库务必用 Retain |
StatefulSet 的存储身份
| 机制 | 效果 |
|---|---|
volumeClaimTemplates | 为每个序号生成固定名字的 PVC:data-web-0、data-web-1 |
| 缩容 | 只缩 Pod,PVC 保留(默认不删) |
| 扩容 | 新序号自动创建新 PVC 并绑定 |
| 重建 | web-0 重建后挂回 data-web-0,数据接上 |
⚠️ 删 StatefulSet 不会删 PVC,这是保护数据的设计,也常导致 “以为清理干净了,磁盘还在计费”。清理要显式删 PVC。
扩容、快照与备份
| 能力 | 前提 | 边界 |
|---|---|---|
| 在线扩容 | StorageClass allowVolumeExpansion: true + 驱动支持 | 只能增不能减;文件系统扩容可能要求重启 Pod |
| 快照 | VolumeSnapshot CRD + CSI 驱动支持 | 快照 ≠ 备份:同账号同区域,误删会连快照一起没 |
| 跨区恢复 | 快照复制到另一区域 | 有延迟与流量费,RPO 取决于复制频率 |
备份要按 “独立副本” 设计:跨账号/跨区/离线,并做过恢复演练,方法与 RPO/RTO 的关系见 10.4 快照与备份。
挂载失败的四类原因
| 现象 | 原因 | 查法 |
|---|---|---|
PVC 一直 Pending | 无匹配 PV / StorageClass 不存在 / 等待首个消费者 | kubectl describe pvc 看事件 |
Pod ContainerCreating 很久 | attach/detach 中(跨节点漂移 30 秒–2 分钟) | kubectl describe pod 看 AttachVolume 事件 |
Multi-Attach error | RWO 卷还挂在旧节点 | 确认旧 Pod 已终止;必要时等节点侧摘除 |
| 权限错误(Permission denied) | 卷的 fsGroup/UID 与容器用户不匹配 | 设 securityContext.fsGroup 或用 initContainer 修权限 |
反模式
| 反模式 | 后果 |
|---|---|
| 数据库数据目录不挂 PVC | 重建即丢数据(机制见 6.7 容器存储与网络) |
| 用 hostPath 存业务数据 | Pod 漂移后数据留在旧节点 |
reclaimPolicy: Delete 用于生产数据库 | 误删 PVC 直接删盘,无法恢复 |
| 用 RWO 卷给多副本共享写 | 文件损坏(RWO 是单节点,不是单 Pod) |
| 把快照当备份 | 同区域同账号,账号被封/误删时一起消失 |
| StatefulSet 缩容后忘了清理 PVC | 存储持续计费,且再扩容时挂到旧数据 |
忽略 WaitForFirstConsumer | 误判 “卷创建失败”,实际在等 Pod |
三条铁律
🔑 数据要活过 Pod 就挂 PVC;容器可写层永远只放临时数据。
🔑 生产数据库用
Retain;Delete让一次误删变成不可恢复事故。
🔑 快照 ≠ 备份;备份必须是独立副本,并且恢复演练过。
自检
Q1 一个 StatefulSet 有 3 个副本,缩到 1 再扩回 3,数据还在吗?
▸ 答案:在。缩容只删 Pod,data-web-1、data-web-2 两个 PVC 保留;扩回 3 时新 Pod 按序号复用同名 PVC,数据接上。但这意味着 “缩容不省钱”(存储仍计费),也意味着缩容后重新扩容可能读到旧数据——如果要 “干净重建”,得手工删 PVC。
Q2 PVC 一直 Pending,你会按什么顺序查?
▸ 答案:① kubectl describe pvc 的事件(no persistent volumes available / waiting for first consumer / storageclass not found);② StorageClass 是否存在且 volumeBindingMode 是 WaitForFirstConsumer(需要先有 Pod);③ 云侧配额(云盘数量上限、容量配额);④ 可用区是否匹配(Pod 被调度到没有该类存储的 AZ)。四步能覆盖 90% 的 Pending。
Q3 为什么 reclaimPolicy: Delete 在生产数据库上是危险默认?
▸ 答案:因为它把 “删 PVC” 与 “删底层云盘” 绑在一起。一次误操作(改错命名空间、清理脚本写错)就会让数据不可恢复,连快照也可能同账号一起没。生产数据库应显式设 Retain,让删除 PVC 后底层盘保留,人工确认后再清理——多一步人工,换回可恢复性。
Q4 Pod 从节点 A 漂到节点 B,为什么恢复时间比 “启动进程” 长很多?
▸ 答案:因为要多一步 attach/detach:云盘先从 A 摘除、再挂到 B,典型 30 秒–2 分钟,叠加镜像拉取与启动。这就是 RTO 里最容易被漏掉的一段。优化:用 nodeAffinity 尽量回到原节点、选支持快速重挂的存储类型、把这段时长计入 RTO 目标。
Q5 快照和备份到底差在哪?给一个反例。
▸ 答案:快照是同一份存储的时间点视图,通常与源共享故障域(同区域、同账号);备份是独立副本(跨账号/跨区/离线)。反例:某团队每天做快照,觉得万无一失;攻击者拿到账号权限后先删云盘再删快照,两条一起没。正确做法是快照解决 “误操作/回滚”,备份解决 “账号级/区域级灾难”,两者都要,且备份要演练恢复。
跨篇相关:卷的挂载类型与生命周期见 6.7 容器存储与网络;快照与备份的差别见 10.4 快照与备份;存储介质的选择见 10.1 块存储 / 文件存储 / 对象存储;云盘性能与 IOPS 见 10.3 云盘性能模型 (IOPS / 吞吐 / 时延);StatefulSet 的对象语义见 7.2 K8s 对象模型 (Pod / ReplicaSet / Deployment / StatefulSet / DaemonSet / Job);调度与 WaitForFirstConsumer 的交互见 7.5 调度与亲和性。