k8s 存储管理

导读
容器文件系统随 Pod 销毁而消失,有状态应用需要数据持久化才能正常运行。K8s 通过 Volume 机制把外部存储挂载进容器,又以 PV/PVC/StorageClass 三层抽象解耦存储供给与消费,让应用开发者无需关心底层存储实现。本篇梳理 Volume 类型与持久化抽象、静态与动态供给差异、Pod 挂载全流程,以及 CSI 插件机制与主流存储后端。
核心概念
Volume 是 Pod 中可供容器访问的目录,生命周期与 Pod 绑定。emptyDir 在 Pod 调度到节点时创建,初始为空,Pod 内所有容器可共享读写,Pod 删除后数据一并清除,适合临时缓存、多容器文件交换等场景。hostPath 把节点上的文件或目录直接挂载进 Pod,数据在 Pod 销毁后仍保留在节点上,但 Pod 漂移到其他节点时无法访问原有数据,仅适用于 DaemonSet 或需要访问节点文件系统的特殊场景。
configMap 和 secret 作为特殊的 Volume 类型,把 API 对象中的数据以文件形式注入容器。configMap 挂载后每个 key 成为一个文件,内容为对应的 value,适合注入配置文件;secret 与 configMap 结构一致,但数据经过 base64 编码,且 tmpfs 挂载不落盘,用于传递敏感信息如密码、证书、令牌。这两种 Volume 都是只读的,由 kubelet 定时同步更新。
持久化存储通过三层抽象实现解耦。PersistentVolume(PV) 是集群中的一块存储资源,由管理员预先创建或由 StorageClass 动态供给,生命周期独立于 Pod,包含容量、访问模式、回收策略等属性。PersistentVolumeClaim(PVC) 是用户对存储的申请,声明所需容量、访问模式和 StorageClass 名称,控制面自动寻找匹配的 PV 并绑定。StorageClass 定义存储的类型和供给参数,指向一个 Provisioner,由它负责在存储后端创建实际卷并生成 PV 对象。
图 1:PV / PVC / StorageClass 三层抽象与 Pod 的绑定关系
图 1 展示了从 Pod 到实际存储的完整层次。Pod 通过 volumeMounts 把 Volume 挂到容器内路径,Volume 再通过 persistentVolumeClaim 字段引用 PVC,PVC 与 PV 一一绑定,PV 则指向底层真实存储。StorageClass 不直接参与挂载链路,它是 PV 的"模板工厂",负责按 PVC 的规格动态生成 PV 和对应后端卷。这种分层使得应用只需声明 PVC,存储管理员只需维护 StorageClass,双方职责清晰。
访问模式描述卷的挂载方式,ReadWriteOnce 表示仅可被单个节点以读写方式挂载(同一节点上的多个 Pod 可共享),ReadOnlyMany 表示可被多个节点以只读方式挂载,ReadWriteMany 表示可被多个节点同时读写。并非所有存储后端都支持全部模式,例如块存储通常只支持 ReadWriteOnce,文件存储如 NFS、CephFS 支持 ReadWriteMany。
图解原理
静态供给由管理员预先创建一批 PV,用户提交 PVC 后,控制面的 PersistentVolumeController 在现有 PV 中查找容量和访问模式匹配的对象,找到则将二者绑定。动态供给则无需提前创建 PV,当 PVC 指定了 StorageClass 且没有匹配的现成 PV 时,StorageClass 对应的 Provisioner 会自动在存储后端创建卷,并据此生成一个新的 PV 对象,再与 PVC 完成绑定。动态供给大幅减少了管理员的手动操作,是生产环境的主流方式。
图 2:动态供给的请求与创建链路
图 2 描述了从 PVC 提交到 Pod 挂载的完整流程。用户提交 PVC 后,控制面先尝试匹配已有 PV,若未命中则走动态供给路径:根据 storageClassName 找到 StorageClass,调用其 Provisioner 在后端创建实际卷,再自动生成 PV 对象,随后 PVC 与新 PV 绑定。Pod 启动时,kubelet 根据 PVC 找到对应的 PV,调用存储插件把实际卷挂载到节点的全局路径,再通过 bind mount 映射进容器的指定目录。
回收策略决定 PVC 删除后 PV 的去向。Retain 保留 PV 和后端数据,需管理员手动清理,适合重要数据;Delete 在 PVC 删除时同时删除 PV 和后端存储卷,由动态供给的 StorageClass 默认使用,适合临时或可重建的数据;Recycle 已废弃,其行为是对卷执行 rm -rf 后重新可用,新的存储实现应优先使用动态供给替代。
Pod 挂载卷的过程分两步。Attach 阶段由控制面的 AttachDetachController 执行,把存储卷附加到 Pod 所在节点(对块存储而言就是 attach 磁盘,对文件存储则跳过此步)。Mount 阶段由 kubelet 中的 volume manager 执行,把已附加的卷格式化(按需)并挂载到节点的 /var/lib/kubelet/pods/<pod-uid>/volumes/ 目录,再 bind mount 进每个容器的指定路径。卸载时按相反顺序执行 unmount 和 detach。
CSI 插件机制与常见存储后端
早期 K8s 的存储插件都写在核心代码库中,新增存储支持需要发版,迭代缓慢。CSI(Container Storage Interface) 定义了一套标准接口,把存储供给、挂载、快照等能力从 K8s 核心解耦,存储厂商只需实现 CSI Driver 即可接入 K8s,无需修改主干代码。CSI Driver 通常以 StatefulSet 或 DaemonSet 形式部署在集群中,包含 controller 组件(负责卷的创建/删除/快照等控制面操作)和 node 组件(负责节点上的挂载/卸载)。
常见存储后端各有适用场景。NFS 部署简单、支持 ReadWriteMany,适合轻量共享存储,但性能和可靠性依赖底层网络和 NFS 服务器,生产环境需做高可用。Ceph 提供块存储(RBD)、文件存储(CephFS)和对象存储(RGW)三种接口,支持动态供给和快照,可靠性高、扩展性好,是自建集群的主流选择,运维复杂度也相对较高。云厂商块存储(AWS EBS、阿里云云盘、腾讯云 CBS 等)通过各自的 CSI Driver 接入,支持动态供给、快照、扩容,与云主机亲和性好,适合云上生产环境。Local Persistent Volume 直接使用节点本地磁盘,延迟最低、性能最好,但 Pod 被调度到其他节点时数据不可用,适合对延迟敏感且可接受节点级故障的有状态应用,需配合节点亲和性使用。
动手实操
以下示例在集群中演示静态 PV 创建、PVC 绑定与 Pod 挂载,随后配置 NFS StorageClass 实现动态供给。操作前确认集群节点可访问存储后端,使用 NFS 时需在节点安装 nfs-utils 或 nfs-common 包。
先创建一个基于 NFS 的静态 PV,容量 5Gi,访问模式 ReadWriteMany,回收策略 Retain。server 和 path 替换为实际 NFS 服务器地址和共享路径。
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-5g
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-static
nfs:
server: 192.168.1.100
path: /data/k8s-pv提交后查看 PV 状态,STATUS 应为 Available,表示尚未被任何 PVC 绑定。
kubectl apply -f nfs-pv.yaml
kubectl get pv nfs-pv-5g接着创建 PVC,申请 2Gi 空间,指定 storageClassName 为 nfs-static,访问模式与 PV 一致即可触发绑定。PVC 的容量不超过 PV 容量、访问模式兼容,且 storageClassName 相同,控制面就会自动完成绑定。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
storageClassName: nfs-static提交后观察 PV 和 PVC 的状态变化。PV 的 STATUS 从 Available 变为 Bound,PVC 的 STATUS 也变为 Bound,二者一一对应。
kubectl apply -f pvc.yaml
kubectl get pvc data-pvc
kubectl get pv nfs-pv-5g创建 Pod 引用该 PVC,通过 volumeMounts 挂载到容器的 /data 目录。Pod 内写入 /data 的文件会持久化到 NFS 后端,Pod 删除重建后数据仍然存在。
apiVersion: v1
kind: Pod
metadata:
name: storage-demo
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc进入容器写入测试文件,删除 Pod 后重建,验证数据仍然存在。
kubectl apply -f pod-pvc.yaml
kubectl exec storage-demo -- sh -c 'echo "hello storage" > /data/test.txt'
kubectl delete pod storage-demo
kubectl apply -f pod-pvc.yaml
kubectl exec storage-demo -- cat /data/test.txt接下来演示动态供给。部署 NFS CSI Driver 后,创建一个 StorageClass,指向 NFS Provisioner。provisioner 字段的值需与实际部署的 CSI Driver 名称一致,parameters 中的 server 和 share 替换为真实值。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
share: /data/k8s-dynamic
reclaimPolicy: Delete
volumeBindingMode: Immediate创建一个新的 PVC,指定 storageClassName: nfs-csi,无需手动创建 PV。提交后观察,Provisioner 会自动在 NFS 后端创建子目录并生成对应 PV 对象,PVC 自动变为 Bound 状态。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi查看自动生成的 PV,其名称由系统随机生成,容量和访问模式与 PVC 一致。删除 PVC 后,根据 reclaimPolicy: Delete,PV 和后端子目录会被一并清理。
kubectl get pvc dynamic-pvc
kubectl get pv | grep dynamic-pvc把 StorageClass 设为默认类后,PVC 不指定 storageClassName 也会自动使用该类。默认类通过 annotation storageclass.kubernetes.io/is-default-class: "true" 标记,一个集群中只应有一个默认 StorageClass。
kubectl patch storageclass nfs-csi -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl get storageclass常见问题与避坑
PVC 一直 Pending 先查 StorageClass 是否存在。PVC 指定的 storageClassName 在集群中不存在,或对应的 Provisioner 未部署,动态供给就无法触发,PVC 永远停在 Pending。执行 kubectl describe pvc <name> 查看 Events,再用 kubectl get sc 确认 StorageClass 列表。
块存储的 ReadWriteOnce 不是单 Pod 读写。ReadWriteOnce 限制的是"单个节点"而非"单个 Pod",同一节点上的多个 Pod 可以同时挂载并写入。若需要严格的单实例写入,需在应用层面加锁,或使用 StatefulSet 的 volumeClaimTemplates 配合有序部署。
删除 PVC 前确认 reclaimPolicy 是否可接受数据丢失。动态供给的 StorageClass 默认 reclaimPolicy: Delete,PVC 删除后 PV 和后端数据都会被清除。重要数据应把 StorageClass 的 reclaimPolicy 改为 Retain,或在删除 PVC 前先备份数据。
小结与进阶
PV/PVC/StorageClass 三层抽象把存储的供给与消费彻底解耦,动态供给免去了管理员手动预配的负担,CSI 机制则让存储接入标准化。理解 Volume 类型的适用场景、绑定规则与挂载流程,是排查有状态应用存储问题的基础。下一篇进入配置与密钥管理,拆解 ConfigMap 与 Secret 的多种注入方式及安全最佳实践。