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 调度与亲和性。