k8s(五)PV和PVC详解
文章目录
前言
在容器化技术飞速发展的今天,Kubernetes 已成为编排容器集群的事实标准。然而,容器天生的 “短暂性” 特性给数据持久化带来了巨大挑战 —— 容器重启后内部数据丢失、跨容器数据共享困难等问题,成为阻碍企业级应用大规模容器化部署的关键瓶颈。如何为容器提供稳定、可靠且灵活的存储支持,实现数据的安全持久化与高效共享,成为每一位 Kubernetes 运维与开发人员必须攻克的核心课题。
本文围绕 Kubernetes 容器存储展开,从基础问题出发,逐步深入讲解各类存储方案的原理、配置与实践。首先剖析容器存储的短暂性痛点,引出 Kubernetes Volume 抽象的核心作用;随后详细介绍 emptyDir、hostPath、NFS 等基础存储卷的特性与使用方法;再深入讲解 PV(Persistent Volume)与 PVC(Persistent Volume Claim)的持久化机制,厘清二者的生命周期与匹配规则;通过 NFS + PV + PVC 的实战案例,帮助读者掌握静态存储配置流程;最后介绍 StorageClass 动态存储方案,对比各类存储方案的优缺点并给出选型建议,为不同规模、不同场景下的容器存储需求提供完整的解决方案参考。
1. 容器存储的核心挑战与解决方案
1.1 容器存储的短暂性问题
容器的文件系统存在天然局限性:
- 容器崩溃重启后,内部数据会完全丢失
- 同一 Pod 内的多个容器无法直接实现文件共享
1.2 Kubernetes Volume 抽象
Kubernetes 通过 Volume 抽象 解决上述问题:
- 引入 Pause 容器作为 Pod 内所有容器的共享存储载体
- 实现跨容器的文件共享与数据持久化
- 支持多种存储后端,适应不同场景需求
2. 基础存储卷类型
2.1 emptyDir 存储卷
2.1.1 核心特点
- 随 Pod 调度到节点时自动创建
- 与 Pod 生命周期绑定,Pod 删除后数据随之销毁
- 仅适合临时缓存或容器间短期数据共享
2.1.2 配置示例
mkdir /opt/volumes
cd /opt/volumes
vim pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-emptydir
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
# 定义容器挂载内容
volumeMounts:
# 使用的存储卷名称,需与下方volumes字段name值匹配
- name: html
# 挂载至容器中哪个目录
mountPath: /usr/share/nginx/html/
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
# 在容器内定义挂载存储名称和挂载路径
mountPath: /data/
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
# 定义存储卷
volumes:
# 定义存储卷名称
- name: html
# 定义存储卷类型
emptyDir: {}
2.1.3 验证方法
kubectl apply -f pod-emptydir.yaml
# 查看Pod状态
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-emptydir 2/2 Running 0 36s 10.244.2.19 node02 <none> <none>
# 验证容器间共享效果
curl 10.244.2.19
Thu May 27 18:17:11 UTC 2021
Thu May 27 18:17:13 UTC 2021
...
2.2 hostPath 存储卷
2.2.1 核心特点
- 将节点(宿主机)上的目录直接挂载到容器
- 可实现数据持久化(不受 Pod 生命周期影响)
- 存在节点亲和性,节点故障会导致数据无法访问
2.2.2 配置示例
# 1. 在节点上创建挂载目录(所有可能调度的节点都需要)
# 在 node01 节点
mkdir -p /data/pod/volume1
echo 'node01.benet.com' > /data/pod/volume1/index.html
# 在 node02 节点
mkdir -p /data/pod/volume1
echo 'node02.benet.com' > /data/pod/volume1/index.html
# 2. 创建 Pod 资源
vim pod-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-hostpath
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
# 定义容器挂载内容
volumeMounts:
# 使用的存储卷名称,需与下方volumes字段name值匹配
- name: html
# 挂载至容器中哪个目录
mountPath: /usr/share/nginx/html
# 读写挂载方式,默认为读写模式false
readOnly: false
# volumes字段定义了paues容器关联的存储卷
volumes:
# 存储卷名称
- name: html
# 路径,为宿主机存储路径
hostPath:
# 在宿主机上目录的路径
path: /data/pod/volume1
# 定义类型,表示如果宿主机没有此目录则自动创建
type: DirectoryOrCreate
2.2.3 验证方法
kubectl apply -f pod-hostpath.yaml
# 查看Pod调度节点
kubectl get pods -o wide
# 访问测试
curl 10.244.2.35 # 输出对应节点的内容(node01或node02.benet.com)
# 验证持久化效果
kubectl delete -f pod-hostpath.yaml
kubectl apply -f pod-hostpath.yaml
# 重新访问仍能获取相同内容
注意:hostPath 不适合生产环境的分布式部署,因为它会导致 Pod 与特定节点强绑定,影响调度灵活性。
2.3 NFS 网络共享存储卷
2.3.1 核心特点
- 基于网络的共享存储,支持多节点访问
- 数据集中存放于 NFS 服务端,实现真正的跨节点共享
- 支持 RWX(多路读写)访问模式,适合多 Pod 协作场景
2.3.2 配置示例
2.3.2.1 NFS 服务端配置(stor01节点)
# 安装NFS组件
rpm -q rpcbind nfs-utils
yum install -y nfs-utils rpcbind
# 创建共享目录
mkdir /data/volumes -p
chmod 777 /data/volumes
# 配置共享策略
vim /etc/exports
/data/volumes 192.168.10.0/24(rw,no_root_squash)
# 启动服务
systemctl start rpcbind
systemctl start nfs
systemctl enable rpcbind
systemctl enable nfs
# 验证配置
showmount -e
# 输出应显示:Export list for stor01:/data/volumes 192.168.10.0/24
2.3.2.2 创建使用NFS的Pod
vim pod-nfs-vol.yaml
kind: Pod
apiVersion: v1
metadata:
name: pod-vol-nfs
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
path: /data/volumes
server: stor01
2.3.3 验证方法
kubectl apply -f pod-nfs-vol.yaml
# 在NFS服务器创建测试文件
echo "<h1> nfs stor01</h1>" >/data/volumes/index.html
# 访问测试
kubectl get pods -o wide # 获取Pod IP
curl 10.244.2.38 # 应输出<h1> nfs stor01</h1>
# 验证持久化
kubectl delete -f pod-nfs-vol.yaml
kubectl apply -f pod-nfs-vol.yaml
# 重新访问仍能获取相同内容

