存储与持久化¶
Pod 随时会被删除、重建、漂移,容器里的文件也跟着消失。存储卷要解决的就是:哪些数据应该跟着 Pod 走,哪些应该独立于 Pod 存在。
你将学到¶
- emptyDir、hostPath、NFS、PVC 的适用场景与边界
- PV / PVC / StorageClass 的绑定流程
- 静态供给与动态供给的差异
- 访问模式、回收策略、绑定模式的实际含义
- StatefulSet 的卷模板与数据保留
- PVC 一直 Pending 的排查路径
为什么需要卷¶
容器的文件系统是临时的。Pod 重建后,容器层的数据全部丢失。Kubernetes 用卷(Volume)把存储的生命周期从容器中解耦:
| 存储 | 生命周期 | 适合 |
|---|---|---|
| 容器文件系统 | 容器重启即丢 | 临时文件 |
emptyDir |
Pod 删除即丢 | 容器间共享、缓存 |
hostPath |
跟节点走 | 节点级代理、实验 |
| NFS | 独立于 Pod | 多 Pod 共享 |
| PVC | 独立于 Pod,可动态供给 | 数据库、业务数据 |
flowchart LR
P[Pod] --> V[Volume]
V --> E[emptyDir]
V --> H[hostPath]
V --> N[NFS]
V --> PVC[PVC]
PVC -->|绑定| PV[PV]
PV --> SC[StorageClass]
SC -->|动态创建| CSI[CSI Driver]
CSI --> D[(云盘 / 本地盘 / NFS)]
图:Pod 通过 volume 使用存储;PVC 是「存储申请」,PV 是「存储资源」。
临时卷¶
emptyDir¶
Pod 启动时创建,Pod 删除时销毁。适合同一 Pod 内多容器共享文件、临时缓存。
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
initContainers:
- name: prepare
image: busybox:1.36
command: ["sh", "-c", "echo hello > /data/index.html"]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: web
image: nginx:1.27
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
emptyDir:
medium: Memory
sizeLimit: 16Mi
medium: Memory 使用 tmpfs,速度快但占内存;sizeLimit 防止写满节点。
hostPath¶
把节点上的目录挂进 Pod。适合 DaemonSet 类型的节点代理,不适合业务数据。
风险:
- Pod 漂移到别的节点后,看不到原数据;
- 容器可能读写节点敏感目录;
- 多 Pod 并发写同一目录容易冲突。
PV / PVC / StorageClass¶
三个角色¶
| 对象 | 谁创建 | 作用 |
|---|---|---|
| PV | 管理员 / 动态供给 | 一块实际存储资源 |
| PVC | 应用开发者 | 对存储的申请(大小、访问模式) |
| StorageClass | 管理员 | 定义「如何动态创建 PV」 |
绑定流程¶
- 用户创建 PVC,声明
storageClassName、accessModes、resources.requests.storage; - 控制器寻找匹配的 PV,或调用 StorageClass 的 provisioner 动态创建;
- 绑定成功后,PVC 变为
Bound; - Pod 引用 PVC,kubelet 调用 CSI 把卷挂载到容器。
访问模式¶
| 模式 | 缩写 | 含义 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写 |
| ReadWriteOncePod | RWOP | 单 Pod 读写(较新) |
RWO 是「单节点」,不是「单 Pod」。同一节点上的多个 Pod 可以同时挂载同一个 RWO 卷(取决于驱动)。
回收策略¶
| 策略 | 行为 |
|---|---|
Delete |
删除 PVC 时删除后端存储(动态供给默认) |
Retain |
保留 PV 与数据,需手工清理 |
生产环境的重要数据建议 Retain,避免误删 PVC 导致数据丢失。
绑定模式¶
Immediate:PVC 创建后立即绑定,可能绑到不满足 Pod 调度条件的节点;WaitForFirstConsumer:等 Pod 调度后再绑定,适合本地盘和拓扑受限的存储。
静态供给:NFS 示例¶
先创建一个 NFS PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-demo
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.10.20
path: /data/k8s/pv-nfs-demo
创建 PVC 绑定它:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-demo
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: ""
storageClassName: "" 表示不使用动态供给,只匹配静态 PV。
Pod 挂载:
动态供给:StorageClass¶
以 NFS CSI 为例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.nfs.svc.cluster.local
share: /
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
创建 PVC 时指定:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
storageClassName: nfs-csi
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
预期 PVC Bound,并自动出现一个对应的 PV。
设置默认 StorageClass
kubectl patch storageclass nfs-csi -p \
'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
集群只能有一个默认 StorageClass。PVC 不写 storageClassName 时使用默认值。
StatefulSet 与卷模板¶
StatefulSet 用 volumeClaimTemplates 为每个副本创建独立 PVC:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: nfs-csi
resources:
requests:
storage: 10Gi
sts-demo-0→ PVCdata-sts-demo-0sts-demo-1→ PVCdata-sts-demo-1
缩容时 PVC 默认保留,扩容后能重新挂回。需要自动删除时配置:
扩容与快照¶
扩容¶
前提:StorageClass 设置 allowVolumeExpansion: true。
kubectl patch pvc app-data -p \
'{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
kubectl get pvc app-data -w
文件系统扩容通常需要 Pod 重启或 CSI 支持在线扩容。
快照¶
需要 VolumeSnapshotClass、VolumeSnapshot CRD 与支持快照的 CSI:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: app-data-snapshot
spec:
volumeSnapshotClassName: nfs-csi-snapclass
source:
persistentVolumeClaimName: app-data
NFS CSI 的快照能力取决于后端;块存储通常支持得更完整。
实验:动态 PVC + 数据持久化¶
- 确认有默认 StorageClass:
- 创建 PVC:
- 创建 Pod 写入数据:
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: writer
image: busybox:1.36
command: ["sh", "-c", "echo persisted > /data/hello.txt; sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
- 删除 Pod,重建一个 reader:
kubectl delete pod writer
kubectl run reader --image=busybox:1.36 --restart=Never --overrides='
{"spec":{"volumes":[{"name":"data","persistentVolumeClaim":{"claimName":"app-data"}}],
"containers":[{"name":"reader","image":"busybox:1.36","command":["cat","/data/hello.txt"],
"volumeMounts":[{"name":"data","mountPath":"/data"}]}]}}'
预期仍然输出 persisted,证明数据独立于 Pod。
排错表¶
| 现象 | 原因 | 处理 |
|---|---|---|
PVC 一直 Pending |
没有匹配 PV / SC 不存在 / 容量不足 | describe pvc 看 Events |
PVC Pending 且 SC 是 WaitForFirstConsumer |
还没有 Pod 使用它 | 创建 Pod 后再看 |
Pod FailedMount |
NFS 不可达 / Secret 缺失 / CSI 未就绪 | describe pod,检查 CSI Pod |
Multi-Attach error |
RWO 卷被另一个节点占用 | 等旧 Pod 完全终止,或换 RWX |
| 删除 PVC 后数据还在 | 回收策略是 Retain |
手工清理 PV 与后端目录 |
| 扩容失败 | SC 不允许扩容 / 驱动不支持 | 检查 allowVolumeExpansion 与 CSI 能力 |
| 写入很慢 | NFS 网络 / 后端磁盘瓶颈 | 测网络与 IOPS,考虑本地盘 |
进阶挑战¶
- 创建一个
WaitForFirstConsumer的本地 PV,观察 PVC 在 Pod 调度前一直 Pending。 - 把 PVC 从 2Gi 扩到 5Gi,验证文件系统是否真的变大。
- 用
Retain策略删除 PVC,手工恢复 PV 并重新绑定。 - 用
kubectl get pv -o custom-columns列出所有 PV 的回收策略与绑定状态。 - 给 StatefulSet 配
persistentVolumeClaimRetentionPolicy,观察缩容时 PVC 是否被删除。
小结¶
- 临时数据用 emptyDir,节点级数据用 hostPath,共享数据用 NFS/PVC。
- PVC 是申请,PV 是资源,StorageClass 决定如何动态创建 PV。
- RWO 是节点级限制;需要多节点读写时选 RWX。
- 数据安全三件事:回收策略、备份、恢复演练。
延伸阅读¶
- 存储卷
- Persistent Volume
- CSI 规范
- 原仓库示例:examples/volumes
- 下一章:服务与网络