k8s中的Volume体系与存储架构

一、容器中使用Volume

1、作为文件系统挂载

容器中通过volumeMounts字段使用Volume作为文件系统挂载:
(1)name字段指定使用哪个Volume;
(2)mountPath字段指定Volume在容器中的挂载路径;
(3)readOnly字段指定挂载的Volume是否只读;
(4)多个容器共享Volume时,可以隔离不同容器在Volume上数据存储的路径
subPath直接指定子目录的名字,
subPathExpr则指定${XXX},通过环境变量获取子目录的名字
这两个字段都默认为空,不能同时配置
(5)通过mountPropagation配置挂载传播
None(默认):此Volume挂载不会接收到任何后续挂载到该Volume(或子目录)下的挂载
HostToContainer:此Volume挂载将会接收到任何后续挂载到该Volume(或子目录)下的挂载
Bidirectional:类似HostToContainer,但容器创建的Volume挂载都将传播回主机和所有使用相同Volume的容器
三种挂载传播机制分别对应了Linux内核mount namespace的private、rslave、rshared传播机制

 2、作为块设备挂载

容器中通过volumeDevices字段使用Volume作为块设备挂载
(1)name字段指定使用哪个Volume
(2)devicePath指定volume在容器中的挂载路径

二、Pod中声明Volume

Pod在spec.volumes中声明挂载了哪些Volume,VolumeSource大体分为以下几类:

1、本地存储

(1)emptyDir:在Pod创建过程中创建的一个临时目录,这个目录里的数据随着Pod删除也会被清除
存储介质包括内存(基于RAM的temfs)、磁盘等、巨页三种
目前也支持设置SizeLimit
存储介质为内存的Volume会同时受SizeLimit和Pod内所有容器总内存limit的限制,都没设的话就会限制为主机内存的一半
(2)hostPath:宿主机上的一个路径,在Pod删除之后还存在。路径如果是软链接的话会自动转向实际路径。
种类包括Directory、File、Socket、CharDevice、BlockDevice五种
考虑到Directory、File在主机上可能不存在,可以设置为DirectoryOrCreate、FileOrCreate
 

2、网络存储

如NFS、CephFS、RBD、 ISCSI等
 

3、云厂商提供的存储

例如在vSphere平台上部署Kubernetes时,可以使用vSphereVolume作为后端存储
在GCE可使用GCEPersistentDisk
在AWS上可以使用AWSElasticBlockStore
在Azure可以使用AzureFile
......
 

4、配置文件 

(1)DownwardAPI可以将Pod API对象的部分字段以临时文件的形式注入到容器中
(2)可以将ConfigMap/Secret以持久化Volume的形式挂载到容器中,让容器中的程序可以通过POSIX接口来访问配置数据;也支持仅将它们的一部分以临时文件的形式注入到容器中
(3)Projected可以将配置信息的一部分,以临时文件的形式注入到容器中
包括Configmap、Secret、DownwardAPI、ServiceAccount Token四种
例如:将Pod的metadata.labels中的内容,以labels这个文件名挂载到mountPath下
  volumes:
    - name: podinfo
      projected:
        sources:
        - downwardAPI:
            items:
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels

5、CSI和FlexVolume

CSIVolumeSource的数据结构:
  • Driver
根据Driver name指定由哪个CSI Plugin来处理该Volume
  • ReadyOnly
  • FsType
希望挂载的文件系统种类,如ext4、xfs等
  • VolumeAttribute
用于附加参数,如PV定义的是OSS,就可以在这里定义bucket、访问的地址等信息
  • NodePublishSecretRef
指定一个Secret的Name
调用CSI Plugin的NodePublishVolume/NodeUnpublishVolume接口时,使用该Secret传递敏感信息
 
FlexVolumeSource的数据结构:
  • Driver
  • ReadyOnly
  • FsType
  • SecretRef
  • Options
 
由于是在Pod中声明的,所以其实是一种临时Volume。Volume需要和Pod一起生成,并且作为Pod的一部分,和Pod一起终结。
 

6、PVC