3. PV 与 PVC 持久化机制
3.1 核心概念
- PV(Persistent Volume):持久化存储卷,由运维人员定义,描述底层存储资源
- PVC(Persistent Volume Claim):持久化存储请求,由开发者创建,描述所需存储特性
- StorageClass:存储类,用于自动创建 PV 的模板,简化存储管理
| 概念 | 作用 | 维护者 |
|---|---|---|
| PV | 定义底层存储资源 | 运维管理员 |
| PVC | 用户提出存储需求 | 应用开发者 |
| StorageClass | 自动创建 PV 模板 | 运维工程师 |
3.2 生命周期管理
PV和PVC的交互遵循以下生命周期:
Provisioning(配置)→ Binding(绑定)→ Using(使用)→ Releasing(释放)→ Recycling(回收)
- Provisioning:PV的创建,支持静态创建和动态创建(通过StorageClass)
- Binding:系统将合适的PV分配给PVC
- Using:Pod通过PVC使用存储卷,系统会阻止删除正在使用的PVC
- Releasing:Pod删除PVC释放存储卷
- Recycling:根据回收策略处理PV资源
3.3 PV的状态流转
- Available:可用状态,未被任何PVC绑定
- Bound:已绑定到某个PVC
- Released:PVC已删除,但资源尚未被集群回收
- Failed:自动回收失败
3.4 回收策略
- Retain:保留数据,需手动清理(默认策略)
- PV被标记为Released状态,数据保留
- 需要手动删除PV并清理存储资源
- Delete:自动删除存储资源
- 仅支持AWS EBS、GCE PD等云存储
- Recycle:清空数据后重新可用
- 仅支持NFS和HostPath
- 相当于执行
rm -rf /thevolume/* - 注意:Kubernetes 1.14+已弃用此策略
3.5 PV与PVC定义规范
3.5.1 PV定义示例
# 查看PV定义规范
kubectl explain pv
kubectl explain pv.spec
# 示例PV定义
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001 # PV名称
spec:
nfs: # 存储类型
path: /data/volumes/v1 # 挂载路径
server: stor01 # NFS服务器地址
accessModes: # 访问模式
- ReadWriteOnce # RWO:仅单Pod读写
- ReadWriteMany # RWX:多Pod读写
capacity: # 存储容量
storage: 1Gi
persistentVolumeReclaimPolicy: Retain # 回收策略
storageClassName: "" # 存储类名称(为空表示不关联任何存储类)
3.5.2 PVC定义示例
# 查看PVC定义规范
kubectl explain pvc
kubectl explain pvc.spec
# 示例PVC定义
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc # PVC名称
namespace: default
spec:
accessModes: # 访问模式(必须是PV支持的子集)
- ReadWriteMany
resources:
requests:
storage: 2Gi # 申请的存储容量
storageClassName: "" # 需与目标PV的storageClassName匹配
匹配规则:PVC与PV需在accessModes、storageClassName和存储容量上匹配才能成功绑定
4. NFS + PV + PVC 实战
4.1 准备工作
# 在NFS服务器上创建目录
mkdir /data/volumes/v{1,2,3,4,5}
# 配置NFS共享
vim /etc/exports
/data/volumes/v1 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.10.0/24(rw,no_root_squash)
# 生效配置
exportfs -arv
# 验证
showmount -e

4.2 创建PV
vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: stor01
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 5Gi
# 创建PV
kubectl apply -f pv-demo.yaml
# 查看PV状态
kubectl get pv
# 所有PV应处于Available状态

4.3 创建PVC和使用它的Pod
vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-pvc
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
# 创建资源
kubectl apply -f pod-vol-pvc.yaml
# 查看绑定状态
kubectl get pv
# pv003应处于Bound状态,CLAIM列显示default/mypvc
kubectl get pvc
# mypvc应处于Bound状态,VOLUME列显示pv003


4.4 验证效果
# 在NFS服务器对应目录创建测试文件
cd /data/volumes/v3/
echo "welcome to use pv3,hhhhhhh" > index.html
# 访问Pod验证
kubectl get pods -o wide # 获取Pod IP
curl 10.244.1.27 # 应输出welcome to use pv3,hhhhhhh

5. StorageClass + NFS 动态存储

5.1 动态存储的优势
- 自动创建PV,无需手动预配置
- 按需分配存储资源,提高资源利用率
- 简化存储管理流程,降低运维成本
5.2 实现原理
通过nfs-client-provisioner组件实现:
- 监听PVC创建事件
- 根据StorageClass配置自动创建PV
- 在NFS服务器上创建对应目录
- 建立PV与PVC的绑定关系
5.3 部署步骤
5.3.1 准备NFS服务
# 在stor01节点
mkdir /opt/k8s
chmod 777 /opt/k8s/
vim /etc/exports
/opt/k8s 192.168.10.0/24(rw,no_root_squash,sync)
systemctl restart nfs
5.3.2 创建RBAC权限配置
因为要使用外部插件所以必须给进行授权,然后给权限绑定角色
vim nfs-client-rbac.yaml
# 创建ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
# 创建集群角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-clusterrole
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
# 集群角色绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-client-provisioner-clusterrolebinding
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-clusterrole
apiGroup: rbac.authorization.k8s.io
kubectl apply -f nfs-client-rbac.yaml

5.3.3 部署NFS Provisioner
# 注意:K8s 1.20+版本需要添加以下配置
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false # 添加此行
- --advertise-address=192.168.10.120
...
# 重启API Server
kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
kubectl delete pods kube-apiserver -n kube-system
# 创建Provisioner部署
vim nfs-client-provisioner.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: nfs-storage # 需与StorageClass中的provisioner一致
- name: NFS_SERVER
value: stor01 # NFS服务器地址
- name: NFS_PATH
value: /opt/k8s # NFS共享目录
volumes:
- name: nfs-client-root
nfs:
server: stor01
path: /opt/k8s
kubectl apply -f nfs-client-provisioner.yaml
# 验证部署
kubectl get pod
# 应看到nfs-client-provisioner-xxx处于Running状态

5.3.4 创建StorageClass
vim nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client-storageclass
provisioner: nfs-storage # 与Provisioner中的PROVISIONER_NAME一致
parameters:
archiveOnDelete: "false" # 删除PVC时是否存档数据
kubectl apply -f nfs-client-storageclass.yaml
# 查看存储类
kubectl get storageclass

5.3.5 测试动态存储
vim test-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-client-storageclass # 关联存储类
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-storageclass-pod
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: nfs-pvc
mountPath: /mnt
restartPolicy: Never
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-nfs-pvc # 与PVC名称一致
kubectl apply -f test-pvc-pod.yaml

5.4 验证动态存储功能
# 1. 查看PVC状态
kubectl get pvc
# test-nfs-pvc应处于Bound状态
# 2. 查看自动创建的PV
kubectl get pv
# 应有一个与PVC对应的PV处于Bound状态
# 3. 查看NFS服务器上自动创建的目录
ls /opt/k8s/
# 应看到类似default-test-nfs-pvc-<随机ID>的目录
# 4. 验证数据写入
kubectl exec -it test-storageclass-pod sh
/ # cd /mnt
/mnt # echo 'this is test file' > test.txt
# 5. 在NFS服务器验证
cat /opt/k8s/default-test-nfs-pvc-*/test.txt
# 应输出this is test file




