k8s(五)PV和PVC详解
文章目录
- 前言
- 1. 容器存储的核心挑战与解决方案
- 1.1 容器存储的短暂性问题
- 1.2 Kubernetes Volume 抽象
- 2. 基础存储卷类型
- 2.1 emptyDir 存储卷
- 2.1.1 核心特点
- 2.1.2 配置示例
- 2.1.3 验证方法
- 2.2 hostPath 存储卷
- 2.2.1 核心特点
- 2.2.2 配置示例
- 2.2.3 验证方法
- 2.3 NFS 网络共享存储卷
- 2.3.1 核心特点
- 2.3.2 配置示例
- 2.3.2.1 NFS 服务端配置(stor01节点)
- 2.3.2.2 创建使用NFS的Pod
- 2.3.3 验证方法
- 3. PV 与 PVC 持久化机制
- 3.1 核心概念
- 3.2 生命周期管理
- 3.3 PV的状态流转
- 3.4 回收策略
- 3.5 PV与PVC定义规范
- 3.5.1 PV定义示例
- 3.5.2 PVC定义示例
- 4. NFS + PV + PVC 实战
- 4.1 准备工作
- 4.2 创建PV
- 4.3 创建PVC和使用它的Pod
- 4.4 验证效果
- 5. StorageClass + NFS 动态存储
- 5.1 动态存储的优势
- 5.2 实现原理
- 5.3 部署步骤
- 5.3.1 准备NFS服务
- 5.3.2 创建RBAC权限配置
- 5.3.3 部署NFS Provisioner
- 5.3.4 创建StorageClass
- 5.3.5 测试动态存储
- 5.4 验证动态存储功能
- 6. 存储方案对比与选型建议
- 6.1 选型建议
- 6.2 注意事项
- 总结
前言
在容器化技术飞速发展的今天,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/volumesvim pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-emptydirnamespace: defaultlabels:app: myapptier: frontend
spec:containers:- name: myappimage: ikubernetes/myapp:v1imagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 80# 定义容器挂载内容volumeMounts:# 使用的存储卷名称,需与下方volumes字段name值匹配- name: html# 挂载至容器中哪个目录mountPath: /usr/share/nginx/html/- name: busyboximage: busybox:latestimagePullPolicy: IfNotPresentvolumeMounts:- 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-hostpathnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1# 定义容器挂载内容volumeMounts:# 使用的存储卷名称,需与下方volumes字段name值匹配- name: html# 挂载至容器中哪个目录mountPath: /usr/share/nginx/html# 读写挂载方式,默认为读写模式falsereadOnly: 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-nfsnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/htmlvolumes:- name: htmlnfs:path: /data/volumesserver: 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: 1GipersistentVolumeReclaimPolicy: 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支持的子集)- ReadWriteManyresources: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: pv001labels:name: pv001
spec:nfs:path: /data/volumes/v1server: stor01accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv002labels:name: pv002
spec:nfs:path: /data/volumes/v2server: stor01accessModes: ["ReadWriteOnce"]capacity:storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv003labels:name: pv003
spec:nfs:path: /data/volumes/v3server: stor01accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv004labels:name: pv004
spec:nfs:path: /data/volumes/v4server: stor01accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv005labels:name: pv005
spec:nfs:path: /data/volumes/v5server: stor01accessModes: ["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: mypvcnamespace: default
spec:accessModes: ["ReadWriteMany"]resources:requests:storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:name: pod-vol-pvcnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/htmlvolumes:- name: htmlpersistentVolumeClaim:claimName: mypvc
# 创建资源
kubectl apply -f pod-vol-pvc.yaml# 查看绑定状态
kubectl get pv
# pv003应处于Bound状态,CLAIM列显示default/mypvckubectl 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: ServiceAccountname: nfs-client-provisionernamespace: default
roleRef:kind: ClusterRolename: nfs-client-provisioner-clusterroleapiGroup: 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: 1selector:matchLabels:app: nfs-client-provisionerstrategy:type: Recreatetemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisionerimage: quay.io/external_storage/nfs-client-provisioner:latestimagePullPolicy: IfNotPresentvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: nfs-storage # 需与StorageClass中的provisioner一致- name: NFS_SERVERvalue: stor01 # NFS服务器地址- name: NFS_PATHvalue: /opt/k8s # NFS共享目录volumes:- name: nfs-client-rootnfs:server: stor01path: /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.yamlapiVersion: v1
kind: PersistentVolumeClaim
metadata:name: test-nfs-pvc
spec:accessModes:- ReadWriteManystorageClassName: nfs-client-storageclass # 关联存储类resources:requests:storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:name: test-storageclass-pod
spec:containers:- name: busyboximage: busybox:latestimagePullPolicy: IfNotPresentcommand: ["/bin/sh", "-c", "sleep 3600"]volumeMounts:- name: nfs-pvcmountPath: /mntrestartPolicy: Nevervolumes:- name: nfs-pvcpersistentVolumeClaim: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 容器存储方案没有 “最优解”,只有 “最适解”。掌握各类存储方案的特性与适用场景,根据业务需求灵活选择与配置,才能为容器化应用构建稳定、高效且可靠的数据存储底座,支撑企业级应用的持续稳定运行。