k8s中PV 与 PVC
目录
1、容器存储的短暂性问题
2、emptyDir 存储卷
2.1 特点
2.2 案例
2.3 验证
3、hostPath 存储卷
3.1 特点
3.2 案例
3.3 访问测试
4、NFS 网络共享存储卷
4.1 特点
4.2 案例
4.2.1 部署 NFS 服务端(stor01 节点)
4.2.2 所有 Node 节点安装 NFS 客户端(关键)
4.2.3 创建 NFS 存储卷的 Pod(master 节点)
4.2.4 验证数据共享与持久化
5、PV 与 PVC 持久化机制
5.1 PV 与 PVC 的核心概念
5.2 PV 与 PVC 的生命周期
5.3 PV 的关键配置参数
关键参数说明
5.4 PVC 的关键配置参数
5.5 PV 与 PVC 的匹配规则
6、NFS + PV + PVC 实战(静态 PV)
6.1 准备 NFS 存储(stor01 节点)
6.2 定义 PV(master 节点)
6.3 定义 PVC + Pod(master 节点)
6.4 验证数据持久化与共享
7、StorageClass + NFS 动态存储(自动创建 PV)
7.1 核心原理
7.2 部署步骤
7.2.1 准备 NFS 服务端(stor01 节点)
7.2.2 创建 RBAC 权限(master 节点)
7.2.3 部署 NFS Provisioner(master 节点)
7.2.4 创建 StorageClass(master 节点)
7.2.5 测试动态存储(创建 PVC + Pod)
7.3 验证动态存储功能
7.3.1 验证 PVC 绑定与 PV 自动创建
7.3.2 验证 NFS 目录自动创建
7.3.3 验证数据同步
7.3.4 验证自动回收
8、常见问题与排查
8.1 PV 无法绑定 PVC
8.2 Pod 卡在 ContainerCreating
8.3 动态 PV 创建失败(K8s 1.20+)
9、总结
核心选型建议
1、容器存储的短暂性问题
容器的文件系统是临时性的:
- 容器崩溃重启后,容器内数据会丢失;
- 同一个 Pod 内的多个容器无法直接共享文件。
Kubernetes 的 Volume 抽象 解决了这两个问题:通过 Pause 容器 让多个容器共享同一个 Volume,从而实现文件共享与持久化。
2、emptyDir 存储卷
2.1 特点
- Pod 调度到节点时自动创建;
- Pod 删除后数据也随之销毁;
- 仅适合临时缓存或容器间数据共享。
2.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.3 验证
kubectl apply -f pod-emptydir.yamlkubectl 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># 验证两个容器通过 emptyDir 共享文件
curl 10.244.2.19
Thu May 27 18:17:11 UTC 2021
Thu May 27 18:17:13 UTC 2021
Thu May 27 18:17:15 UTC 2021
可见日期不断追加,说明容器间共享成功。
3、hostPath 存储卷
3.1 特点
- 将节点(宿主机)上的目录挂载到容器;
- 可实现持久化(Pod 删除后节点目录数据保留);
- 但节点故障会导致数据丢失,不支持跨节点共享。
3.2 案例
# 1)在所有 Node 节点上创建挂载目录(确保调度到任意节点都能找到目录)
ssh root@node01 "mkdir -p /data/pod/volume1 && echo 'node01.benet.com' > /data/pod/volume1/index.html"
ssh root@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# 读写挂载方式,默认为读写(false)readOnly: false# volumes 字段定义了 Pause 容器关联的宿主机存储卷volumes:# 存储卷名称- name: html# 存储类型为 hostPath(宿主机路径)hostPath:# 宿主机上的目录路径path: /data/pod/volume1# 目录类型:若宿主机无此目录则自动创建type: DirectoryOrCreatekubectl apply -f pod-hostpath.yaml
3.3 访问测试
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-hostpath 1/1 Running 0 37s 10.244.2.35 node02 <none> <none># 访问 Pod IP,返回节点目录中的内容
curl 10.244.2.35
node02.benet.com# 删除 Pod 后重建,验证数据持久化(调度到同一节点仍能访问原内容)
kubectl delete -f pod-hostpath.yaml
kubectl apply -f pod-hostpath.yaml kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-hostpath 1/1 Running 0 36s 10.244.2.37 node02 <none> <none>curl 10.244.2.37
node02.benet.com
Pod 删除重建后仍能访问相同内容,说明数据在节点本地实现持久化。
4、NFS 网络共享存储卷
4.1 特点
- 多节点共享数据(支持跨节点访问);
- 数据集中存放于 NFS 服务端,节点故障不影响数据;
- 支持 RWX(多路读写),适合集群级共享存储场景。
4.2 案例
4.2.1 部署 NFS 服务端(stor01 节点)
# 安装 NFS 相关组件
rpm -q rpcbind nfs-utils || yum install -y nfs-utils rpcbind# 创建共享目录
mkdir /data/volumes -p
chmod 777 /data/volumes # 赋予读写权限(测试环境,生产环境需按需调整)# 配置 NFS 共享(允许 192.168.100.0/24 网段访问)
vim /etc/exports
/data/volumes 192.168.100.0/24(rw,no_root_squash)# 启动服务并设置开机自启
systemctl start rpcbind nfs
systemctl enable rpcbind nfs# 验证 NFS 共享
showmount -e stor01
Export list for stor01:
/data/volumes 192.168.100.0/24
4.2.2 所有 Node 节点安装 NFS 客户端(关键)
# 所有 Node 节点必须安装 nfs-utils,否则无法挂载 NFS 卷
yum install -y nfs-utils
4.2.3 创建 NFS 存储卷的 Pod(master 节点)
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/html # Nginx 静态文件目录volumes:- name: htmlnfs:path: /data/volumes # NFS 服务端共享目录server: stor01 # NFS 服务端主机名(需保证 Node 能解析)kubectl apply -f pod-nfs-vol.yaml# 查看 Pod 状态(确保 Running)
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-vol-nfs 1/1 Running 0 21s 10.244.2.38 node02 <none> <none>
4.2.4 验证数据共享与持久化
# 在 NFS 服务端创建测试文件
echo "<h1> NFS Storage: stor01 </h1>" > /data/volumes/index.html# 访问 Pod IP,验证能否读取 NFS 中的文件
curl 10.244.2.38
<h1> NFS Storage: stor01 </h1># 删除 Pod 后重建,验证数据持久化
kubectl delete -f pod-nfs-vol.yaml
kubectl apply -f pod-nfs-vol.yaml# 重新访问,仍能获取 NFS 中的文件
curl 10.244.2.39 # 新 Pod IP
<h1> NFS Storage: stor01 </h1>
5、PV 与 PVC 持久化机制
5.1 PV 与 PVC 的核心概念
- PV(Persistent Volume):持久化存储卷,由运维工程师定义,描述底层存储资源(如 NFS 目录、容量、访问模式),是集群级资源(跨命名空间)。
- PVC(Persistent Volume Claim):持久化存储请求,由应用开发者提出,描述所需存储的条件(如容量、访问模式),无需关心底层存储细节。
- 核心逻辑:PVC 向集群申请存储资源,Kubernetes 自动匹配满足条件的 PV 并绑定,Pod 通过 PVC 间接使用 PV 存储。
概念 | 作用 | 维护者 |
---|---|---|
PV | 定义底层存储资源 | 运维管理员 |
PVC | 提出存储需求 | 应用开发者 |
StorageClass | 动态创建 PV 的模板 | 运维工程师 |
5.2 PV 与 PVC 的生命周期
- Provisioning(配置):创建 PV(静态手动创建或动态通过 StorageClass 创建);
- Binding(绑定):PVC 匹配满足条件的 PV,建立绑定关系;
- Using(使用):Pod 通过 PVC 挂载并使用存储;
- Releasing(释放):Pod 删除,PVC 与 PV 解绑,PV 状态变为 Released;
- Recycling(回收):根据 PV 的回收策略处理数据(保留、删除或清空)。
5.3 PV 的关键配置参数
apiVersion: v1
kind: PersistentVolume
metadata:name: pv001 # PV 名称(唯一)
spec:nfs: # 存储类型(支持 nfs、hostPath、ceph 等)path: /data/volumes/v1 # 底层存储目录server: stor01 # 存储服务端地址accessModes: # 访问模式(支持多模式组合)- ReadWriteOnce # RWO:仅单个 Pod 读写- ReadWriteMany # RWX:多个 Pod 同时读写capacity: # 存储容量storage: 1GipersistentVolumeReclaimPolicy: Retain # 回收策略(Retain/Delete/Recycle)storageClassName: "" # 存储类名称(用于匹配 PVC)
关键参数说明
- 访问模式(accessModes):
- RWO(ReadWriteOnce):仅允许单个 Pod 挂载读写(所有存储类型均支持);
- ROX(ReadOnlyMany):多个 Pod 挂载只读;
- RWX(ReadWriteMany):多个 Pod 同时挂载读写(仅 NFS、Ceph 等共享存储支持)。
- 回收策略(persistentVolumeReclaimPolicy):
- Retain(保留):PVC 删除后,PV 状态变为 Released,数据保留,需手动清理;
- Delete(删除):PVC 删除后,自动删除 PV 及底层存储资源(仅云存储支持,如 AWS EBS);
- Recycle(回收):PVC 删除后,清空 PV 数据并恢复为 Available 状态(仅支持 NFS/HostPath)。
5.4 PVC 的关键配置参数
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: mypvc # PVC 名称namespace: default # 命名空间级资源
spec:accessModes: # 需为 PV 支持的访问模式子集- ReadWriteManyresources:requests:storage: 2Gi # 申请的存储容量storageClassName: "" # 需与目标 PV 的 storageClassName 一致(为空则匹配无存储类的 PV)
5.5 PV 与 PVC 的匹配规则
- 存储容量:PV 容量 ≥ PVC 申请容量;
- 访问模式:PV 支持的访问模式包含 PVC 要求的模式;
- 存储类(storageClassName):两者必须一致(均为空或相同名称);
- 标签选择器(可选):PVC 可通过标签筛选特定 PV。
6、NFS + PV + PVC 实战(静态 PV)
6.1 准备 NFS 存储(stor01 节点)
# 在 NFS 服务端创建 5 个独立子目录(对应 5 个 PV)
mkdir /data/volumes/v{1,2,3,4,5} -p# 配置 NFS 共享(允许 192.168.100.0/24 网段访问所有子目录)
vim /etc/exports
/data/volumes/v1 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.100.0/24(rw,no_root_squash)# 重新加载 NFS 配置
exportfs -arv# 验证共享
showmount -e stor01
6.2 定义 PV(master 节点)
vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: pv001labels:name: pv001
spec:nfs:path: /data/volumes/v1server: stor01accessModes: ["ReadWriteMany", "ReadWriteOnce"]capacity:storage: 1GipersistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv002labels:name: pv002
spec:nfs:path: /data/volumes/v2server: stor01accessModes: ["ReadWriteOnce"]capacity:storage: 2GipersistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv003labels:name: pv003
spec:nfs:path: /data/volumes/v3server: stor01accessModes: ["ReadWriteMany", "ReadWriteOnce"]capacity:storage: 2GipersistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv004labels:name: pv004
spec:nfs:path: /data/volumes/v4server: stor01accessModes: ["ReadWriteMany", "ReadWriteOnce"]capacity:storage: 4GipersistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv005labels:name: pv005
spec:nfs:path: /data/volumes/v5server: stor01accessModes: ["ReadWriteMany", "ReadWriteOnce"]capacity:storage: 5GipersistentVolumeReclaimPolicy: Retain# 应用 PV 配置
kubectl apply -f pv-demo.yaml# 查看 PV 状态(初始为 Available)
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 7s
pv002 2Gi RWO Retain Available 7s
pv003 2Gi RWO,RWX Retain Available 7s
pv004 4Gi RWO,RWX Retain Available 7s
pv005 5Gi RWO,RWX Retain Available 7s
6.3 定义 PVC + Pod(master 节点)
vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: mypvcnamespace: default
spec:accessModes: ["ReadWriteMany"] # 要求 RWX 访问模式resources:requests:storage: 2Gi # 申请 2Gi 容量
---
apiVersion: v1
kind: Pod
metadata:name: pod-vol-pvcnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/html # 挂载到 Nginx 静态目录volumes:- name: htmlpersistentVolumeClaim:claimName: mypvc # 关联上面定义的 PVC# 应用配置
kubectl apply -f pod-vol-pvc.yaml# 查看 PVC 状态(应为 Bound,自动绑定匹配的 PV)
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv003 2Gi RWO,RWX 22s# 查看 PV 状态(pv003 变为 Bound)
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv003 2Gi RWO,RWX Retain Bound default/mypvc 19m
6.4 验证数据持久化与共享
# 在 NFS 服务端的 pv003 对应目录创建测试文件
echo "Welcome to PV003 (NFS + PV + PVC)" > /data/volumes/v3/index.html# 查看 Pod IP 并访问
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-vol-pvc 1/1 Running 0 3m 10.244.2.39 node02 <none> <none>curl 10.244.2.39
Welcome to PV003 (NFS + PV + PVC)# 跨节点验证共享:在另一节点创建 Pod 关联同一个 PVC(需先删除现有 Pod)
kubectl delete pod pod-vol-pvc
vim pod-vol-pvc-node1.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-vol-pvc-node1
spec:nodeName: node1 # 强制调度到 node1containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/htmlvolumes:- name: htmlpersistentVolumeClaim:claimName: mypvckubectl apply -f pod-vol-pvc-node1.yaml
curl 10.244.1.40 # 新 Pod IP(node1 节点)
Welcome to PV003 (NFS + PV + PVC) # 成功访问共享数据
7、StorageClass + NFS 动态存储(自动创建 PV)
静态 PV 需手动创建,适合小规模集群;动态存储通过 StorageClass + NFS Provisioner 实现 PV 自动创建,适合大规模部署。
7.1 核心原理
- NFS Provisioner:外部存储插件,负责监听 PVC 请求,自动在 NFS 服务端创建目录、生成 PV 并绑定 PVC;
- StorageClass:动态存储模板,定义 PV 的创建规则(如存储类型、Provisioner、回收策略);
- RBAC 权限:授予 NFS Provisioner 操作 PV、PVC、StorageClass 等资源的权限。
7.2 部署步骤
7.2.1 准备 NFS 服务端(stor01 节点)
# 创建动态存储的根目录
mkdir /opt/k8s -p
chmod 777 /opt/k8s# 配置 NFS 共享(允许 192.168.100.0/24 网段访问)
vim /etc/exports
/opt/k8s 192.168.100.0/24(rw,no_root_squash,sync)# 重新加载 NFS 配置
exportfs -arv
showmount -e stor01
7.2.2 创建 RBAC 权限(master 节点)
vim nfs-client-rbac.yaml
# 创建 ServiceAccount:用于 NFS Provisioner 的身份认证
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisioner
---
# 创建 ClusterRole:定义操作资源的权限
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"]
---
# 绑定 ClusterRole 到 ServiceAccount
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
7.2.3 部署 NFS Provisioner(master 节点)
# 注意:K8s 1.20~1.24 版本需添加 RemoveSelfLink=false 参数(1.25+ 版本无需添加)
# 1)若为 K8s 1.20~1.24 版本,修改 kube-apiserver 配置:
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:containers:- command:- kube-apiserver- --feature-gates=RemoveSelfLink=false # 添加这一行- --advertise-address=192.168.100.49 # 你的 master IP# 其他默认参数保持不变...# 保存后,kubelet 会自动重启 kube-apiserver,验证状态:
kubectl get pods -n kube-system | grep kube-apiserver# 2)部署 NFS 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: Recreate # 重建策略:避免多副本冲突template:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisioner # 关联前面创建的 ServiceAccountcontainers:- name: nfs-client-provisioner# 推荐使用国内镜像(避免 quay.io 拉取失败)image: registry.cn-hangzhou.aliyuncs.com/opensourceway/kubernetes/nfs-client-provisioner:v3.1.0-k8s1.11imagePullPolicy: IfNotPresentvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumes # 挂载 NFS 根目录env:- name: PROVISIONER_NAMEvalue: nfs-storage # Provisioner 名称(需与 StorageClass 中一致)- 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# 查看 Provisioner 状态(确保 Running)
kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-5d7df8dcc8-x66dr 1/1 Running 0 14s
7.2.4 创建 StorageClass(master 节点)
vim nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-client-storageclass # StorageClass 名称
provisioner: nfs-storage # 必须与 Provisioner 的 PROVISIONER_NAME 一致
parameters:archiveOnDelete: "false" # 删除 PVC 时自动删除数据(false=删除,true=存档)
reclaimPolicy: Delete # PV 回收策略(与 parameters.archiveOnDelete 配合)
volumeBindingMode: Immediate # 立即绑定 PVC# 应用配置
kubectl apply -f nfs-client-storageclass.yaml# 查看 StorageClass
kubectl get storageclass
NAME PROVISIONER RECLAIM POLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client-storageclass nfs-storage Delete Immediate false 43s
7.2.5 测试动态存储(创建 PVC + Pod)
vim test-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: test-nfs-pvc
spec:accessModes: ["ReadWriteMany"] # 访问模式storageClassName: nfs-client-storageclass # 关联上面创建的 StorageClassresources:requests:storage: 1Gi # 申请 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: /mnt # 容器内挂载路径restartPolicy: Nevervolumes:- name: nfs-pvcpersistentVolumeClaim:claimName: test-nfs-pvc # 关联上面的 PVC# 应用配置
kubectl apply -f test-pvc-pod.yaml
7.3 验证动态存储功能
7.3.1 验证 PVC 绑定与 PV 自动创建
# 查看 PVC 状态(应为 Bound)
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-nfs-pvc Bound pvc-3c68f93d-4dac-4db5-a92f-1ce89a86ae6c 1Gi RWX nfs-client-storageclass 11s# 查看自动创建的 PV(名称以 pvc- 开头)
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pvc-3c68f93d-4dac-4db5-a92f-1ce89a86ae6c 1Gi RWX Delete Bound default/test-nfs-pvc nfs-client-storageclass 15s
7.3.2 验证 NFS 目录自动创建
# 在 NFS 服务端查看自动创建的目录(格式:${namespace}-${pvcName}-${pvName})
ls /opt/k8s/
default-test-nfs-pvc-pvc-3c68f93d-4dac-4db5-a92f-1ce89a86ae6c
7.3.3 验证数据同步
# 进入 Pod,在挂载目录 /mnt 写入文件
kubectl exec -it test-storageclass-pod -- sh
/mnt # echo "Dynamic PV Test: NFS + StorageClass" > test.txt
/mnt # exit# 在 NFS 服务端的自动创建目录中查看文件
cd /opt/k8s/default-test-nfs-pvc-pvc-3c68f93d-4dac-4db5-a92f-1ce89a86ae6c
cat test.txt
Dynamic PV Test: NFS + StorageClass # 数据同步成功
7.3.4 验证自动回收
# 删除 PVC 和 Pod
kubectl delete pod test-storageclass-pod
kubectl delete pvc test-nfs-pvc# 查看 PV 状态(自动删除)
kubectl get pv | grep test-nfs-pvc # 无输出# 查看 NFS 目录(自动删除)
ls /opt/k8s/default-test-nfs-pvc-pvc-3c68f93d-4dac-4db5-a92f-1ce89a86ae6c # 目录不存在
8、常见问题与排查
8.1 PV 无法绑定 PVC
- 检查访问模式:PVC 要求的模式必须是 PV 支持的子集;
- 检查容量:PV 容量需 ≥ PVC 申请容量;
- 检查存储类:两者的
storageClassName
必须一致(均为空或相同名称); - 检查 NFS 连通性:Node 节点需安装
nfs-utils
,且能访问 NFS 服务端(showmount -e stor01
)。
8.2 Pod 卡在 ContainerCreating
- 镜像拉取失败:替换为国内镜像(如阿里云镜像);
- NFS 挂载失败:检查 NFS 服务端防火墙(开放 111、2049 端口)、共享目录权限、节点网络连通性;
- 权限不足:确保 RBAC 权限配置正确(ServiceAccount、ClusterRole、ClusterRoleBinding 完整)。
8.3 动态 PV 创建失败(K8s 1.20+)
- 1.20~1.24 版本:需添加
--feature-gates=RemoveSelfLink=false
到 kube-apiserver 配置; - 1.25+ 版本:无需添加上述参数,直接使用适配的 Provisioner 镜像(如
k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
)。
9、总结
存储类型 | 生命周期 | 共享性 | 持久性 | 典型场景 |
---|---|---|---|---|
emptyDir | Pod 生命周期 | 同 Pod 内容器 | ❌ | 临时缓存、容器间共享 |
hostPath | 节点生命周期 | 单节点 | ✅ | 节点本地持久化(测试) |
NFS | 独立服务生命周期 | 多节点 | ✅ | 集群共享存储 |
PV + PVC(静态) | 集群级别 | 跨节点 | ✅ | 小规模生产环境 |
StorageClass(动态) | 自动创建 / 回收 | 跨节点 | ✅ | 大规模生产环境、自动化部署 |
核心选型建议
- 临时存储:使用 emptyDir;
- 节点本地持久化:使用 hostPath(测试环境);
- 集群共享存储:使用 NFS + PV + PVC(静态);
- 大规模自动化部署:使用 StorageClass + NFS Provisioner(动态)。