K8s: PersistantVolume, PersistentVolumeClaim, Storage Class的相关概念与应用

PersistantVolume


1 )概述

  • PersistentVolume 持久化存储对象
  • 在使用 nfs 的时候不是给一个项目使用的,而是全公司项目服务的
  • 这就会涉及到一些项目隔离的问题,比如私有的目录,不被项目团队之外的人访问到
  • 同时又不希望自己去维护那些目录,希望管理员给我做好这个事情
  • 只要告诉管理员,用什么样的资源,要多大的空间内存空间,让管理员提供这么一个环境
  • 相当于我把整个NFS这个目录把它拆分成了若干块这种隔离的存储
  • 管理员事先提供这样的隔离存储,然后供给不同的团队来用

2 )应用

  • $ vi pv-demo1.yaml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv-demo
    spec:
      capacity:
        storage: 200Mi # 这里可以调大,比如 5Gi
      accessModes:
      - ReadWriteOnce # 一次只由一个 work node 去读写,保证读写不会冲突
      persistentVolumeReclaimPolicy: Recycle
      nfs:
        path: /nfsdata
        server: master.k8s # master节点配置的hostname
    
  • $ kubectl apply -f pv-demo1.yaml

    persistentvolume/pv-demo created
    
  • $ kubectl get pv pv-demo

    NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    pv-demo   200Mi      RWO            Recycle          Available                                   81s
    
    • 可以看到,这里的状态是 available 是可以申领的状态

PersistentVolumeClaim


1 )概述

  • PVC (PersistentVolumeClaims),这里 claim,就是申领,申请的意思
  • PV 表示:管理员事先划分好了一堆硬盘做在什么办公室等着大家来用。
  • PVC 表示: 员工排的队过来领新的硬盘, 需要声明领取什么型号类型的硬盘,硬盘由哪些功能
  • PV 和 PVC它的设计就是面向一个组织,一个集群模式下设计的

2 )应用

  • PVC 的申请

  • $ vi pvc-demo1.yaml

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: pvc-demo
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi # 申领 1G 的空间
    
  • 注意,要先由pv, 之后才能创建 pvc

  • $ kubectl apply -f pvc-demo1.yaml

    persistentvolumeclaim/pvc-demo created
    
  • $ kubectl get pv pv-demo

    NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
    pv-demo   2Gi        RWO            Recycle          Bound    default/pvc-demo                           2m23s
    
    • 这里可以看到,STATUS 已经是 Bound 了
    • 只要有人申领磁盘,这个状态就变为 绑定状态
  • $ kubectl get pvc pvc-demo

    NAME       STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    pvc-demo   Bound    pv-demo   2Gi        RWO                           3m17s
    
    • 现在不用关心底层的PV存储在哪里,我只需要知道有 pvc-demo 就可以调用了
    • 这里屏蔽了对存储资源的细节的管理

3 ) 相关概念

  • 访问模式
    • Claim在请求具有特定访问模式的存储时,使用与卷相同的访问模式约定
  • 卷模式
    • Claim使用与卷相同的约定来表明是将卷作为文件系统还是块设备来使用
  • 资源
    • Claim和 Pod 一样,也可以请求特定数量的资源。在这个上下文中,请求的资源是存储
    • 卷和Claim都使用相同的资源模型
  • 选择算符
    • Claim可以设置标签选择算符 来进一步过滤卷集合
    • 只有标签与选择算符相匹配的卷能够绑定到Claim上
    • 选择算符包含两个字段:
      • matchLabels - 卷必须包含带有此值的标签
      • matchExpressions - 通过设定键(key)、值列表和操作符(operator) 来构造的需求
        • 合法的操作符有 In、NotIn、Exists 和 DoesNotExist
    • 来自 matchLabels 和 matchExpressions 的所有需求都按逻辑与的方式组合在一起
    • 这些需求都必须被满足才被视为匹配
    • Claim可以通过为 storageClassName 属性设置 StorageClass 的名称来请求特定的存储类
    • 只有所请求的类的 PV 卷,即 storageClassName 值与 PVC 设置相同的 PV 卷, 才能绑定到 PVC Claim

