Skip to content

Kubernetes-PV存储卷

Kubernetes 作为容器编排领域的领导者,其存储系统设计解决了容器化应用中数据持久化的核心需求。PV(PersistentVolume)和 PVC(PersistentVolumeClaim)是 Kubernetes 存储体系的两大基石。

PV概念

PersistentVolume (PV) 是 Kubernetes 集群中的存储资源单元,由集群管理员预先配置,代表底层物理存储的逻辑抽象。PV 独立于 Pod 生命周期存在,支持多种存储类型包括本地存储(HostPath)、网络存储(NFS、iSCSI、Ceph)以及云存储(AWS EBS、Azure Disk 等)

PV 的核心属性包括:

  • 容量:指定存储空间大小(如 1Gi、5Gi)
  • 访问模式:定义读写权限(ReadWriteOnce/RWO、ReadOnlyMany/ROX、ReadWriteMany/RWX)
  • 回收策略:决定 PVC 释放后 PV 的处理方式(Retain、Delete、Recycle)
  • 存储类别:通过 storageClassName 实现动态供应

PVC 概念

PersistentVolumeClaim (PVC) 是用户对存储资源的声明(申请),描述应用所需的存储特性(容量、访问模式等),不关心底层实现细节。PVC 与 Pod 同生命周期,当 Pod 被删除时,关联的 PVC 通常也会被删除(除非显式保留)。PVC 通过匹配机制与 PV 绑定,绑定后 PV 进入专有状态,不能被其他 PVC 使用

StorageClass概念

K8S有两种存储资源的供应模式:静态模式和动态模式,资源供应的最终目的就是将适合的PV与PVC绑 定:

  • 静态模式:管理员预先创建许多各种各样的PV,等待PVC申请使用。
  • 动态模式:管理员无须预先创建PV,而是通过StorageClass自动完成PV的创建以及与PVC的绑定。

PV 与 PVC 的关系

  • PV 是仓库:由管理员预先建立的存储资源池
  • PVC 是租约:用户通过声明获取存储使用权
  • StorageClass 是自动化系统:动态按需创建 PV 的模板机制

PV 与 PVC 生命周期

PV 和 PVC 的交互遵循明确的五阶段生命周期

  1. Provisioning(配置)

    • 静态配置:管理员手动创建 PV,适用于固定规模存储需求
    • 动态配置:通过 StorageClass 自动创建 PV,响应 PVC 请求
  2. Binding(绑定)

    • Kubernetes 控制平面匹配 PVC 与符合条件的 PV
    • 绑定后形成一对一独占关系,PV 状态变为 Bound
  3. Using(使用)

    • Pod 通过 volumes 字段引用 PVC
    • 存储卷被挂载到容器指定路径
    • 启用 StorageProtection 准入控制可防止误删使用中的 PVC
  4. Releasing(释放)

    • Pod 删除后,PVC 被释放
    • PV 根据回收策略进入 Released 或 Failed 状态
  5. Recycling(回收)

    • Retain:保留 PV 和数据,需手动清理(生产环境推荐)

    • Delete:自动删除 PV 及底层存储(需存储插件支持)

    • Recycle(已弃用):删除数据后重置 PV 为 Available

访问模式

模式缩写描述适用存储类型
ReadWriteOnceRWO单节点读写本地磁盘、云盘
ReadOnlyManyROX多节点只读配置文件、日志
ReadWriteManyRWX多节点读写NFS、Ceph

注意:不同存储类型支持的访问模式不同,例如 HostPath 不支持 ROX/RWX,iSCSI 不支持 RWX

回收策略