Pod中声明的Volume,生命周期与Pod相同。k8s又引入了PV(Persistent Volumes)概念,它可以将存储和计算分离,通过不同的组件来管理存储资源和计算资源,解耦Pod和Volume之间生命周期的关联。
当Pod删除后,它使用的PV仍然存在,还可以被新建的Pod复用。
用户只需在PVC(PersistentVolumeClaim)中声明自己需要的Resources、AccessModes等需求,而不需要关心存储实现细节。
PV才是存储的实际信息的承载体。PV和对应的后端存储信息交由集群管理员统一运维和管控,安全策略更容易控制。
 

PersistentVolumeSpec的数据结构

  • Capacity
使用map[ResourceName]resource.Quantity来描述资源量
一般只设置一对ResourceName为storage、resource.Quantity为xxx Gi的kv,表明创建的存储大小
  • PersistentVolumeSource
该PV真正的Source
和Pod中直接声明Volume所支持的Source种类几乎一样(只是少了emptyDir、配置文件的四种、Ephemeral和自己) 
  • AccessModes
PV访问策略控制列表,表明创建出来的存储的访问方式
    ReadWriteOnce只允许单node上的Pod访问
    ReadOnlyMany允许多个node的Pod只读访问
    ReadWriteMany允许多个node上的Pod读写访问
  • VolumeMode
Volume将被如何挂载,包括Block和Filesystem两种
  • ClaimRef
记录着PVC的绑定信息
  • PersistentVolumeReclaimPolicy
表明该Volume被release后(即与之绑定的PVC被删除后)的回收策略
    Delete:Volume被released后直接删除,需要Volume plugin支持
    Retain:默认策略,由系统管理员来手动管理该Volume。
  • StorageClassName
  • MountOptions
传递挂载选项的String切片
PS:传递过程不会做验证,如果Volume plugin不支持某个Options,则分配操作可能会失败。
  • NodeAffinity
限制了可以访问该Volumes的node,对使用该Volume的Pod调度有影响
 

PersistentVolumeClaimSpec的数据结构

  • AccessModes
  • Selector
通过标签选择器选择一组PV
  • VolumeName
直接指定要绑定的PV名
  • Resources
包括request和limit两种,使用map[ResourceName]resource.Quantity来描述资源需求量
  • StorageClassName
  • VolumeMode
  • DataSource

 

快照体系

k8s引入了VolumeSnapshotContent/VolumeSnapshot/VolumeSnapshotClass体系,以支持存储快照功能。不过目前只有CSI支持。
(1)首先创建VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1a1pha1
kind: VolumeSnapshotClass
metadata:
    name: disk-snapshotclass
snapshotter: xxxxxx
snapshotter指定了真正创建存储快照所使用的Volume Plugin
(2)通过Volumesnapshot声明创建存储快照:
apiVersion: snapshot.storage.k8s.io/v1a1pha1
kind: VolumeSnapshot
metadata:
    name:disk-snapshot
    namespace: xxx
spec:
    snapshotClassName: disk-snapshotclass
    source:
        name: disk-pvc
        kind: PersistentVolumeClaim
.spec.snapshotClassName指定了使用的VolumeSnapshotClass
.spec.source.name指定了作为数据源的PVC
 
提交VolumeSnapshot对象后,集群中的相关组件会找到数据源PVC对应的PV存储,对这个PV存储做一次快照。并自动创建Volumesnapshotcontent对象。
Volumesnapshotcontent对象中记录了云存储厂商返回的snapshot的ID、作为数据源的PVC
删除VolumeSnapshot后Volumesnapshotcontent也会自动删除
(3)数据恢复时,将PVC对象的DataSource指定为VolumeSnapshot对象。
这样当PVC提交之后,会由集群中的相关组件找到dataSource所指向的VolumeSnapshotContent,然后新创建对应的存储以及PV,将存储快照数据恢复到新的PV中。
 

绑定

通过kube-controller-manager中的PV Controller将PV和合适的PVC绑定到一起,绑定有三种方式:

(1)静态产生方式 - Static Volume Provisioning

集群管理员事先规划用户怎样使用存储,并预分配一些存储(即预创建一些 PV)
用户在使用存储的时候,需要先提交自己的存储需求(即PVC)
K8s内部相关组件会根据PVC的size和accessMode,判断它跟静态创建的哪个PV匹配,然后绑定到一起。
注:一个PV可以设置多个访问策略。PVC与PV绑定时,PV Controller会优先找到AccessModes列表最短并且匹配PVC AccessModes列表的PV集合,然后从该集合中找到Capacity最小且符合PVC size需求的PV对象
之后,用户通过Pod使用存储时,可以在volumes里声明要用哪个PVC。Pod通过该PVC找到绑定的PV。

