目录

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/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.3 验证

kubectl apply -f pod-emptydir.yaml

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>

# 验证两个容器通过 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-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 字段定义了 Pause 容器关联的宿主机存储卷
  volumes:
    # 存储卷名称
    - name: html
      # 存储类型为 hostPath(宿主机路径)
      hostPath:
        # 宿主机上的目录路径
        path: /data/pod/volume1
        # 目录类型:若宿主机无此目录则自动创建
        type: DirectoryOrCreate

kubectl 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-nfs
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html  # Nginx 静态文件目录
  volumes:
    - name: html
      nfs:
        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 的生命周期

  1. Provisioning(配置):创建 PV(静态手动创建或动态通过 StorageClass 创建);
  2. Binding(绑定):PVC 匹配满足条件的 PV,建立绑定关系;
  3. Using(使用):Pod 通过 PVC 挂载并使用存储;
  4. Releasing(释放):Pod 删除,PVC 与 PV 解绑,PV 状态变为 Released;
  5. 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: 1Gi
  persistentVolumeReclaimPolicy: 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 支持的访问模式子集
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi  # 申请的存储容量
  storageClassName: ""  # 需与目标 PV 的 storageClassName 一致(为空则匹配无存储类的 PV)

5.5 PV 与 PVC 的匹配规则

  1. 存储容量:PV 容量 ≥ PVC 申请容量;
  2. 访问模式:PV 支持的访问模式包含 PVC 要求的模式;
  3. 存储类(storageClassName):两者必须一致(均为空或相同名称);
  4. 标签选择器(可选):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: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: stor01
  accessModes: ["ReadWriteMany", "ReadWriteOnce"]
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: stor01
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
  persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: stor01
  accessModes: ["ReadWriteMany", "ReadWriteOnce"]
  capacity:
    storage: 2Gi
  persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
  labels:
    name: pv004
spec:
  nfs:
    path: /data/volumes/v4
    server: stor01
  accessModes: ["ReadWriteMany", "ReadWriteOnce"]
  capacity:
    storage: 4Gi
  persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv005
  labels:
    name: pv005
spec:
  nfs:
    path: /data/volumes/v5
    server: stor01
  accessModes: ["ReadWriteMany", "ReadWriteOnce"]
  capacity:
    storage: 5Gi
  persistentVolumeReclaimPolicy: 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: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]  # 要求 RWX 访问模式
  resources:
    requests:
      storage: 2Gi  # 申请 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  # 挂载到 Nginx 静态目录
  volumes:
    - name: html
      persistentVolumeClaim:
        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  # 强制调度到 node1
  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-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: 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
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: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate  # 重建策略:避免多副本冲突
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner  # 关联前面创建的 ServiceAccount
      containers:
      - name: nfs-client-provisioner
        # 推荐使用国内镜像(避免 quay.io 拉取失败)
        image: registry.cn-hangzhou.aliyuncs.com/opensourceway/kubernetes/nfs-client-provisioner:v3.1.0-k8s1.11
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes  # 挂载 NFS 根目录
        env:
        - name: PROVISIONER_NAME
          value: nfs-storage  # Provisioner 名称(需与 StorageClass 中一致)
        - 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

# 查看 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  # 关联上面创建的 StorageClass
  resources:
    requests:
      storage: 1Gi  # 申请 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

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(动态)。
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