5-4 Kubernetes 持久卷(PV)和持久卷申领(PVC)

更新时间:2023年3月

简介

参考:持久卷 | Kubernetes

简单示例:配置 Pod 以使用 PersistentVolume 作为存储 | Kubernetes

持久卷(PersistentVolume,PV)、**持久卷申领(PersistentVolumeClaim,PVC)**用于实现应用程序与存储的解耦,将存储的细节信息从应用实例中剥离

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用 存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统

持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参考 访问模式

通常不同用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷,集群需要能够提供不同性质的 PersistentVolume, 并且这些 PV 卷之间的差别对用户侧暴露仅限于卷大小和访问模式,同时又不能将卷的实现细节暴露给用户。为了满足这类需求,就有了存储类(StorageClass)

总结

持久卷(PersistentVolume,PV) 抽象了存储资源,屏蔽了存储资源的细节。整合不同的存储资源,统一存储资源暴露的属性

**持久卷申领(PersistentVolumeClaim,PVC)**抽象了用户对存储资源的需求,统一存储需求的属性

**存储类(StorageClass)**可以动态制备 持久卷(PersistentVolume,PV),进一步抽象存储资源

静态 PV 与动态 PV

静态 PV

手动创建 PV,然后创建 PVC 并绑定到指定的 PV。PVC 挂载至 Pod 使用。适用于 PV 和 PVC 比较固定的场景

动态 PV

先创建一个 StorageClass(存储类),创建 PVC 时指定 StorageClass,由 StorageClass 动态创建符合 PVC 申请条件的 PV ,并将 PVC 绑定到 PV

对比图

在这里插入图片描述

PV

参考:持久卷 | Kubernetes

PV 的类型

参考:持久卷 - 持久卷类型 | Kubernetes

PV 持久卷通过插件的形式来实现与不同存储资源的连接调度。Kubernetes 目前支持以下插件:

  • cephfs - CephFS volume
  • csi - 容器存储接口 (CSI)
  • fc - Fibre Channel (FC) 存储
  • hostPath - HostPath 卷 (仅供单节点测试使用;不适用于多节点集群;请尝试使用 local 卷作为替代)
  • iscsi - iSCSI (SCSI over IP) 存储
  • local - 节点上挂载的本地存储设备
  • nfs - 网络文件系统 (NFS) 存储
  • rbd - Rados 块设备 (RBD) 卷

Kubernetes 支持的插件随版本变化较大,使用前请先查看当前使用的版本是否支持对应的存储插件

阶段

每个 PV 会处于以下阶段(Phase)之一

  • Available(可用)

    PV 是一个空闲资源,尚未绑定到任何申领

  • Bound(已绑定)

    该 PV 已经绑定到某 PVC

  • Released(已释放)

    所绑定的 PVC 已被删除,但是资源尚未被集群回收

  • Failed(失败)

    PV 的自动回收操作失败

命令行接口能够显示绑定到某 PV 卷的 PVC 对象

简单示例

PV 对象 yaml 文件示例

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  # 容量
  capacity:
    storage: 5Gi
  # 卷模式
  volumeMode: Filesystem
  # 访问模式
  accessModes:
    - ReadWriteOnce
  # 回收策略
  persistentVolumeReclaimPolicy: Recycle
  # 指定存储类
  storageClassName: slow
  # 挂载选项
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2

注:在集群中使用 PV 存储通常需要一些特定于具体卷类型的辅助程序。 在这个例子中,PV 是 NFS 类型的,因此需要辅助程序 /sbin/mount.nfs 来支持挂载 NFS 文件系统

主要参数

容量

每个 PV 卷都有确定的存储容量。 可以使用 PV 对象的 capacity 属性来设置

目前,存储大小是可以设置和请求的唯一一个资源。 未来可能会包含 IOPS、吞吐量等属性

卷模式

Kubernetes 目前支持两种卷模式,由

  • Filesystem(文件系统)
  • Block(块)

卷模式由 volumeMode 参数指定。 如果该参数未设置,默认的卷模式是 Filesystem

volumeMode 属性设置为 Filesystem 的卷会被 Pod 挂载(Mount)到某个目录。 如果卷的存储来自某块设备而该设备目前为空,Kuberneretes 会在第一次挂载卷之前在设备上创建文件系统

volumeMode 设置为 Block 时。卷以块设备的方式交给 Pod 使用,其上没有任何文件系统。Pod 中运行的应用必须知道如何处理原始的块设备。支持以块设备形式挂载的插件请参考:持久卷 - 原始块卷支持 | Kubernetes

访问模式