(2)动态产生方式 - Dynamic Volume Provisioning

集群管理员不预分配PV,而是预先准备StoregeClass(创建PV的模板),它包含了创建某种具体类型(块存储、文件存储等)的PV所需要的参数信息。
StorageClass为管理员提供了描述存储 "类" 的方法,其数据结构:
  • metav1.TypeMeta
  • metav1.ObjectMeta
  • Provisioner
指定了使用哪个Volume Plugin的Provisioner(存储分配器)来进行PV的provision/delete操作。
其中,Internal Provisioner的名称前缀为kubernetes.io并打包在Kubernetes中(由PV Controller进行此工作)
Volume Plugin
Internal Provisioner
Config Example
AWSElasticBlockStore
AzureFile
AzureDisk
CephFS
-
-
Cinder
FC
-
-
FlexVolume
-
-
Flocker
-
GCEPersistentDisk
Glusterfs
iSCSI
-
-
Quobyte
NFS
-
-
RBD
VsphereVolume
PortworxVolume
ScaleIO
StorageOS
Local
-
  • ReclaimPolicy
由SC动态创建出来的PV将使用该ReclaimPolicy
  • AllowVolumeExpansion
若设置为true,且底层Volume Plugin支持卷扩展,可以编辑PVC对象来扩大(不能缩小)Volume大小
  • MountOptions
由SC动态创建的PV将使用该MountOptions
  • VolumeBindingMode
控制了卷绑定和动态分配的发生时间:
    Immediate模式表示一旦创建了PVC,也就完成了卷的动态分配和绑定。对于由于拓扑限制而非集群所有节点可达的存储后端,PV会在不知道Pod调度要求的情况下分配、绑定。
    WaitForFirstConsumer模式将延迟PV的分配和绑定,直到使用该PVC的Pod被创建。PV再根据Pod的调度结果分配和绑定。
  • AllowedTopologies
指定了允许的拓扑结构
VolumeBindingMode配置为WaitForFirstConsumer模式的情况下一般无需再配置此字段,除非除了Pod调度,动态创建的PV也有拓扑位置的限制。
此时需要同时配置WaitForFirstConsumer和AllowedTopologies,这样既限制了动态创建的PV要能被这个可用区访问、也限制了Pod在选择node时要落在这个可用区内。
  • Parameters
以key:values的形式描述属于Volume的参数,最多可定义512个,总长度不超过256KB
PV分配器可以接受哪些参数,需要后端存储的提供者提供。
该产生方式下,PVC配置方式不变,只需在PVC中指定需要使用的StoregeClass模板
此时,若PVC找不到相应的PV,就会用指定的StorageClass模板去动态创建PV,并将PVC和PV进行绑定;存在一个满足条件的PV时,就会直接使用现有的PV

(3)静态产生方式下满足PV的拓扑限制

Local PV一般通过静态创建的方式,先要声明PV对象,在PV对象中通过nodeAffinity来给这个PV加上拓扑限制,限制其只能在单node上访问
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-local
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - cn-beijing.192.168.1.147
虽然是静态产生方式,仍然需要创建SC
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
    name:lacal-storage
provisioner: kubernetes.io/noprovisioner
volumeBindingMode:WaitFirstConsumer
provisioner指定为no-provisioner,目的是告诉PV Controller遇到该PVC时无需进行任何操作
VolumeBindingMode需要设置为WaitForFirstConsumer。用户提交PVC后,PV Controller找到相应的SC,发现BindingMode是延迟绑定,就不会做任何事情。
 
当真正使用这个PVC的Pod,在调度的时恰好调度在符合PV拓扑要求的节点上之后,Pod里使用的PVC才会真正与PV做绑定。这样就能保证最终创建出来的Pod能访问这块Local PV。
 

PV和PVC的状态流转:

