摘要:梳理 Volume、emptyDir、hostPath、PV、PVC 与 StorageClass,理解动态供给、访问模式、回收策略和 StatefulSet 的存储关联。
这一阶段学习 Kubernetes 如何让容器保存数据、共享文件,以及在 Pod 被删除或重新创建后继续使用原来的数据。
核心知识点:Volume、emptyDir、hostPath、PV、PVC、StorageClass、动态供给,以及 StatefulSet 的持久化存储。
一、为什么容器需要持久化存储?
假设你部署了一个数据库容器:
flowchart TB
N0["Pod"]
N1["MySQL 容器"]
N2["/var/lib/mysql"]
N0 --> N1
N1 --> N2
数据库把数据写入容器的可写层。当容器被删除并重新创建时,原来的容器可写层不会自动保留。
因此,数据库不能仅依赖容器自身的文件系统保存需要长期保留的数据。
Kubernetes 通过 Volume 将存储挂载到容器中。
flowchart TB
N0["Pod<br/>MySQL 容器<br/>挂载路径:/var/lib/mysql"]
N1["Volume<br/>把存储暴露给容器使用"]
N2["实际存储<br/>节点磁盘、网络存储、云盘或分布式存储"]
N0 -->|"Mount(挂载)"| N1
N1 -->|"关联 / 使用"| N2
这里有一个关键概念:
Volume 是 Pod 使用存储的接口;它本身不一定意味着数据已经持久化到独立存储设备。
例如,emptyDir 通常只在 Pod 生命周期内保留数据,而持久卷可以在 Pod 替换后继续提供原有数据。
二、Volume:Pod 的存储卷
Volume 是 Pod 层级的存储配置。一个 Pod 可以声明多个 Volume,并将它们挂载到一个或多个容器中。
1. emptyDir:临时共享目录
emptyDir 在 Pod 被分配到节点后创建,Pod 在该节点上的生命周期内可以使用它。
典型用途:
- 多个容器共享文件。
- 保存临时缓存。
- 存储中间计算结果。
- 让 Sidecar 容器读取主容器产生的文件。
示例:
apiVersion: v1kind: Podmetadata: name: emptydir-demospec: containers: - name: writer image: busybox:1.36 command: ["sh", "-c", "while true; do date >> /data/time.txt; sleep 5; done"] volumeMounts: - name: shared-data mountPath: /data
- name: reader image: busybox:1.36 command: ["sh", "-c", "sleep 3600"] volumeMounts: - name: shared-data mountPath: /data
volumes: - name: shared-data emptyDir: {}需要注意:
- 同一个 Pod 内的容器可以共享这个目录。
- 单纯重启容器时,
emptyDir通常仍然保留。 - Pod 被删除后,
emptyDir的数据会随该 Pod 的生命周期结束而被清理。 - 如果使用
emptyDir.medium: Memory,数据会存放在内存中,并计入相关内存资源管理。
因此,emptyDir 适合临时数据,不适合保存需要长期保留的数据库文件。
2. hostPath:直接挂载节点目录
hostPath 将节点文件系统中的路径挂载到 Pod 内。
volumes: - name: host-data hostPath: path: /data/app type: DirectoryOrCreate容器可以把这个卷挂载到 /app/data,实际数据位于当前 Node 的 /data/app。
它的主要风险是:
- Pod 被调度到其他节点时,访问的可能是另一份目录。
- 节点上的文件权限和目录内容可能影响容器运行。
- 容器可能接触到节点的敏感文件。
- 节点故障时,数据可能无法访问。
hostPath 常用于特定节点级应用、日志采集等场景,但不应简单地将它当作通用的生产持久化存储方案。
三、PV 与 PVC:把存储和应用解耦
这是 Kubernetes 存储最核心的部分。
- PV(PersistentVolume):集群中可供使用的持久卷资源。
- PVC(PersistentVolumeClaim):应用对存储容量、访问模式等提出的申请。
可以把它理解为:
- PV 是可供使用的存储资源。
- PVC 是应用提交的存储申请。
- Pod 通过 PVC 使用相应的存储。
flowchart TB
N0["Pod<br/>容器通过 volumeMounts 挂载存储"]
N1["PVC:存储申请<br/>申请 5Gi、指定访问模式等"]
N2["PV:持久卷<br/>与符合条件的 PVC 绑定"]
N3["底层存储<br/>例如网络文件系统、云盘或分布式存储"]
N0 -->|"引用"| N1
N1 <-->|"绑定"| N2
N2 -->|"关联 / 使用"| N3
1. 创建一个静态 PV
下面是一个教学示例,使用 hostPath 作为底层存储,适合单节点实验环境。
apiVersion: v1kind: PersistentVolumemetadata: name: demo-pvspec: capacity: storage: 1Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: manual hostPath: path: /mnt/data/demo-pv type: DirectoryOrCreate说明:
capacity.storage:声明卷的容量。accessModes:声明卷支持的访问模式。storageClassName:指定存储类别。persistentVolumeReclaimPolicy:定义 PVC 释放后 PV 的回收策略。Retain:释放后保留底层存储资源,通常需要管理员手动处理。
重要: hostPath PV 只是教学示例,不具备自动跨节点迁移、数据复制或高可用能力。生产环境应选用符合业务要求的存储后端。
2. 创建 PVC
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: demo-pvcspec: accessModes: - ReadWriteOnce storageClassName: manual resources: requests: storage: 1GiPVC 会申请一个容量至少满足需求、访问模式兼容且存储类别匹配的卷。
创建后查看:
kubectl apply -f pv.yamlkubectl apply -f pvc.yaml
kubectl get pvkubectl get pvc正常情况下,符合条件的 PV 与 PVC 会绑定,状态显示为 Bound。
如果 PVC 一直处于 Pending,需要检查是否有合适的 PV、访问模式是否匹配、存储类别是否一致,以及容量是否满足要求。
3. 在 Pod 中使用 PVC
apiVersion: v1kind: Podmetadata: name: persistent-demospec: containers: - name: app image: busybox:1.36 command: ["sh", "-c", "echo hello > /data/hello.txt; sleep 3600"] volumeMounts: - name: app-data mountPath: /data volumes: - name: app-data persistentVolumeClaim: claimName: demo-pvc即使删除这个 Pod,再创建一个引用同一个 PVC 的 Pod,数据通常仍然可以保留,前提是底层存储没有被删除或损坏,且新 Pod 能够正常挂载该存储。
四、StorageClass:动态创建存储
如果每个应用都要求管理员提前手动创建 PV,维护成本会很高。
StorageClass 用于描述一种存储类型,并可以与支持动态供给的存储插件配合,在 PVC 申请时自动创建底层存储卷。
例如:
flowchart TB
N0["应用提交 PVC"]
N1["指定 StorageClass"]
N2["存储插件动态创建卷"]
N3["PV 与 PVC 绑定"]
N4["Pod 挂载存储"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4
常见配置:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: dynamic-pvcspec: accessModes: - ReadWriteOnce storageClassName: fast-storage resources: requests: storage: 10Gi这里的 fast-storage 只是示例名称,需要集群中存在对应的 StorageClass,并配置可用的存储供给程序。
查看集群存储类别:
kubectl get storageclasskubectl get sc注意:
- 创建 StorageClass 对象本身不会自动实现存储功能。
- 必须有兼容的动态供给程序和底层存储。
volumeBindingMode: WaitForFirstConsumer可以让部分存储在考虑 Pod 调度位置后再进行绑定或供给。
五、访问模式与回收策略
1. 访问模式
| 模式 | 含义 |
|---|---|
ReadWriteOnce(RWO) | 可由单个节点以读写方式挂载;同一节点上的多个 Pod 仍可能共享 |
ReadOnlyMany(ROX) | 可由多个节点只读挂载 |
ReadWriteMany(RWX) | 可由多个节点读写挂载 |
ReadWriteOncePod(RWOP) | 限制为单个 Pod 以读写方式使用卷,需满足相关 CSI 驱动与集群版本要求 |
这些是卷访问模式,不应简单理解为所有存储系统都能支持所有模式。实际支持范围取决于存储类型和驱动。
例如,普通云块存储经常支持 RWO,而共享文件存储可能支持 RWX。
2. 回收策略
| 策略 | 含义 |
|---|---|
Retain | PVC 释放后保留 PV 和底层数据,通常需要人工清理 |
Delete | 支持该策略的动态供给插件通常会删除对应存储资源 |
Recycle | 旧式回收机制,已弃用,不应作为新方案使用 |
删除 PVC 不一定意味着底层数据一定会保留,也不一定意味着它一定会被删除。需要结合 PV 回收策略、StorageClass 配置和存储插件行为判断。
六、StatefulSet 为什么经常和存储一起出现?
Deployment 适合大多数无状态应用,但数据库等有状态服务往往需要稳定的身份和独立的数据卷。
StatefulSet 可以为每个副本提供稳定的 Pod 身份,并通过 volumeClaimTemplates 为每个副本创建独立 PVC。
例如:
flowchart TB
N0["StatefulSet: mysql"]
N1["mysql-0"]
N2["mysql-1"]
N3["mysql-2"]
N4["PVC: data-mysql-0"]
N5["PVC: data-mysql-1"]
N6["PVC: data-mysql-2"]
N0 --> N1
N0 --> N2
N0 --> N3
N1 --> N4
N2 --> N5
N3 --> N6
每个副本有独立的存储声明。Pod 被替换后,StatefulSet 可以重新创建对应的 Pod,并让它重新使用原来的 PVC。
但 StatefulSet 本身并不会自动实现数据库复制、数据备份、故障切换或数据一致性,这些仍需要数据库和存储系统配合。
七、存储机制要点
emptyDir:适合 Pod 生命周期内的临时共享数据。PV/PVC:分离存储资源与应用的存储需求。StorageClass:定义存储类别,并可配合插件实现动态供给。StatefulSet:提供稳定身份和每副本存储声明。- 持久化不等于高可用,也不等于备份。