阿里云服务器ECS    
弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新 [咨询更多]
阿里云存储OSS
简单易用、多重冗余、数据备份高可靠、多层次安全防护安全性更强、低成本 [咨询更多]
阿里云数据库RDS
稳定可靠、可弹性伸缩、更拥有容灾、备份、恢复、监控、迁移等方面的全套解决方案 [咨询更多]
阿里云安全产品
DDoS高防IP、web应用防火墙、安骑士、sll证书、态势感知众多阿里云安全产品热销中 [咨询更多]
阿里云折扣优惠    
云服务器ECS、数据库、负载均衡等产品新购、续费、升级联系客服获取更多专属折扣 [咨询更多]
K8s 存储卷
2020-7-1    点击量:
  K8s 存储卷
  
  1. 基础概念
  
  根据之前的描述,为了实现容器数据的持久化我们需要使用数据卷的功能,在 K8s 编排系统中如何为运行的负载(Pod)定义存储呢?K8s 是一个容器编排系统,其关注的是容器应用在整个集群的管理和部署形式,所以在考虑 K8s 应用存储的时候就需要从集群角度考虑。K8s 存储卷定义了在 K8s 系统中应用与存储的关联关系。其包含以下概念:
  
  1)Volume 数据卷
  
  数据卷定义了外置存储的细节,并内嵌到 Pod 中作为 Pod 的一部分。其实质是外置存储在 K8s 系统的一个记录对象,当负载需要使用外置存储的时候,从数据卷中查到相关信息并进行存储挂载操作。
  
  生命周期和 Pod 一致,即 pod 被删除的时候数据卷也一起消失(注意不是数据删除);
  
  存储细节定义在编排模板中,应用编排感知存储细节;
  
  一个负载(Pod)中可以同时定义多个 volume,可以是相同类型或不同类型的存储;
  
  Pod 的每个 container 可以引用一个或多个 volume,不同 container 可以同时使用相同 volume。

  K8S Volume 常用类型:
  
本地存储:如 HostPath、emptyDir,这些存储卷的特点是,数据保存在集群的特定节点上,并且不能随着应用飘逸,节点宕机时数据即不再可用;
  
  网络存储:Ceph、Glusterfs、NFS、Iscsi 等类型,这些存储卷的特点是数据不在集群的某个节点上,而是在远端的存储服务上,使用存储卷时需要将存储服务挂载到本地使用;
  
  Secret/ConfigMap:这些存储卷类型,其数据是集群的一些对象信息,并不属于某个节点,使用时将对象数据以卷的形式挂载到节点上供应用使用;
  
  CSI/Flexvolume:这是两种数据卷扩容方式,可以理解为抽象的数据卷类型。每种扩展方式都可再细化成不同的存储类型;
  
  PVC:一种数据卷定义方式,将数据卷抽象成一个独立于 pod 的对象,这个对象定义(关联)的存储信息即存储卷对应的真正存储信息,供 K8s 负载挂载使用。

  一些 volume 模板示例如下:

volumes:
  - name: hostpath
    hostPath:
      path: /data
      type: Directory
---
  volumes:
  - name: disk-ssd
    persistentVolumeClaim:
      claimName: disk-ssd-web-0
  - name: default-token-krggw
    secret:
      defaultMode: 420
      secretName: default-token-krggw
---
  volumes:
    - name: "oss1"
      flexVolume:
        driver: "alicloud/oss"
        options:
          bucket: "docker"
          url: "oss-cn-hangzhou.aliyuncs.com"

  2)PVC和PV
  
  K8s 存储卷是一个集群级别的概念,其对象作用范围是整个 K8s 集群,而不是而一个节点;
  
  K8s 存储卷包含一些对象(PVC、PV、SC),这些对象和应用负载(Pod)是独立,通过编排模板进行关联;
  
  K8s 存储卷可以有自己的独立生命周期,不依附于 Pod。
  
  PVC 是 PersistentVolumeClaim 的缩写,译为存储声明;PVC 是在 K8s 中一种抽象的存储卷类型,代表了某个具体类型存储的数据卷表达。其设计意图是:存储与应用编排分离,将存储细节抽象出来并实现存储的编排(存储卷)。这样 K8s 中存储卷对象独立于应用编排而单独存在,在编排层面使应用和存储解耦。
  
  PV 是 PersistentVolume 的缩写,译为持久化存储卷;PV 在 K8s 中代表一个具体存储类型的卷,其对象中定义了具体存储类型和卷参数。即目标存储服务所有相关的信息都保存在 PV 中,K8s 引用 PV 中的存储信息执行挂载操作。
  
  应用负载、PVC、PV 的关联关系为:

K8s 存储卷

  从实现上看,只要有了 PV 既可以实现存储和应用的编排分离,也能实现数据卷的挂载,为何要用 PVC + PV 两个对象呢?K8s 这样设计是从应用角度对存储卷进行二次抽象;由于 PV 描述的是对具体存储类型,需要定义详细的存储信息,而应用层用户在消费存储服务的时候往往不希望对底层细节知道的太多,让应用编排层面来定义具体的存储服务不够友好。这时对存储服务再次进行抽象,只把用户关系的参数提炼出来,用 PVC 来抽象更底层的 PV。所以 PVC、PV 关注的对象不一样,PVC 关注用户对存储需求,给用户提供统一的存储定义方式;而 PV 关注的是存储细节,可以定义具体存储类型、存储挂载使用的详细参数等。
  
  使用时应用层会声明一个对存储的需求(PVC),而 K8s 会通过最佳匹配的方式选择一个满足 PVC 需求的 PV,并与之绑定。所以从职责上 PVC 是应用所需要的存储对象,属于应用作用域(和应用处于一个名词空间);PV 是存储平面的存储对象,属于整个存储域(不属于某个名词空间);
  
  下面给出 PVC、PV 的一些属性:
  

  PVC 和 PV 总是成对出现的,PVC 必须与 PV 绑定后才能被应用(Pod)消费;
  
  PVC 和 PV 是一一绑定关系,不存在一个 PV 被多个 PVC 绑定,或者一个 PVC 绑定多个 PV 的情况;
  
  PVC 是应用层面的存储概念,是属于具体的名词空间的;
  
  PV 是存储层面的存储概念,是集群级别的,不属于某个名词空间;PV 常由专门的存储运维人员负责管理;
  
  消费关系上:Pod 消费 PVC,PVC 消费 PV,而 PV 定义了具体的存储介质。

  3)PVC 详细定义
  
  PVC 定义的模板如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-ssd-web-0
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: alicloud-disk-available
  volumeMode: Filesystem
  PVC 定义的存储接口包括:存储的读写模式、资源容量、卷模式等;主要参数说明如下:
  
  accessModes:存储卷的访问模式,支持:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三种模式。
  
  ReadWriteOnce 表示 pvc 只能同时被一个 pod 以读写方式消费;
  
  ReadWriteMany 可以同时被多个 pod 以读写方式消费;
  
  ReadOnlyMany 表示可以同时被多个 pod 以只读方式消费;
  
  注意:这里定义的访问模式只是编排层面的声明,具体应用在读写存储文件的时候是否可读可写,需要具体的存储插件实现确定。

  storage:定义此 PVC 对象期望提供的存储容量,同样此处的数据大小也只是编排声明的值,具体存储容量要看底层存储服务类型。
  
  volumeMode:表示存储卷挂载模式,支持 FileSystem、Block 两种模式;
  
  FileSystem:将数据卷挂载成文件系统的方式供应用使用;
  
  Block:将数据卷挂载成块设备的形式供应用使用。

  4)PV 详细定义
  
  下面为云盘数据卷 PV 对象的编排示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    failure-domain.beta.kubernetes.io/region: cn-shenzhen
    failure-domain.beta.kubernetes.io/zone: cn-shenzhen-e
  name: d-wz9g2j5qbo37r2lamkg4
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 30Gi
  flexVolume:
    driver: alicloud/disk
    fsType: ext4
    options:
      VolumeId: d-wz9g2j5qbo37r2lamkg4
  persistentVolumeReclaimPolicy: Delete
  storageClassName: alicloud-disk-available
  volumeMode: Filesystem

  accessModes:存储卷的访问模式,支持:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三种模式;具体含义同 PVC 字段;
  
  capacity:定义存储卷容量;
  
  persistentVolumeReclaimPolicy:定义回收策略,即删除 pvc 的时候如何处理 PV;支持 Delete、Retain 两种类型,动态数据卷部分会详细说明此参数;
  
  storageClassName:表示存储卷的使用的存储类名字,动态数据卷部分会详细说明此参数;
  
  volumeMode:同 PVC 中的 volumeMode 定义;
  
  Flexvolume:此字段表示具体的存储类型,这里 Flexvolume 为一种抽象的存储类型,并在 flexvolume 的子配置项中定义了具体的存储类型、存储参数。

  5)PVC/PV 绑定
  
  PVC 只有绑定了 PV 之后才能被 Pod 使用,而 PVC 绑定 PV 的过程即是消费 PV 的过程,这个过程是有一定规则的,下面规则都满足的 PV 才能被 PVC 绑定:
  
  VolumeMode:被消费 PV 的 VolumeMode 需要和 PVC 一致;
  
  AccessMode:被消费 PV 的 AccessMode 需要和 PVC 一致;
  
  StorageClassName:如果 PVC 定义了此参数,PV 必须有相关的参数定义才能进行绑定;
  
  LabelSelector:通过 label 匹配的方式从 PV 列表中选择合适的 PV 绑定;
  
  storage:被消费 PV 的 capacity 必须大于或者等于 PVC 的存储容量需求才能被绑定。
  
  满足上述所有需要的 PV 才可以被 PVC 绑定。
  
  如果同时有多个 PV 满足需求,则需要从 PV 中选择一个更合适的进行绑定;通常选择容量最小的,如果容量最小的也有多个,则随机选择。
  
  如果没有满足上述需求的 PV 存储,则 PVC 会处于 Pending 状态,等待有合适的 PV 出现了再进行绑定。

  2. 静态、动态存储卷
  
  从上面的讨论我们了解到,PVC 是针对应用服务对存储的二次抽象,具有简洁的存储定义接口。而 PV 是具有繁琐存储细节的存储抽象,一般有专门的集群管理人员定义、维护。
  
  根据 PV 的创建方式可以将存储卷分为动态存储和静态存储卷:
  
  静态存储卷:由管理员创建的 PV
  
  动态存储卷:由 Provisioner 插件创建的 PV
  
  1)静态存储卷
  
  一般先由集群管理员分析集群中存储需求,并预先分配一些存储介质,同时创建对应的 PV 对象,创建好的 PV 对象等待 PVC 来消费。如果负载中定义了 PVC 需求,K8s 会通过相关规则实现 PVC 和匹配的 PV 进行绑定,这样就实现了应用对存储服务的访问能力。
  
  2)动态存储卷
  
  由集群管理员配置好后端的存储池,并创建相应的模板(storageclass),等到有 PVC 需要消费 PV 的时候,根据 PVC 定义的需求,并参考 storageclass 的存储细节,由 Provisioner 插件动态创建一个 PV。
  
  两种卷的比较:
  
  动态存储卷和静态存储卷最终的效果都是:Pod -> PVC -> PV 的使用链路,且对象的具体模板定义都是一致的;
  
  动态存储卷和静态存储卷区别是:动态卷是插件自动创建 PV,而静态卷是集群管理员手动创建 PV。
  
  提供动态存储卷的优势:
  
  动态卷让 K8s 实现了 PV 的自动化生命周期管理,PV 的创建、删除都通过 Provisioner 完成;
  
  自动化创建 PV 对象,减少了配置复杂度和系统管理员的工作量;
  
  动态卷可以实现 PVC 对存储的需求容量和 Provision 出来的 PV 容量一致,实现存储容量规划最优。
  
  3)动态卷的实现流程
  
  当用户声明一个 PVC 时,如果在 PVC 中添加了 StorageClassName 字段,其意图为:当 PVC 在集群中找不到匹配的 PV 时,会根据 StorageClassName 的定义触发相应的 Provisioner 插件创建合适的 PV 供绑定,即创建动态数据卷;动态数据卷时由 Provisioner 插件创建的,并通过 StorageClassName 与 PVC 进行关联。
  
  StorageClass 可译为存储类,表示为一个创建 PV 存储卷的模板;在 PVC 触发自动创建PV的过程中,即使用 StorageClass 对象中的内容进行创建。其内容包括:目标 Provisioner 名字,创建 PV 的详细参数,回收模式等配置。
  
  StorageClasss 模板定义如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: alicloud-disk-topology