创建PV对象后,它会暂时处于pending状态。等真正的PV创建好之后,它处在available状态(可以使用)。
创建PVC对象后,它会暂时处于pending状态,直到PV和PVC就结合到一起,此时两者都处在bound状态。
当用户在使用完PVC将其删除后,PV处于released状态,依赖于ReclaimPolicy配置被删除或保留。在默认的Retain策略下,保留的PV无法从released状态回到available状态(即无法被一个新的PVC绑定),需要:
  1. 新建一个PV对象,把之前的released的PV的相关字段的信息填到新的PV对象里面,这个PV就可以结合新的PVC了;
  2. 删除Pod之后不删除PVC对象,这样给PV绑定的PVC还存在。下次Pod使用的时候,就可以直接通过PVC去复用。(K8s中的StatefulSet管理的Pod带存储的迁移就是通过这种方式)。
  3. 从PV的spec.claimRef字段删除PVC绑定信息,即可重新释放PV从而达到available。
处于Bound状态的PVC,与其关联的PV被删除后,变成Lost状态;重新与PV绑定后变成Bound状态。
 

7、ephemeral

区别于Persistent Volumes,k8s又提出了Ephemeral Volumes
其类似emptyDir,可以为每个Pod提供临时的数据存放目录
由于是通过volumeClaimTemplate声明的,其实是一种通用临时卷,可以由所有支持PV的存储所提供
spec:
  volumes:
    - name: scratch-volume
      ephemeral:
        volumeClaimTemplate:
          metadata:
            labels:
              type: my-frontend-volume
          spec:
            accessModes: [ "ReadWriteOnce" ]
            storageClassName: "scratch-storage-class"
            resources:
              requests:
                storage: 1Gi

 

三、Volume挂载流程

(1)PV Controller

负责PV/PVC的绑定、生命周期管理,并根据需求进行数据卷的Provision/Delete操作。
主要有两个实现逻辑:
①ClaimWorker实现PVC的状态迁移:
②VolumeWorker实现PV的状态迁移:

(2)Attach/Detach Controller

负责存储设备的Attach/Detach操作,将设备挂载到目标节点
有两个核心对象:
下图是AD Controller实现的逻辑框图:
对于使用了CSI Plugin的PV/PVC,PV Controller和AD Controller不会进行Provision/Delete和Attach/Detach,只会进行一些辅助工作,详见container storage interface

(3)Volume manager

Volume Manager实际是Kubelet中众多Manager中的一个。它主要是用来做本节点Volume的Attach/Detach/Mount/Unmount操作、卷设备的格式化
逻辑上和AD Controller类似,一样包含有desireStateofWorld以及actualStateofWorld,通过desiredStateOfWorldPopulator进行数据的同步、通过Reconciler进行接口的调用。
Kubelet启动参数"--enable-controller-attach-detach" 为True,Attach/Detach操作由AD Controller来控制;若为False,则由Volume Manager来做(一般不需要,后续可能删除此功能)。
下图是 Volume Manager 实现逻辑图:

(4)Volume Plugins

PV Controller、AD Controller、Volume Manager主要是进行操作的调用,而具体操作则是由Volume Plugins实现的。
根据源码的位置可将Volume Plugins分为In-Tree和Out-of-Tree两类:
  • Out-of-Tree类的Volume Plugins的代码放在Kubernetes内部,和Kubernetes一起发布、管理与迭代,缺点是迭代速度慢、灵活性差;
  • Out-of-Tree类的Volume Plugins的代码独立于Kubernetes,它是由存储商提供实现的,目前主要有Flexvolume和CSI两种实现机制。通过抽象接口将不同存储的driver实现从k8s代码仓库中剥离,是社区主推的一种实现网络存储插件的方式。

Volume Plugins的发现

Kubernetes会在PV Controller、AD Controller以及Volume Manager中做插件管理。通过VolumePlguinMg对象进行管理。主要包含Plugins和Prober两个数据结构。
Plugins主要是用来保存Plugins列表的一个对象,而Prober是一个探针,用于发现新的Plugin
下图是插件管理的整个过程:

参考资料:

[1] https://kubernetes.io/docs/home/

[2] https://edu.aliyun.com/roadmap/cloudnative

[3] 郑东旭《Kubernetes源码剖析》

posted @ 2021-01-17 15:00  扬羽流风  阅读(2573)  评论(0编辑  收藏  举报