决定 PVC 删除后 PV 和数据的命运

  • Retain
    • 保留 PV 和存储数据
    • 管理员需手动执行:1) 清理数据 2) 删除 PV 3) 重建 PV
    • 适用场景:数据敏感型应用(如数据库)
  • Delete
    • 自动删除 PV 及后端存储资源
    • 依赖存储插件支持(AWS EBS、GCE PD 等)
    • 风险:数据不可逆丢失
  • Recycle(Kubernetes 1.15+ 已弃用)
    • 自动删除卷内数据(执行 rm -rf /volume/*)
    • 仅适用于 NFS 和 HostPath
    • 替代方案:使用动态配置的 StorageClass
    • 当删除pvc后塔会自动下载一个镜像
yaml
apiVersion: v1                  # Kubernetes API 版本
kind: PersistentVolume          # 资源类型为持久卷(PV)
metadata:
  name: nfs-pv                  # PV 的名称,集群内唯一标识
spec:
  persistentVolumeReclaimPolicy: Retain/Delete/Recycle		# 回收策略

NFS 服务器部署

在 Kubernetes 中使用 NFS 存储前,需要先部署和配置 NFS 服务器。以下是基于 openEuler/CentOS 系统的标准配置流程:

1. NFS 服务器安装

bash
# 安装NFS服务器软件包
sudo yum install -y nfs-utils

# 创建共享目录并设置权限
sudo mkdir -p /datanfs/k8s
sudo chown nobody:nobody /datanfs/k8s

2. 配置共享目录

编辑 /etc/exports 文件,添加共享配置:

cat > /etc/exports << EOF
/datanfs/k8s 192.168.148.0/24(rw,sync,all_squash,anonuid=65534,anongid=65534,no_subtree_check)
EOF

关键参数解析

  • rw:允许读写操作
  • sync:同步写入,保证数据一致性
  • all_squash:将所有客户端用户映射为匿名用户
  • anonuid/anongid:指定映射的UID/GID(通常为nobody用户)
  • no_subtree_check:提高性能,不检查子目录权限

3. 启动NFS服务

bash
# 应用导出配置并启动服务
sudo exportfs -a
sudo systemctl enable nfs-server --now

# 验证共享配置
exportfs -v

4. 客户端节点配置

所有 Kubernetes 节点需要安装 NFS 客户端工具:

bash
sudo yum install -y nfs-utils

可通过手动挂载测试NFS共享是否可用:

bash
mkdir -p /mnt/nfs
mount -t nfs 192.168.148.97:/datanfs/k8s /mnt/nfs/

Pod 基础挂载NFS

资源清单

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-nfs
  labels:
    app: demo-nfs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo-nfs
  template:
    metadata:
      name: demo-nfs
      labels:
        app: demo-nfs
    spec:
      volumes:
        - name: demo-nfs
          nfs:
            path: /datanfs/k8s				# NFS挂载的目录
            server: 192.168.148.183			# NFS 服务
      containers:
        - name: demo-nfs
          image: nginx
          imagePullPolicy: Never
          volumeMounts:
            - name: demo-nfs
              mountPath: /usr/share/nginx/html/

测试挂载

  1. NFS服务端创建测试文件

    bash
    hostname -I | awk '{print $1}' > /datanfs/k8s/index.html
  2. kubernetes集群测试

    bash
    # 查看刚才创建的pod的IP地址
    kubectl get pod -o wide
    # http协议请求网页
    curl 10.244.58.100

静态 PV 配置

创建 PV(NFS 示例)

yaml
apiVersion: v1                  # Kubernetes API 版本
kind: PersistentVolume          # 资源类型为持久卷(PV)
metadata:
  name: nfs-pv                  # PV 的名称,集群内唯一标识
spec:
  storageClassName: demo-nfs-pv	# 
  capacity:                     # 定义存储容量
    storage: 50Mi               # 分配存储空间
  accessModes:                  # 访问模式配置
    - ReadWriteMany             # 允许多个节点同时读写(RWX)
  persistentVolumeReclaimPolicy: Retain  # PVC释放后PV的处理策略:保留数据
  nfs:                          # NFS存储配置段
    path: /data/volumes/v1      # NFS服务器上的导出路径
    server: 192.168.148.183     # NFS服务器IP地址

创建 PVC

yaml
apiVersion: v1                  # Kubernetes API 版本
kind: PersistentVolumeClaim     # 资源类型为持久卷声明(PVC)
metadata:
  name: app-pvc                 # PVC 的名称,在命名空间内唯一
spec:
  storageClassName: demo-nfs-pv	# 这里要和PV的storageClassName保持相同
  accessModes:                  # 请求的访问模式
    - ReadWriteMany             # 需要支持多节点读写(RWX)
  resources:                    # 资源请求配置
    requests:
      storage: 50Mi              # 申请50MB存储空间(需匹配PV容量)
  # 注意:此示例未指定storageClassName,将匹配可用的未分类PV

Pod 挂载 PVC

yaml
apiVersion: v1                  # Kubernetes API 版本
kind: Pod                      # 资源类型为Pod
metadata:
  name: web-server             # Pod名称
spec:
  containers:
  - name: nginx                # 容器名称
    image: nginx               # 使用官方nginx镜像
    imagePullPolicy: Never
      resources:
        limits:
          cpu: 200m
          memory: 200Mi
        requests:
          cpu: 200m
          memory: 200Mi
    volumeMounts:              # 卷挂载配置
    - name: data               # 引用下面定义的卷名称
      mountPath: /usr/share/nginx/html  # 挂载到容器内的路径
  volumes:                     # Pod级存储卷定义
  - name: data                 # 卷名称,与volumeMounts对应
    persistentVolumeClaim:      # PVC类型卷
      claimName: app-pvc       # 使用名为app-pvc的PVC
      # readOnly: false        # 默认可读写(可显式声明)

测试

bash
# 写入测试用例
hostname -I > /data/volumes/v1/index.html

# 获取pod的IP地址
kubectl get pod -o wide

# curl请求NFS挂载的文件(NFS属于网络存储会有延迟)
curl 10.224.85.248

动态 PV 配置(StorageClass)

因为Kubernetes自己不自带NFS动态供给的驱动,所以我们需要下载第三方的NFS动态供给驱动。Kubernetes官方推荐了两个第三方的驱动可供选择。

安装 nfs-subdir-external-provisioner

helm安装

bash
# 创建命名空间
kubectl create ns nfs-provisioner

# 安装
helm install nfs-subdir-external-provisioner \
  nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  --namespace kube-system \
  --set nfs.server=<NFS-SERVER-IP> \
  --set nfs.path=<NFS-SHARE-PATH> \
  --set storageClass.name=nfs-client \
  --set storageClass.defaultClass=true \
  --set image.repository=registry.k8s.io/sig-storage/nfs-subdir-external-provisioner \
  --set image.tag=v4.0.2


#注:需要替换国内镜像
crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisionerv4.0.2

# 卸载nfs-subdir-external-provisioner
helm uninstall nfs-subdir-external-provisioner \
 -n nfs-provisioner

参数说明:

  • nfs.server: NFS 服务器 IP 地址
  • nfs.path: NFS 共享路径
  • storageClass.name: 存储类名称
  • storageClass.defaultClass: 设为默认存储类
  • image.repository: 使用官方镜像仓库
  • image.tag: 指定版本

源码部署

bash
wget https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/download/nfs-subdir-external-provisioner-4.0.18/nfs-subdir-external-provisioner-4.0.18.tgz

tar -zxvf nfs-subdir-external-provisioner-4.0.18.tgz


# 解压后进入到deploy目录中操作
修改命名空间
bash
# 创建命名空间
kubectl create namespace nfs-provisioner

# 替换默认的default命名空间
sed -i 's/namespace: default/namespace: nfs-provisioner/g' `grep -rl 'namespace:
default' ./`
修改deployment.yaml
bash
# 与RBAC文件中的namespace保持一致
# 注:需要替换国内镜像
crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2
# 或
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisionerv4.0.2

# 下面是需要修改的地方
# deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  namespace: default  # 需与RBAC中的namespace保持一致
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner  # 必须与RBAC中创建的ServiceAccount一致
      containers:
      - name: nfs-client-provisioner
        image: crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2 # 修正镜像地址格式
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: k8s-sigs.io/nfs-subdir-external-provisioner  # 需与StorageClass的provisioner字段匹配
        - name: NFS_SERVER
          value: 192.168.148.183  		# NFS服务器IP
        - name: NFS_PATH
          value: /datanfs/k8s       	# NFS共享路径
      volumes:
      - name: nfs-client-root
        nfs:
          server: 192.168.148.183  		# 需与env中NFS_SERVER一致
          path: /datanfs/k8s         	# 需与env中NFS_PATH一致
部署
bash
# 批量安装
kubectl apply -k .
查看部署情况
bash
kubectl get all -o wide -n nfs-provisioner
查询动态供应存储类
bash
kubectl get storageclass

创建一个测试pod

yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pv-pod  # Pod名称,在命名空间内需唯一
spec:
  volumes:  # 定义Pod级别的存储卷
    - name: demo-pv  # 卷名称,与volumeMounts对应
      persistentVolumeClaim:  # 使用PVC类型的存储卷
        claimName: demo-nfs-pv  # 引用的PVC名称,必须与下面定义的PVC一致
  
  containers:
    - name: demo-pod  # 容器名称
      image: nginx  # 使用官方nginx镜像
      imagePullPolicy: Never  # 镜像拉取策略:Never表示使用本地镜像
      
      resources:  # 资源限制配置
        limits:   # 容器最大可用资源
          cpu: 200m  # 0.2个CPU核心
          memory: 200Mi  # 200MB内存
        requests: # 容器启动最小需求资源
          cpu: 200m
          memory: 200Mi
      
      volumeMounts:  # 容器内的卷挂载点
        - name: demo-pv  # 引用上面定义的卷名称
          mountPath: /usr/share/nginx/html  # 挂载到容器内的路径(nginx默认网站目录)
          # readOnly: false  # 默认可读写,如需只读可设置为true

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-nfs-pv  # PVC名称,需与Pod中引用的名称一致
spec:
  storageClassName: nfs-client  # 指定使用的StorageClass名称(需提前创建)
  accessModes:    # 访问模式配置
    - ReadWriteOnce  # 单节点读写模式(RWO)
  
  resources:
    requests:
      storage: 30Mi  # 申请30MB存储空间
      # 注意:实际分配空间取决于StorageClass和PV配置
      # 动态配置时会自动创建符合要求的PV

# ----------------------------
# 使用说明:
# 1. 需提前部署NFS Provisioner并创建名为nfs-client的StorageClass
# 2. 应用配置:
#    kubectl apply -f demo-pvc-pod.yaml
# 3. 验证:
#    kubectl get pvc demo-nfs-pv  # 查看PVC是否Bound
#    kubectl exec -it demo-pv-pod -- ls /usr/share/nginx/html  # 查看挂载内容
# 4. 清理:
#    kubectl delete pod demo-pv-pod
#    kubectl delete pvc demo-nfs-pv

测试

bash
# 写入测试用例
hostname -I > /datanfs/k8s/default-demo-nfs-pv-pvc-578cb0b6-5661-4382-9f82-b6c0f7c9bc2c/index.html

# 获取pod的IP地址
kubectl get pod -o wide

# curl请求NFS挂载的文件(NFS属于网络存储会有延迟)
curl 10.224.58.207

默认存储

默认存储是当PVC未指定storageClassName的情况下默认使用的存储

创建默认存储

yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client				# 这里的名字如果存在的话会直接将原有的存储变成默认的
  namespace: nfs-provisioner
  annotations:
    storageclass.kubernetes.io/is-default-class: "true" # 设为默认存储类
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须与deployment中的PROVISIONER_NAME一致
parameters:
  archiveOnDelete: "false"

创建测试pod

yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-default-pv-pod
spec:
  volumes:
    - name: demo-default-pv
      persistentVolumeClaim:
        claimName: demo-default-nfs-pv
  containers:
    - name: demo-pod
      image: nginx
      imagePullPolicy: Never
      resources:
        limits:
          cpu: 200m
          memory: 200Mi
        requests:
          cpu: 200m
          memory: 200Mi
      volumeMounts:
        - name: demo-default-pv
          mountPath: /usr/share/nginx/html
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-default-nfs-pv
spec:
 storageClassName: nfs-default-client		# 这里指定存储类名
 accessModes:
   - ReadWriteOnce
 resources:
   requests:
     storage: 30Mi