4 )总结

  • PVC Claim不必一定要请求某个类
  • 如果 PVC 的 storageClassName 属性值设置为 “”, 则被视为要请求的是没有设置存储类的 PV 卷
  • 因此这一 PVC Claim只能绑定到未设置 存储类的 PV 卷
  • (未设置注解或者注解值为 “” 的 PersistentVolume(PV)对象在系统中不会被删除,因为这样做可能会引起数据丢失。
  • 未设置 storageClassName 的 PVC 与此大不相同,也会被集群作不同处理
  • 具体筛查方式取决于 DefaultStorageClass 准入控制器插件是否被启用
  • 当某 PVC 除了请求 StorageClass 之外还设置了 selector,则这两种需求会按 逻辑与关系处理
  • 只有隶属于所请求类且带有所请求标签的 PV 才能绑定到 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]}
    
  • 持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求,概念上与 Pod 类似
  • Pod会耗用节点资源,而 PVC 申领会耗用 PV 资源
  • Pod 可以请求特定数量的资源(CPU 和内存),同样 PVC 申领也可以请求特定的大小和访问模式
    • 例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany
    • 这些模式之一来挂载,参见访问模式
  • 尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源
  • 常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷
  • 集群管理员需要能够提供不同性质的 PersistentVolume
  • 并且这些 PV 卷之间的差别不 仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户
  • 为了满足这类需求,就有了 存储类(StorageClass) 资源
  • 综上
    • 存储的管理是一个与计算实例的管理完全不同的问题
    • PersistentVolume 子系统为用户和管理员提供了一组 API,将存储如何供应的细节从其如何被使用中抽象出来
    • 为了实现这点,引入了两个新的 API资源:PersistentVolume 和 PersistentVolumeClaim

存储类 Storage Class


1 ) 概述

  • 回顾一下PV和PVC的关系
    • PV是管理员买了一堆磁盘
    • PVC是员工排着队,拿着这个PVC这个申请单找管理员要PV
    • 要到PV之后,PV就会在底层的真正存储进行一个磁盘的一个创建
    • 这个就是相关工作流程
  • Storage Class 面向更大规模的开发
    • 对于开发者来讲, 他其实只想知道pv的属性, volume的大小
    • 如果在大规模的 K8s 集群里面可能有成千上万个PVC
    • 这就意味着运维人员必须手动的创建这么多PV,然后会有新的PVC不断被提交
    • 运维人员就不断的添加新的PV,这样的话存在一个人工操作的问题,就是效率非常低
    • 对不同应用的存储性能要求可能不尽相同, 比如说独写速度,并发性能
    • 所以 K8s 又为我们引用了新的一个资源,StorageClass 存储类型
    • 这个就更抽象了,比PVC还要抽象一层
  • Storage Class 构成
    • PV的属性
      • 比如:存储类型, Volume的大小等
    • 创建这种PV需要用到的存储插件
      • K8s提供自动创建PV的机制:Dynamic Provisioning
      • 可以动态的发布创建存储
      • 解决了管理员手工操作的问题

2 )应用示例

  • $ vi sc-demo1.yaml

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: managed-nfs-storage
    provisioner: qgg-nfs-storage # 将存储变为可用存储
    parameters:
      archiveOnDelete: "false"
    
  • 注意,上面provisioner配置里的名称要和provisioner配置文件中的环境变量PROVISION_NAME保持一致

3 )PV, PVC,StorageClass 的协作流程

  • 现在分两个角色,一个是管理员,一个是用户
  • K8s 管理员首先会安装 Provisioner
    • 先看下什么是 provision,当一个机器买来,它是一台裸机,没有操作系统
    • 我装了一个操作系统,这个过程就叫 provision,就是把东西变得可用
    • 装好动态能够创建磁盘的这个工具之后,管理员还要定义有哪些 Storage Class
      • 比如说我手里有大概五十TB的存储,这些存储应该怎么定义
      • 有的存储是SSD的,有的是这个机械硬盘
      • 那我就分两个类,一个是 高速存储,一个是 低速存储
      • 通过 provision 能够动态的创建底层的这个PV的对象去调用实际的存储
  • 对于用户来讲,用户比如说是开发者来说,我创建了一个pod
    • 这个pod定义了PVC, PVC里面引入了一个 Storage Class
    • 这个 类型的比如是 SSD的, Storage Class 就会通过我的申领请求
    • 动态给我创建一块能用的磁盘来交付给我
  • 这就是它整个自动创建的一个流程

综合应用


场景:基于 PV,PVC,StorageClass来挂载这个NFS进行文件创建

1 )环境准备

2 )编写yaml文件进行应用的综合创建

$ vi app-nfs-demo1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default  #与RBAC文件中的namespace保持一致
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          # image: jmgao1983/nfs-client-provisioner
          # image: registry.cn-beijing.aliyuncs.com/qingfeng666/nfs-client-provisioner:v3.1.0
          image: dyrnq/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes # 容器中预置
          env:
            - name: PROVISIONER_NAME
              value: qgg-nfs-storage  #provisioner名称,请确保该名称与 nfs-StorageClass.yaml文件中的provisioner名称保持一致
            - name: NFS_SERVER
              value: master.k8s   #NFS Server IP地址
            - name: NFS_PATH
              value: /nfsdata   #NFS挂载卷
      volumes:
        - name: nfs-client-root
          nfs:
            server: master.k8s  #NFS Server IP地址
            path: /nfsdata    #NFS 挂载卷
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default        #根据实际环境设定namespace,下面类同
---
kind: ClusterRole # 定义了 集群角色
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
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: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects: # 定义绑定关系:绑定了服务账号
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
    # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: qgg-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
  archiveOnDelete: "false"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"   #与nfs-StorageClass.yaml metadata.name保持一致
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
---
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: nginx
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"   #创建一个SUCCESS文件后退出
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim  #与PVC名称保持一致

