2.7. 存储与持久化
Pod 随时会被删除、重建、漂移,容器里的文件也跟着消失。存储卷要解决的就是:哪些数据应该跟着 Pod 走,哪些应该独立于 Pod 存在。
- 层级:
L2 · 核心
- 难度:
进阶
- 预计:
55 分钟
- 前置:
Pod、工作负载
2.7.1. 你将学到
emptyDir、hostPath、NFS、PVC 的适用场景与边界
PV / PVC / StorageClass 的绑定流程
静态供给与动态供给的差异
访问模式、回收策略、绑定模式的实际含义
StatefulSet 的卷模板与数据保留
PVC 一直 Pending 的排查路径
2.7.2. 为什么需要卷
容器的文件系统是临时的。Pod 重建后,容器层的数据全部丢失。Kubernetes 用卷(Volume)把存储的生命周期从容器中解耦:
存储 |
生命周期 |
适合 |
|---|---|---|
容器文件系统 |
容器重启即丢 |
临时文件 |
|
Pod 删除即丢 |
容器间共享、缓存 |
|
跟节点走 |
节点级代理、实验 |
NFS |
独立于 Pod |
多 Pod 共享 |
PVC |
独立于 Pod,可动态供给 |
数据库、业务数据 |
图:Pod 通过 volume 使用存储;PVC 是「存储申请」,PV 是「存储资源」。
2.7.3. 临时卷
2.7.3.1. 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 防止写满节点。
2.7.3.2. hostPath
把节点上的目录挂进 Pod。适合 DaemonSet 类型的节点代理,不适合业务数据。
volumes:
- name: host-data
hostPath:
path: /var/lib/app-data
type: DirectoryOrCreate
风险:
Pod 漂移到别的节点后,看不到原数据;
容器可能读写节点敏感目录;
多 Pod 并发写同一目录容易冲突。
2.7.4. PV / PVC / StorageClass
2.7.4.1. 三个角色
对象 |
谁创建 |
作用 |
|---|---|---|
PV |
管理员 / 动态供给 |
一块实际存储资源 |
PVC |
应用开发者 |
对存储的申请(大小、访问模式) |
StorageClass |
管理员 |
定义「如何动态创建 PV」 |
2.7.4.2. 绑定流程
用户创建 PVC,声明
storageClassName、accessModes、resources.requests.storage;控制器寻找匹配的 PV,或调用 StorageClass 的 provisioner 动态创建;
绑定成功后,PVC 变为
Bound;Pod 引用 PVC,kubelet 调用 CSI 把卷挂载到容器。
kubectl get pv,pvc -A
kubectl describe pvc <name> -n <ns>
2.7.4.3. 访问模式
模式 |
缩写 |
含义 |
|---|---|---|
ReadWriteOnce |
RWO |
单节点读写 |
ReadOnlyMany |
ROX |
多节点只读 |
ReadWriteMany |
RWX |
多节点读写 |
ReadWriteOncePod |
RWOP |
单 Pod 读写(较新) |
RWO 是「单节点」,不是「单 Pod」。同一节点上的多个 Pod 可以同时挂载同一个 RWO 卷(取决于驱动)。
2.7.4.4. 回收策略
策略 |
行为 |
|---|---|
|
删除 PVC 时删除后端存储(动态供给默认) |
|
保留 PV 与数据,需手工清理 |
生产环境的重要数据建议 Retain,避免误删 PVC 导致数据丢失。
2.7.4.5. 绑定模式
volumeBindingMode: WaitForFirstConsumer
Immediate:PVC 创建后立即绑定,可能绑到不满足 Pod 调度条件的节点;WaitForFirstConsumer:等 Pod 调度后再绑定,适合本地盘和拓扑受限的存储。
2.7.5. 静态供给: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 挂载:
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-nfs-demo
2.7.6. 动态供给: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
kubectl apply -f pvc-dynamic.yaml
kubectl get pvc app-data -w
kubectl get pv | grep app-data
预期 PVC Bound,并自动出现一个对应的 PV。
小技巧
设置默认 StorageClass
kubectl patch storageclass nfs-csi -p \
'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
集群只能有一个默认 StorageClass。PVC 不写 storageClassName 时使用默认值。
2.7.7. 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 默认保留,扩容后能重新挂回。需要自动删除时配置:
persistentVolumeClaimRetentionPolicy:
whenScaled: Delete
whenDeleted: Delete
2.7.8. 扩容与快照
2.7.8.1. 扩容
前提:StorageClass 设置 allowVolumeExpansion: true。
kubectl patch pvc app-data -p \
'{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
kubectl get pvc app-data -w
文件系统扩容通常需要 Pod 重启或 CSI 支持在线扩容。
2.7.8.2. 快照
需要 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 的快照能力取决于后端;块存储通常支持得更完整。
2.7.9. 实验:动态 PVC + 数据持久化
确认有默认 StorageClass:
kubectl get storageclass
创建 PVC:
kubectl apply -f pvc-dynamic.yaml
kubectl get pvc app-data
创建 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
kubectl apply -f writer.yaml
kubectl exec writer -- cat /data/hello.txt
删除 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。
2.7.10. 排错表
现象 |
原因 |
处理 |
|---|---|---|
PVC 一直 |
没有匹配 PV / SC 不存在 / 容量不足 |
|
PVC |
还没有 Pod 使用它 |
创建 Pod 后再看 |
Pod |
NFS 不可达 / Secret 缺失 / CSI 未就绪 |
|
|
RWO 卷被另一个节点占用 |
等旧 Pod 完全终止,或换 RWX |
删除 PVC 后数据还在 |
回收策略是 |
手工清理 PV 与后端目录 |
扩容失败 |
SC 不允许扩容 / 驱动不支持 |
检查 |
写入很慢 |
NFS 网络 / 后端磁盘瓶颈 |
测网络与 IOPS,考虑本地盘 |
2.7.11. 进阶挑战
创建一个
WaitForFirstConsumer的本地 PV,观察 PVC 在 Pod 调度前一直 Pending。把 PVC 从 2Gi 扩到 5Gi,验证文件系统是否真的变大。
用
Retain策略删除 PVC,手工恢复 PV 并重新绑定。用
kubectl get pv -o custom-columns列出所有 PV 的回收策略与绑定状态。给 StatefulSet 配
persistentVolumeClaimRetentionPolicy,观察缩容时 PVC 是否被删除。
2.7.12. 小结
临时数据用 emptyDir,节点级数据用 hostPath,共享数据用 NFS/PVC。
PVC 是申请,PV 是资源,StorageClass 决定如何动态创建 PV。
RWO 是节点级限制;需要多节点读写时选 RWX。
数据安全三件事:回收策略、备份、恢复演练。
2.7.13. 延伸阅读
原仓库示例:examples/volumes
下一章:服务与网络