容器重启数据就没了:为什么要持久化
容器的文件系统是临时的:容器一旦被删除重建,里面写的数据就跟着没了。Pod 被调度到别的节点、滚动发布换了新 Pod、节点故障被重新拉起——这些在 K8s 里再正常不过的操作,都会让"存在容器里"的数据消失。日志、上传文件、数据库数据、缓存,凡是需要跨 Pod 生命周期存活的东西,都必须放在持久化存储里。
理解 K8s 存储,先记住一个核心模型:Pod 使用 Volume(数据卷),数据卷背后是真实的存储。Volume 把存储挂载进容器的某个目录,容器的读写落在真实的磁盘/云盘上,Pod 没了数据还在。接下来从最简单的 Volume 讲到生产级的 PV/PVC/CSI,一层层把"数据到底存在哪"讲清楚。
# 为什么容器数据会丢:容器生命周期 = 文件系统生命周期
kubectl delete pod <pod> # Pod 删除,容器文件系统一并删除
kubectl rollout restart deploy # 滚动发布,旧 Pod 被替换
# 结论:要跨生命周期保留的数据,必须放 Volume
- VolumePod 内挂载存储的抽象。
- PV/PVC把存储资源和申请解耦。
- StorageClass按需动态创建云盘。
- StatefulSet给有状态应用稳定的卷。
Volume 基础:emptyDir、hostPath 与局限
最简单的两个 Volume 类型是 emptyDir 和 hostPath。emptyDir 是 Pod 内的临时目录,跟随 Pod 生命周期,Pod 还在时容器间可共享,但 Pod 一删就没了——适合缓存、临时文件;hostPath 直接挂宿主机的目录,数据能跨 Pod 存活,但只能在本节点用,Pod 被调度到别的节点就找不到数据,而且多副本同时写同一目录会有冲突。
# emptyDir:临时共享目录(Pod 删除即消失)
# spec:
# template:
# spec:
# containers:
# - name: app
# volumeMounts:
# - name: cache
# mountPath: /var/cache
# volumes:
# - name: cache
# emptyDir: {}
hostPath 在单节点测试或日志采集(比如只读挂载宿主容器日志目录)里还有用,但生产多节点环境千万别拿它当业务数据存储。真正生产级的做法,是用后面讲的 PV/PVC 把存储和节点解耦,让数据跟着卷走而不是跟着节点走。
PV 与 PVC:把存储和 Pod 解耦
PV(PersistentVolume) 是集群里的一块真实存储资源,由管理员或存储系统提供;PVC(PersistentVolumeClaim) 是用户对存储的申请。Pod 只声明"我要一块 10Gi 的读写卷",PVC 负责去匹配或创建 PV,应用不用关心存储到底来自哪块盘。这就是"存储供给"和"存储使用"的解耦。
# PV:管理员提供一块 NFS 存储(示意)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-10g
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteMany"]
nfs:
server: 192.168.1.10
path: /data/k8s
# PVC:应用申请 5Gi,读写模式与 PV 匹配则绑定
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 5Gi
PV 还有个关键属性——回收策略:PVC 删除后,PV 是 Retain(保留数据,人工处理)、Delete(连数据一起删)还是 Recycle(清空后复用)。生产环境对重要数据一般用 Retain,宁可让运维手动确认,也别让系统顺手把数据抹了。
StorageClass 与动态供给
手写 PV 的静态供给要运维提前备盘,麻烦且浪费。有了 StorageClass,PVC 可以直接声明"给我一块 SSD 云盘",集群里的 Provisioner(供给器) 自动去云厂商创建磁盘并生成 PV——这叫动态供给,用完释放后按策略删除,容量按需分配,最省心。
# StorageClass:让 PVC 动态创建云盘(示意)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs # 或对应云厂商 provisioner
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
# PVC 指定 storageClassName 即可动态创建
# spec:
# storageClassName: fast-ssd
# resources:
# requests:
# storage: 20Gi
两个容易被忽略的点:reclaimPolicy 决定 PVC 删掉后云盘是保留还是删除,误配 Delete 会把重要数据一起删掉;默认 StorageClass 会被不带 storageClassName 的 PVC 隐式使用,线上集群要明确哪个是默认,别让应用"随手"申请到不合适的存储类型。
CSI:云盘与对象存储接入
CSI(Container Storage Interface) 是 K8s 和存储系统之间的标准接口:云厂商/存储厂商写一个 CSI 插件,集群就能统一挂载它的云盘、文件存储、对象存储,还支持卷快照、扩容、克隆这些扩展能力。现在主流云厂商(阿里云、腾讯云、AWS、GCP 等)都提供官方 CSI 驱动,开箱即用。
# 安装云厂商 CSI 驱动后,自定义 StorageClass(示意)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-essd
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd
performanceLevel: PL1
reclaimPolicy: Retain
# 卷快照:备份前拍快照(示意)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: app-data-snap
spec:
volumeSnapshotClassName: csi-snap-class
source:
persistentVolumeClaimName: app-data
接入 CSI 后,备份容灾就多了一层利器:定期给关键 PVC 拍卷快照,出问题时快速回滚;配合对象存储做异地备份,数据安全从"靠运气"变成"有兜底"。这也是前面讲的备份容灾体系在 K8s 里的落地形态。
StatefulSet 与有状态应用
数据库、消息队列这类有状态应用不能随便换 Pod——每个实例要稳定的网络标识、要按顺序启动、每份数据要绑定自己的卷。这些正是 StatefulSet 提供的:稳定的名字(如 db-0、db-1)、有序的部署/删除、以及最重要的 volumeClaimTemplates——每个副本自动获得一块独立的 PVC。
# StatefulSet 卷模板:每个副本一块独立数据卷(示意)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db-svc
replicas: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
template:
spec:
containers:
- name: db
volumeMounts:
- name: data
mountPath: /var/lib/data
注意 ReadWriteOnce:同一块卷只能被一个节点挂载,保证数据库实例独占自己的数据。扩容副本数时,StatefulSet 会为每个新副本创建对应 PVC;缩容要谨慎,副本删了 PVC 默认还在,防止误删数据——确认无价值后再手动清理。
存储排障与最佳实践
存储相关的故障大多长一个样:Pod 卡在 ContainerCreating。原因通常是 PVC 没绑定、CSI 插件异常、挂载失败或权限不对。排查时先看事件:describe Pod 里 FailedMount 会直接说出原因;再逐层看 PVC 状态、StorageClass、CSI 组件日志。
# 存储排障组合拳
kubectl get pvc -A # PVC 是否 Bound
kubectl describe pvc <pvc> -n <ns> # 未绑定原因(waiting for provisioner 等)
kubectl describe pod <pod> -n <ns> # 看 FailedMount 事件
kubectl get pods -n kube-system | grep csi # CSI 组件是否健康
最后给几条生产实践:重要数据用 Retain 回收策略并定期拍快照;按性能需求选 StorageClass(SSD 给数据库,普通盘给日志),别一个类走天下;StatefulSet 缩容前先确认数据;监控 PV 使用率,磁盘写满和高可用故障一样是生产事故的高发源。存储做好,配合数据库高可用和异地多活,整条数据链路才算稳。