PV 可以用资源提供者所支持的任何访问方式挂载到宿主系统上,由参数 volumeMode 指定。不同存储资源插件支持的访问模式,可以参考 持久卷 - 访问模式 | Kubernetes

访问模式有:

  • ReadWriteOnce (RWO)

    卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷

  • ReadOnlyMany (ROX)

    卷可以被多个节点以只读方式挂载

  • ReadWriteMany (RWX)

    卷可以被多个节点以读写方式挂载

  • ReadWriteOncePod (RWOP)

    卷可以被单个 Pod 以读写方式挂载。 该模式仅支持 CSI 卷,且需要 Kubernetes 1.22 以上版本。如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式

注:每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持多种访问模式。 例如,一个 GCEPersistentDisk 卷可以被某节点以 ReadWriteOnce 模式挂载,或者被多个节点以 ReadOnlyMany 模式挂载,但不可以同时以两种模式挂载

存储类(StorageClass)

每个 PV 可以属于某个存储类(Class),通过将 storageClassName 参数设置为某个 StorageClass 的名称来指定。特定存储类的 PV 只能绑定到请求该类存储卷的 PVC 。 未设置 storageClassName 的 PV 没有类的设定,只能绑定到那些没有指定特定存储类的 PVC

回收策略

PV 通过 persistentVolumeReclaimPolicy 参数指定回收策略,目前支持的回收策略有

  • Retain:手动回收
  • Recycle :基本擦除 (rm -rf /thevolume/*)
  • Delete :诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除

目前,仅 NFS 和 HostPath 支持回收(Recycle)

挂载选项

可以通过 mountOptions 参数指定 PV 被挂载到节点上时使用的附加挂载选项

注:并非所有持久卷类型都支持挂载选项

以下卷类型支持挂载选项:

  • awsElasticBlockStore
  • azureDisk
  • azureFile
  • cephfs
  • cinder(于 v1.18 弃用
  • gcePersistentDisk
  • iscsi
  • nfs
  • rbd
  • vsphereVolume

Kubernetes 不对挂载选项执行合法性检查。如果挂载选项是非法的,挂载就会失败

PVC

简单示例

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  # 访问模式
  accessModes:
    - ReadWriteOnce
  # 卷模式
  volumeMode: Filesystem
  # 资源申请
  resources:
    requests:
      storage: 8Gi
  # 存储类
  storageClassName: slow
  # 选择器
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}

主要参数

访问模式

PVC 的访问模式指定申请的卷应该具有的访问模式,在请求具有特定访问模式的存储时,只会绑定到具有相同访问模式的 PV 上

卷模式

PVC 定义申请卷的类型,来表明是将卷作为文件系统还是块设备来使用。从而绑定到相同的卷模式的 PV 上,默认为 Filesystem

资源申请

PVC 和 Pod 一样,也可以请求特定数量的资源,或者限制资源的使用最大值。PVC 请求的资源是存储

选择器

PV 可以通过选择器来进一步过滤要绑定的卷的集合,只有标签(Label)与选择器(Selector)相匹配的卷才会绑定 PVC。选择器可以包含两个字段

  • matchLabels :卷必须包含带有此值的标签
  • matchExpressions :通过设定键(key)、值列表和操作符(operator) 来构造的需求。合法的操作符有 In、NotIn、Exists 和 DoesNotExist

matchLabelsmatchExpressions 的所有需求都按逻辑与的方式组合在一起。 这些需求都必须被满足才被视为匹配

存储类(StorageClass)

PVC 可以通过为 storageClassName 属性设置 StorageClass 的名称来请求特定的存储类。 只有所请求的类的 PV ,即 storageClassName 值与 PVC 设置相同的 PV , 才能绑定到 PVC

PVC 可以不指定存储类,或者 storageClassName 属性值设置为 ""。这一 PVC 只能绑定到未设置存储类的 PV (未声明或者 storageClassName"")。没有 storageClassName 的 PVC 并不完全相同,集群对它的处理方式也不同,这取决于是否启用了 DefaultStorageClass 准入控制器插件)

  • 启用 DefaultStorageClass 准入控制器插件时,管理员可以设置一个默认的 StorageClass(默认存储类)。 所有未设置 storageClassName 的 PVC 都只能绑定到隶属于默认存储类的 PV 。 设置默认 StorageClass 的工作是通过将对应 StorageClass 对象的注解 storageclass.kubernetes.io/is-default-class 赋值为 true 来完成的。 如果管理员未设置默认存储类,集群对 PVC 创建的处理方式与未启用准入控制器插件时相同。 如果设定的默认存储类不止一个,准入控制插件会禁止所有创建 PVC 操作

  • 关闭 DefaultStorageClass 准入控制器插件时,则不存在默认 StorageClass (默认存储类)。 所有将 storageClassName 设为 "" 的 PVC 只能被绑定到也将 storageClassName 设为 "" 的 PV。

    注:一旦默认的 StorageClass 由不可用变为可用,建议稍后更新缺少 storageClassName 的 PVC,因为如果这个 PVC 更新了,它将不再绑定到也将 storageClassName 设为 "" 的 PV,可能产生数据丢失风险

当某 PVC 除了请求 StorageClass 之外还设置了 selector,则这两种需求会按逻辑与关系处理: 只有隶属于所请求存储类且带有所请求标签的 PV 才能绑定到 PVC

注:目前 kubernetes 版本(< 1.26),设置了非空 selector 的 PVC 对象无法让集群为其动态制备 PV


示例-静态制备存储卷

此示例使用 NFS 提供存储资源

编写对象 yaml 声明文件

PV

$ vim test-static-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: test-static-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  mountOptions:
    - soft
    - nfsvers=4.2
  nfs:
    # 共享目录需在 NFS 服务端创建并配置
    path: /data/share/static
    server: 192.168.111.171

PVC

$ vim test-static-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-static-pvc
spec:
  volumeName: test-static-pv
  accessModes:
    - ReadWriteMany
  # 卷模式
  volumeMode: Filesystem
  # 资源申请
  resources:
    requests:
      storage: 3Gi
    limits:
      storage: 5Gi

应用

$ vim nginx-deploy-static-storage.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy-static-storage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.22.1
        ports:
        - containerPort: 80        
        volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: static-dir
      volumes:
      - name: static-dir
        persistentVolumeClaim:
          claimName: test-static-pvc

创建对象

# 创建 PV
$ kubectl apply -f test-static-pv.yaml

# 创建 PVC
$ kubectl apply -f test-static-pvc.yaml

# 创建应用,使用 PVC 
$ kubectl apply -f nginx-deploy-static-storage.yaml

检查

# 检查 pod 运行状态
$ kubectl get pods -o wide
NAME                                           READY   STATUS    RESTARTS   AGE   IP              NODE              NOMINATED NODE   READINESS GATES
nginx-deploy-static-storage-6fc7767456-h9cmf   1/1     Running   0          34s   172.20.195.36   192.168.111.187   <none>           <none>


# 测试往共享目录写入数据
$ kubectl exec -it nginx-deploy-static-storage-6fc7767456-h9cmf bash
root@nginx-deploy-static-storage-6fc7767456-h9cmf:/# echo "${HOSTNAME}" > /usr/share/nginx/html/index.html


# NFS 服务器查看数据是否写入
$ ll /data/share/static/
total 4
-rw-r--r--. 1 nfsnobody nfsnobody 45 Mar 10 20:35 index.html


# 测试访问
$ curl http://172.20.195.36:80/index.html 
nginx-deploy-static-storage-6fc7767456-h9cmf

示例-动态制备存储卷

此示例使用 NFS 提供存储资源

NFS 服务器设置

# 创建共享目录
$ mkdir -p /data/share/dynamic

# 授权
$ chmod o+w /data/share/dynamic

$ cat /etc/exports
/data 192.168.111.0/24(rw)

# 重启服务,nfs 依赖于 rpcbind,先重启 rpcbind
$ systemctl restart rpcbind
$ systemctl restart nfs

# 导出 nfs 维护表,重新共享目录
$ exportfs -r

准备制备器

Kubernetes 不包含内部 NFS 驱动(制备器,参考:存储类 - provisioner | Kubernetes)。需要使用外部驱动为 NFS 创建 StorageClass。可以参考以下示例 :

此处使用 nfs-subdir-external-provisioner 作为 provisioner,安装流程参考:kubernetes-sigs/nfs-subdir-external-provisioner: Dynamic sub-dir volume provisioner on a remote NFS server. (github.com)

下载

# 下载安装的 yaml 
$ wget https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/archive/refs/tags/nfs-subdir-external-provisioner-4.0.17.tar.gz



# 解压
$ tar -zxvf nfs-subdir-external-provisioner-4.0.17.tar.gz

$ mv nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.17 nfs-subdir-external-provisioner

$ cd nfs-subdir-external-provisioner

授权

# 将 RBAC 对象配置部署在当前命名空间,默认在 default
$ NS=$(kubectl config get-contexts|grep -e "^\*" |awk '{print $5}')
$ NAMESPACE=${NS:-default}
$ sed -i'' "s/namespace:.*/namespace: $NAMESPACE/g" ./deploy/rbac.yaml ./deploy/deployment.yaml

# 创建 RBAC 对象
$ kubectl create -f deploy/rbac.yaml

安装配置

安装 nfs-client-provisioner,注意需要配置镜像地址和 NFS 连接信息

$ vim ./deploy/deployment.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: registry.k8s.io/sig-storage/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
            # 设置环境变量 NFS_SERVER 为 NFS 的地址或域名
            - name: NFS_SERVER
              value: 192.168.111.171
            # 设置环境变量 NFS_PATH 为 NFS 共享目录
            - name: NFS_PATH
              value: /data/share/dynamic
      volumes:
        - name: nfs-client-root
          nfs:
            # 设置 NFS 地址和共享目录
            server: 192.168.111.171
            path: /data/share/dynamic

应用声明

$ kubectl apply -f ./deploy/deployment.yaml

部署存储类

配置 StorageClass

nfs-subdir-external-provisioner 支持的StorageClass 参数请参考:nfs-subdir-external-provisioner

$ vim deploy/class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
reclaimPolicy: Retain # PV 的删除策略
mountOptions: 
  - soft
  - nfsvers=4.2
  - noatime # 访问文件时不更新文件 inode 中的时间戳,高并发环境可提高性能
parameters:
  pathPattern: "${.PVC.namespace}/${.PVC.annotations.nfs.io/storage-path}" # waits for nfs.io/storage-path annotation, if not specified will accept as empty string.
  archiveOnDelete: "true"  # 删除 pod 时保留 pod 数据,默认为 false,不保留数据 

应用声明

$ kubectl apply -f deploy/class.yaml 

# 检查状态
$ kubectl get sc
NAME         PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-client   k8s-sigs.io/nfs-subdir-external-provisioner   Retain          Immediate           false                  8s

创建 PVC

配置 PVC 声明文件

$ vim demo-dynamic-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: demo-dynamic-pvc
  annotations:
    nfs.io/storage-path: "test-path" # 配合 StorageClass 的 pathPattern 参数,自定义路径
spec:
  storageClassName: nfs-client # 调用的 StorageClass 名称
  accessModes:
    - ReadWriteMany # 访问权限
  resources:
    requests:
      storage: 500Mi # 空间大小

应用声明

$ kubectl apply -f demo-dynamic-pvc.yaml

# 查看状态
$ kubectl get pvc
NAME               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
demo-dynamic-pvc   Bound    pvc-54b4c5da-f64c-4a65-9de8-2cbf6fbb6dc7   500Mi      RWX            nfs-client     41s

# 查看绑定的 PV 
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS   REASON   AGE
pvc-54b4c5da-f64c-4a65-9de8-2cbf6fbb6dc7   500Mi      RWX            Retain           Bound    default/demo-dynamic-pvc   nfs-client              69s

挂载使用

创建应用的声明文件

$ vim nginx-deploy-svc-dynamic-pvc.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    app: nginx-deploy
  name: nginx-deploy-dynamic-pvc
spec:
  replicas: 1 
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.22.1 
          volumeMounts:
          - mountPath: "/usr/share/nginx/html/dynamic"
            name: dynamic-dir
      volumes:
        - name: dynamic-dir
          persistentVolumeClaim:
            claimName: demo-dynamic-pvc

---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: nginx-svc
  name: nginx-svc
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: nginx

应用声明

$ kubectl apply -f nginx-deploy-svc-dynamic-pvc.yaml


# 检查运行情况
$ kubectl get pods
NAME                                           READY   STATUS    RESTARTS   AGE
nginx-deploy-dynamic-pvc-9b9cd67bc-44htl       1/1     Running   0          56s


$ kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
nginx-svc    NodePort    10.68.52.65   <none>        80:30080/TCP   59s

测试

在 pod 中写入数据

$ kubectl exec -it nginx-deploy-dynamic-pvc-9b9cd67bc-44htl bash
root@nginx-deploy-dynamic-pvc-9b9cd67bc-44htl:/# echo "${HOSTNAME}" > /usr/share/nginx/html/dynamic/index.html

在 NFS 服务器上检查数据是否正常写入

$ ll /data/share/dynamic/default/test-path/
total 4
-rw-r--r--. 1 nfsnobody nfsnobody 41 Mar 11 02:23 index.html

测试访问

$ curl http://192.168.111.185:30080/dynamic/index.html
nginx-deploy-dynamic-pvc-9b9cd67bc-44htl

删除 Pod,看数据是否保留

$ kubectl delete -f nginx-deploy-svc-dynamic-pvc.yaml

# 由于 StorageClass 的回收策略为 Retain,不会删除数据,数据依旧存在,
$ ll /data/share/dynamic/default/test-path/
total 4
-rw-r--r--. 1 nfsnobody nfsnobody 41 Mar 11 02:23 index.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值