parameters:
  type: cloud_ssd
provisioner: diskplugin.csi.alibabacloud.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer


  provisioner:为一个注册插件的名字,此插件实现了创建 PV 的功能;一个 StorageClass 只能定义一个Provisioner;
  
  parameters:表示创建数据卷的具体参数;例如这里表示创建一个 SSD 类型的云盘;
  
  reclaimPolicy:用来指定创建 PV 的 persistentVolumeReclaimPolicy 字段值,支持 Delete/Retain;Delete 表示动态创建的 PV,在销毁的时候也会自动销毁;Retain 表示动态创建的 PV,不会自动销毁,而是由管理员来处理;
  
  allowVolumeExpansion:定义由此存储类创建的 PV 是否运行动态扩容,默认为 false;是否能动态扩容是有底层存储插件来实现的,这里只是一个开关;
  
  volumeBindingMode:表示动态创建 PV 的时间,支持 Immediate/WaitForFirstConsumer;分别表示立即创建和延迟创建。

K8s 存储卷:关注容器集群的存储编排,从应用使用存储的角度关注存储服务。
  用户创建一个 PVC 声明时,会在集群寻找合适的 PV 进行绑定,如果没有合适的 PV 与之绑定,则触发下面流程:
  
  Volume Provisioner 会 watch 到这个 PVC 的存在,若这个 PVC 定义了 StorageClassName,且 StorageClass 对象中定义的 Provisioner 插件是自己,Provisioner 会触发创建 PV 的流程;
  
  Provisioner 根据 PVC 定义的参数(Size、VolumeMode、AccessModes)以及 StorageClass 定义的参数(ReclaimPolicy、Parameters)执行 PV 创建;
  
  Provisioner 会在存储介质端创建数据卷(通过 API 调用,或者其他方式),完成后会创建 PV 对象;
  
  PV 创建完成后,实现与 PVC 的绑定;以满足后续的 Pod 启动流程。

  4)延迟绑定动态数据卷
  
  某种存储(阿里云云盘)在挂载属性上有所限制,只能将相同可用区的数据卷和 Node 节点进行挂载,不在同一个可用区不可以挂载。这种类型的存储卷通常遇到如下问题:
  
  创建了 A 可用区的数据卷,但是 A 可用区的节点资源已经耗光,导致 Pod 启动无法完成挂载;
  
  集群管理员在规划 PVC、PV 的时候不能确定在哪些可用区创建多个 PV 来备用。
  
  StorageClass 中的 volumeBindingMode 字段正是用来解决此问题,如果将 volumeBindingMode 配置为 WaitForFirstConsumer 值,则表示 Provisioner 在收到 PVC Pending 的时候不会立即进行数据卷创建,而是等待这个 PVC 被 Pod 消费的时候才执行创建流程。
  
  其实现原理是:

  
  Provisioner 在收到 PVC Pending 状态的时候不会立即进行数据卷创建,而是等待这个 PVC 被 Pod 消费;
  
  如果有 Pod 消费此 PVC,调度器发现 PVC 是延迟绑定,则 pv 继续完成调度功能(后续会详细讲解存储调度);且调度器会将调度结果 patch 到 PVC 的 metadata 中;
  
  当 Provisioner 发现 PVC 中写入了调度信息时,会根据调度信息获取创建目标数据卷的位置信息(zone、Node),并触发 PV 的创建流程。
  
  通过上述流程可见:延迟绑定会先让应用负载进行调度(确定有充足的资源供 pod 使用),然后再触发动态卷的创建流程,这样就避免了数据卷所在可用区没有资源的问题,也避免了存储预规划的不准确性问题。
  
  在多可用区集群环境中,更推荐使用延迟绑定的动态卷方案,目前阿里云 ACK 集群已经支持上述配置方案。
  
  3. 使用示例
  
  下面给出一个 pod 消费 PVC、PV 的例子:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nas-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  selector:
    matchLabels:
      alicloud-pvname: nas-csi-pv
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nas-csi-pv
  labels:
    alicloud-pvname: nas-csi-pv
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  flexVolume:
    driver: "alicloud/nas"
    options:
      server: "***-42ad.cn-shenzhen.extreme.nas.aliyuncs.com"
      path: "/share/nas"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nas
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx1
        image: nginx:1.8
      - name: nginx2
        image: nginx:1.7.9
        volumeMounts:
          - name: nas-pvc
            mountPath: "/data"
      volumes:
        - name: nas-pvc
          persistentVolumeClaim:
            claimName: nas-pvc

  模板解析:
  
此应用为 Deployment 方式编排的一个 Nginx 服务,每个 pod 包含 2 个容器:nginx1、nginx2;
  
  模板中定义了 Volumes 字段,说明期望挂载数据卷给应用使用,此例中使用了 PVC 这种数据卷定义方式;
  
  应用内部:将数据卷 nas-pvc 挂载到 nginx2容器的 /data 目录上;nginx1 容器并没有挂载;
  
  PVC(nas-pvc)定义为一个不小于 50G 容量、读写方式为 ReadWriteOnce 的存储卷需求,且对 PV 有 Label 设置的需求;
  
  PV(nas-csi-pv)定义为一个容量为 50G、读写方式为 ReadWriteOnce、回收模式为 Retain、类型为 Flexvolume 抽象类型的存储卷,且具有 Label 配置;

  根据 PVC、PV 绑定的逻辑,此 PV 符合 PVC 消费要求,则 PVC 会和此 PV 进行绑定,并供 pod 挂载使用。

联系客服免费领取更多阿里云产品新购、续费升级折扣,叠加官网活动折上折更优惠