3 )关于yaml中配置的梳理

  • 创建 Persistent Volume Provisioner
    • 管理员预制一个Provisioner,是NFS客户端的一个provisier
    • 相当于它能够通过脚本自动化的创建一些NFS目录
  • 创建服务账号 (RBAC相关)
  • 创建集群角色 (RBAC相关)
    • 用户一定要有权限才能访问某一个空间的一些API接口
    • 这里定义了ClusterRole这个角色
    • 它能够获取存储的 ["get", "list", "watch", "create", "delete"] 这样的权限
    • 它能够调用 storage.k8s.io 下的 storageclasses 这个api接口
  • 创建集群角色绑定 (RBAC相关)
    • 绑定的是集群角色和服务账号 (nfs-client-provisioner)
    • 绑定之后,用户服务账号就具备了Cluster的相关权限
  • 创建普通角色 (RBAC相关)
    • 以获取endpoint这个终端机器的
    • ["get", "list", "watch", "create", "update", "patch"]
    • 等信息权限
  • 创建角色绑定 (RBAC相关)
    • 这个是普通应用级别角色,和集群角色不一样
  • 创建 Storage Class
    • VOLUMEBINDINGMODE 默认的模式是 Immediate, 就是立刻绑定
    • Storage Class 会调用 Provisioner 去创建 PV
    • 可见这种不同于之前的PVC去已创建好的PV中去申请
    • 这种就更为强大
  • 创建 PVC
    • 就是我的申请,代表我要申请一个什么样的资源
    • 这个是给 Pod 用
    • 可以看到 Storage Class 对应的是 managed-nfs-storage
    • 引用的是 上面的 Storage Class
  • 创建 Pod
    • 这个Pod去调用PVC,PVC 调用 Storage Class
    • Storage Class 对 PV Provisioner 请求资源
    • PV Provisioner 自动创造 PV
    • 当上述环境都搭建完成之后,基于以上
    • Pod就可以从PVC中申请存储了,这样创造出来的 PV 就被提供给 Pod 使用了

4 )执行并验证

  • $ kubectl apply -f app-nfs-demo1.yaml 创建

    deployment.apps/nfs-client-provisioner created
    serviceaccount/nfs-client-provisioner created
    clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
    clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
    role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
    rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
    storageclass.storage.k8s.io/managed-nfs-storage created
    persistentvolumeclaim/test-claim created
    pod/test-pod created
    
  • $ kubectl get all 查看具体部署状态

    NAME                                        READY   STATUS      RESTARTS   AGE
    pod/nfs-client-provisioner-cd49bdc7-ngnr4   1/1     Running     0          26m
    pod/test-pod                                0/1     Completed   0          26m
    
    NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    service/kubernetes   ClusterIP   10.1.0.1     <none>        443/TCP   7d8h
    
    NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/nfs-client-provisioner   1/1     1            1           26m
    
    NAME                                              DESIRED   CURRENT   READY   AGE
    replicaset.apps/nfs-client-provisioner-cd49bdc7   1         1         1       26m
    
  • $ kubectl logs pod/nfs-client-provisioner-cd49bdc7-ngnr4 | grep Provision 查看 Provision

    I0424 11:45:09.634105       1 event.go:278] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-claim", UID:"e325c456-f401-4f90-b684-5b06f2f100c2", APIVersion:"v1", ResourceVersion:"390293", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "default/test-claim"
    I0424 11:45:09.937907       1 event.go:278] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-claim", UID:"e325c456-f401-4f90-b684-5b06f2f100c2", APIVersion:"v1", ResourceVersion:"390293", FieldPath:""}): type: 'Normal' reason: 'ProvisioningSucceeded' Successfully provisioned volume pvc-e325c456-f401-4f90-b684-5b06f2f100c2
    
  • $ kubectl get storageclass 查看 Storage Class

    NAME                  PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    managed-nfs-storage   qgg-nfs-storage   Delete          Immediate           false                  5m45s
    
  • $ kubectl get pvc test-claim 查看 pvc

    NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    test-claim   Bound    pvc-e325c456-f401-4f90-b684-5b06f2f100c2   1Mi        RWX            managed-nfs-storage   18m
    
  • 好,现在所有都已经部署完成,现在回到 master 节点进行检查,看是否同步创建

    • $ cd /nfsdata && ls
      default-test-claim-pvc-e325c456-f401-4f90-b684-5b06f2f100c2
      
    • $ cd default-test-claim-pvc-e325c456-f401-4f90-b684-5b06f2f100c2 && ls
      SUCCESS
      
  • 可见,文件已经同步写到 master 节点上了

5 )总结

  • 整个流程,按照上面流程图来说是比较复杂的
  • 生产环境中,都用 Storage Class 这样PVC操作就很简单了
  • 41
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值