6. 存储方案对比与选型建议
| 存储类型 | 生命周期 | 共享性 | 持久性 | 典型场景 | 优缺点 |
|---|---|---|---|---|---|
| emptyDir | Pod生命周期 | 同Pod内 | ❌ | 临时缓存、容器间通信 | 优点:简单易用;缺点:生命周期短 |
| hostPath | 节点生命周期 | 单节点 | ✅ | 节点级日志存储、临时数据 | 优点:实现简单;缺点:节点亲和性强、不适合分布式 |
| NFS | 独立服务 | 多节点 | ✅✅ | 集群共享存储、中小规模部署 | 优点:易维护、成本低;缺点:性能一般 |
| PV + PVC | 集群级别 | 按需分配 | ✅✅✅ | 生产环境、规范存储管理 | 优点:解耦存储与应用;缺点:需手动管理PV |
| StorageClass | 自动管理 | 动态分配 | ✅✅✅✅ | 大规模部署、云原生应用 | 优点:自动化程度高;缺点:初期配置复杂 |
6.1 选型建议
- 开发测试环境:可选用emptyDir或hostPath
- 小规模生产环境:NFS + PV + PVC组合
- 大规模生产环境:StorageClass + 企业级存储(如Ceph、GlusterFS)
- 有状态应用:推荐使用PVC + StatefulSet组合,确保存储稳定性
6.2 注意事项
- 避免在生产环境使用hostPath,会导致Pod调度受限
- NFS性能有限,高IO场景建议使用专业存储方案
- 合理设置PV回收策略,避免数据意外丢失
- 生产环境建议为StorageClass配置默认存储类,简化PVC创建
通过合理选择和配置Kubernetes存储方案,可以有效解决容器化应用的数据持久化问题,为应用提供可靠、灵活的存储支持。
总结
本文系统梳理了 Kubernetes 容器存储的核心技术与实践方案,从问题本质到解决方案,从基础配置到高级实战,构建了一套完整的容器存储知识体系。首先,针对容器存储的短暂性问题,Kubernetes 通过 Volume 抽象实现了数据持久化与跨容器共享,而 emptyDir、hostPath、NFS 三类基础存储卷,分别适用于临时缓存、节点级存储、跨节点共享等不同基础场景,各有其适用边界与局限性。
在此基础上,PV 与 PVC 机制实现了存储资源与应用需求的解耦 —— 运维人员定义 PV 描述底层存储资源,开发人员通过 PVC 提出存储需求,二者通过访问模式、存储容量、存储类等维度实现自动匹配,规范了存储管理流程。而 StorageClass 动态存储方案则进一步提升了存储管理的自动化水平,通过 nfs-client-provisioner 组件实现 PV 的自动创建、目录生成与绑定,大幅降低了大规模集群下的存储运维成本。
在实际选型中,需结合业务场景与规模综合考量:开发测试环境可选择简单的 emptyDir 或 hostPath;小规模生产环境推荐 NFS + PV + PVC 组合,平衡易用性与稳定性;大规模生产环境则建议采用 StorageClass 结合企业级存储(如 Ceph、GlusterFS),满足高可用与高 IO 需求。同时,需警惕 hostPath 的节点亲和性风险、NFS 的性能瓶颈,合理设置 PV 回收策略,确保数据安全。
总之,Kubernetes 容器存储方案没有 “最优解”,只有 “最适解”。掌握各类存储方案的特性与适用场景,根据业务需求灵活选择与配置,才能为容器化应用构建稳定、高效且可靠的数据存储底座,支撑企业级应用的持续稳定运行。
更多推荐



所有评论(0)