本文由 CNCF + Alibaba 云原生技术公开课 整理而来
Kubernetes 存储架构
- 在 Kubernetes 中挂载一个
Volume
:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-disk
spec:
storageClassName: alicloud-disk-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: disk-pvc
mountPath: /data
volumes:
- name: disk-pvc
persistentVolumeClaim:
claimName: pvc-disk
上面 yaml 文件中定义了 StatefulSet
的一个应用,其中定义了一个名为 disk-pvc 的 volume
,挂载到 Pod
内部的目录是 /data
。disk-pvc 是一个 PVC
类型的数据卷,其中定义了一个 storageClassName
alicloud-disk-ssd。当 StatefulSet
web 被创建之后,由 alicloud-disk-ssd 这个 StorageClass
自动创建 PV
与 PVC
disk-pvc 绑定。
Kubernetes 挂载 Volume
的过程如下:
第一步:用户创建一个 包含 PVC 的 Pod;
第二步:PV Controller 会不断观察 ApiServer,如果它发现一个 PVC 已经创建完毕但仍然是未绑定的状态,它就会试图把一个 PV 和 PVC 绑定。
PV Controller 首先会在集群内部找到一个适合的 PV 进行绑定,如果未找到相应的 PV,就调用 Volume Plugin 去做 Provision。
Provision 就是从远端上一个具体的存储介质创建一个 Volume,并且在集群中创建一个 PV 对象,然后将此 PV 和 PVC 进行绑定;
第三步:通过 Scheduler 完成一个调度功能。
当一个 Pod 运行的时候,需要选择一个 Node,这个 Node 的选择就是由 Scheduler 来完成的。Scheduler 进行调度的时候会有多个参考量,
比如 Pod 内部所定义的 nodeSelector、nodeAffinity 这些定义以及 Volume 中所定义的一些标签等。
因此可以在数据卷中添加一些标准,这样使用这个 PV 的 Pod 就会由于标签的限制,被调度器调度到期望的节点上;
第四步:如果有一个 Pod 调度到某个节点之后,它所定义的 PV 还没有被挂载(Attach),此时 AD Controller 就会调用 VolumePlugin,
把远端的 Volume 挂载到目标节点中的设备上(如:/dev/vdb);
第五步:当 Volum Manager 发现一个 Pod 调度到自己的节点上并且 Volume 已经完成了挂载,它就会执行 mount 操作,将本地设备
(也就是刚才得到的 /dev/vdb)挂载到 Pod 在节点上的一个子目录中。同时它也可能会做一些像格式化、是否挂载到 GlobalPath 等这样的附加操作。
第六步就:绑定操作,就是将已经挂载到本地的 Volume 映射到容器中。
- Kubernetes 的存储架构:
-
PV Controller
:
PV
:持久化存储卷,详细定义了预挂载存储空间的各项参数。
PVC
:持久化存储声明。它是用户所使用的存储接口,对存储细节无感知,主要是定义一些基本存储的 Size、AccessMode 这些参数在里面,并且它是属于某个 NameSpace
内部的。
StorageClass
:存储类。一个动态存储卷会按照 StorageClass
所定义的模板来创建一个 PV
,其中定义了创建模板所需要的一些参数和创建 PV
的一个 Provisioner(就是由谁去创建的)。
PV Controller
的主要任务就是完成 PV
、PVC
的生命周期管理,比如创建、删除 PV
对象,负责 PV
、PVC
的状态迁移;另一个任务就是绑定 PVC
与 PV
对象,一个 PVC
必须和一个 PV
绑定后才能被应用使用,它们是一一绑定的,一个 PV
只能被一个 PVC
绑定,反之亦然。
一个 PV 的状态迁移图:
创建好一个 PV
以后,PV
就处于一个 Available
的状态,当一个 PVC
和一个 PV
绑定的时候,这个 PV
就进入了 Bound
的状态,此时如果把 PVC
删掉,Bound
状态的 PV
就会进入 Released
的状态。
一个 Released
状态的 PV
会根据自己定义的 ReclaimPolicy
字段来决定自己是进入一个 Available
的状态还是进入一个 Deleted
的状态。如果 ReclaimPolicy
定义的是 recycle
类型,它会进入一个 Available
状态,如果转变失败,就会进入 Failed
的状态。
一个 PVC 的状态迁移图:
一个创建好的 PVC
会处于 Pending
状态,当一个 PVC
与 PV
绑定之后,PVC
就会进入 Bound
的状态,当一个 Bound
状态的 PVC
的 PV
被删掉之后,该 PVC
就会进入一个 Lost
的状态。对于一个 Lost
状态的 PVC
,它的 PV
如果又被重新创建,并且重新与该 PVC
绑定之后,该 PVC
就会重新回到 Bound
状态。
一个 PVC
去绑定 PV
时会对 PV
进行筛选,筛选流程(从上至下)如下:
VolumeMode 检查:筛选所有具有相同 VolumeMode 的 Volume:Block、FileSystem;
LabelSelector 检查:如果 PVC 配置了 LabelSelector,会筛选符合条件的 Volume;
StorageClassName 检查:如果 PVC 配置了 StorageClassName,会筛选相同名称的 Volume;
AccessMode 检查:筛选 PVC 的 AccessMode 列表中所有选项,都有相应的 AccessMode 配置的 Volume;
Size 检查:筛选哪些 大于等于 PVC 的 Size 的 Volume,若 Volume 的数量大于 1,则筛选符合条件的 Size 最小 的 Volume。
-
AD Controller
:
AD Controller
是 Attach/Detach Controller
的一个简称。它有两个核心对象,即 DesiredStateofWorld
和 ActualStateOfWorld
。DesiredStateofWorld
是集群中预期要达到的数据卷的挂载状态;ActualStateOfWorld
则是集群内部实际存在的数据卷挂载状态。
AD Controller
有两个核心逻辑:
desiredStateOfWorldPopulator 主要是用来同步集群的一些数据以及 DSW、ASW 数据的更新,比如创建一个新的 PVC、创建一个新的 Pod 的时候,会把集群里面这些数据的状态同步到 DesiredStateofWorld 中;
Reconcile 根据 DesiredStateofWorld 和 ActualStateOfWorld 对象的状态做状态同步。它会把 ActualStateOfWorld 状态变成 DesiredStateofWorld 状态,在这个状态的转变过程中,它会去执行 Attach、Detach 等操作。
AD Controller
中有很多 Informer
,Informer
会把集群中的 Pod
状态、PV
状态、Node
状态、PVC
状态同步到本地。
在初始化的时候会调用 populateDesireStateofWorld
以及 populateActualStateofWorld
将 desireStateofWorld
、actualStateofWorld
两个对象进行初始化。
在执行的时候,通过 desiredStateOfWorldPopulator
进行数据同步,即把集群中的数据状态同步到 desireStateofWorld
中。reconciler
则通过轮询的方式把 actualStateofWorld
和 desireStateofWorld
这两个对象进行数据同步,在同步的时候,会通过调用 Volume Plugin
进行 attach
和 detach
操作,同时它也会调用 nodeStatusUpdater
对 Node
的状态进行更新。
-
Volume Manager
:
Volume Manager
实际上是 Kubelet
中一部分,是 Kubelet
中众多 Manager
的一个。它主要是用来做本节点 Volume
的 Attach/Detach/Mount/Unmount
操作。
它和 AD Controller
一样包含有 desireStateofWorld
以及 actualStateofWorld
,同时还有一个 volumePluginManager
对象,主要进行节点上插件的管理。在核心逻辑上和 AD Controller
也类似,通过 desiredStateOfWorldPopulator
进行数据的同步以及通过 Reconciler
进行接口的调用。
AD Controller
也会做 Attach/Detach
操作,所以到底是由谁来做呢?可以通过 --enable-controller-attach-detach
标签进行定义,如果它为 True
,则由 AD Controller
来控制;若为 False
,就由 Volume Manager
来做。
--enable-controller-attach-detach
是 Kubelet
的一个标签,只能定义某个节点的行为。如果一个有 10 个节点的集群,它有 5 个节点定义该标签为 False
,说明这 5 个节点是由节点上的 Kubelet
来做挂载,而另外 5 个节点是由 AD Controller
来做挂载。
-
Volume Plugins
:
前面提到的 PV Controller
、AD Controller
以及 Volume Manager
其实都是通过调用 Volume Plugin
提供的接口,比如 Provision
、Delete
、Attach
、Detach
等去做一些 PV
、PVC
的管理。而这些接口的具体实现逻辑是放在 VolumePlugin
中的。
根据源码的位置可将 Volume Plugins
分为 In-Tree
和 Out-of-Tree
两类:
In-Tree 表示源码是放在 Kubernetes 内部的,和 Kubernetes 一起发布、管理与迭代,缺点及时迭代速度慢、灵活性差;
Out-of-Tree Volume Plugins 的代码独立于 Kubernetes,它是由存储商提供实现的,目前主要有 Flexvolume 和 CSI 两种实现机制,可以根据存储类型实现不同的存储插件。
Kubernetes 会在 PV Controller
、AD Controller
以及 Volume Manager
中来做插件管理。通过 VolumePlguinMg
对象进行管理。主要包含 Plugins
和 Prober
两个数据结构。
Plugins
主要是用来保存 Plugins
列表的一个对象,而 Prober
是一个探针,用于发现新的 Plugin,比如 Flexvolume
、CSI
是扩展的一种插件,它们是动态创建和生成的,一开始无法预知,因此需要一个探针来发现新的 Plugin。
PV Controller
、AD Controller
以及 Volume Manager
在启动的时候会执行一个 InitPlugins
方法来对 VolumePluginsMgr
做一些初始化。
它首先会将所有 In-Tree
的 Plugins
加入到插件列表中,同时会调用 Prober
的 init
方法,该方法会首先调用一个 InitWatcher
,它会时刻观察着某一个目录(比如 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/
),当这个目录每生成一个新文件的时候,也就是创建了一个新的 Plugins
,此时就会生成一个新的 FsNotify.Create
事件,并将其加入到 EventsMap
中;同理,如果删除了一个文件,就生成一个 FsNotify.Remove
事件加入到 EventsMap
中。
当上层调用 refreshProbedPlugins
时,Prober
就会把这些事件进行一个更新,如果是 Create
,就将其添加到插件列表;如果是 Remove
,就从插件列表中删除一个插件。
Flexvolume 介绍及使用
Flexvolume
是 Volume Plugins
的一个扩展,主要实现 Attach/Detach/Mount/Unmount
这些接口。这些功能本来是由 Volume Plugins
实现的,但是对于某些存储类型,需要将其扩展到 Volume Plugins
以外,所以需要把接口的具体实现放到外面。
Flexvolume
是可被 Kubelet
驱动的可执行文件,每一次调用相当于执行一次 ls
这样的 shell 命令,都是可执行文件的命令行调用,因此它不是一个常驻内存的守护进程。
Flexvolume
的 Stdout
作为 Kubelet
调用的返回结果,这个结果需要是 JSON
格式。Flexvolume
默认的存放地址为 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/alicloud~disk/disk
。
-
Flexvolume
的接口介绍:
Flexvolum
包含以下接口:
init:主要做一些初始化的操作,比如部署插件、更新插件的时候做 init 操作,返回的时候会返回 DriveCapabilities 类型的数据结构,用来说明 Flexvolume 插件有哪些功能
GetVolumeName:返回插件名
Attach: 挂载功能的实现。根据 --enable-controller-attach-detach 标签来决定是由 AD Controller 还是 Kubelet 来进行挂载操作
WaitforAttach:Attach 经常是异步操作,因此需要等待挂载完成,才能需要进行下面的操作
MountDevice:它是 mount 的一部分。这里将 mount 分为 MountDevice 和 SetUp 两部分,MountDevice 主要做一些简单的预处理工作,比如将设备格式化、挂载到 GlobalMount 目录中等
GetPath:获取每个 Pod 对应的本地挂载目录
Setup:使用 Bind 方式将 GlobalPath 中的设备挂载到 Pod 的本地目录
TearDown、UnmountDevice、Detach:实现的是上面一些借口的逆过程
ExpandVolumeDevice:扩容存储卷,由 Expand Controller 发起调用
NodeExpand:扩容文件系统,由 Kubelet 发起调用
-
Flexvolume
的挂载:
从挂载流程和卸载流程两个方向来分析 Flexvolume
的挂载过程:
挂载流程:
1. Attach 操作,调用一个远端的 API 把 Storage 挂载到目标节点中的某个设备上去
2. MountDevice 操作,将本地设备挂载到 GlobalPath 中,同时也会做一些格式化这样的操作
3. Mount 操作,它会把 GlobalPath 挂载 PodPath 中,PodPath 就是 Pod 启动时所映射的一个目录
卸载流程:
1. Unmount 操作,把 GlobalPath 从 PodPath 中卸载
2. UnmountDevice 操作,从 GlobalPath 中卸载本地设备
3. Detach 操作,调用一个远端的 API 把 Storage 从目标节点中的某个设备上卸载
卸载流程就是挂载流程的逆过程。对于块设备的挂载过程,需要 Attach
、MountDevice
、Mount
操作;而对于文件存储类型,就无需 Attach
、MountDevice
操作,只需要 Mount
操作,因此文件系统的 Flexvolume
实现较为简单,只需要 Mount
和 Unmount
过程即可。
-
Flexvolume
的使用:
Flexvolume
中定义了 driver
、fsType
、options
:
driver 定义的是实现的某种驱动,比如 aliclound/disk,也可以是 aliclound/nas 等
fsType 定义的是文件系统类型,比如 "ext4"
options 包含了一些具体的参数,比如定义云盘的 id 等
PV
和 PVC
示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-disk
labels:
failure-domain.beta.kubernetes.io/zone: cn-hangzhou-a
failure-domain.beta.kubernetes.io/rehion: cn-hangzhou
pvname: pv-disk
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
flexVolume:
driver: "alicloud/disk"
fsType: "ext4"
options:
volumeId: "d-wz9bkj7jhbb4j077n1tz"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-disk
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
selector:
matchLabels:
pvname: pv-disk
SC
和 PVC
示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-ssd-hangzhou-a
provisioner: alicloud/disk
reclaimPolicy: Retain
parameters:
type: cloud_ssd
region: cn-hangzhou
zoneid: cn-hangzhou-a
encrypted: "true"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: disk-ssd1
spec:
storageClassName: alicloud-disk-ssd-hangzhou-a
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
CSI 介绍及使用
和 Flexvolume
类似,CSI
也是为第三方存储提供数据卷实现的抽象接口。有了 Flexvolume
,为何还要 CSI
呢?
Flexvolume
只是给 kubernetes
这一个编排系统来使用的,而 CSI
可以满足不同编排系统的需求,比如 Mesos
、Swarm
。
其次 CSI
是容器化部署,可以减少环境依赖,增强安全性,丰富插件的功能。Flexvolume
是在 host 空间一个二进制文件,执行 Flexvolum
时相当于执行本地的一个 shell 命令,这使得在安装 Flexvolume
时需要同时安装某些依赖,而这些依赖可能会对客户的应用产生一些影响,因此在安全性上、环境依赖上会有不好的影响。
CSI
主要包含两个部分:CSI Controller Server
与 CSI Node Server
。
Controller Server 是控制端的功能,主要实现创建、删除、挂载、卸载等功能;
Node Server 主要实现的是节点上的 Mount、Unmount 功能。
-
CSI
的接口介绍:
CSI
的接口主要分为 3 类:通用管控接口、节点管控接口、中心管控接口。
通用管控接口主要返回 CSI 的一些通用信息,像插件的名字、Driver 的身份信息、插件所提供的能力等
节点管控接口的 NodeStageVolume 和 NodeUnstageVolume 就相当于 Flexvolume 中的 MountDevice 和 UnmountDevice,
NodePublishVolume 和 NodeUnpublishVolume 就相当于 SetUp 和 TearDown 接口
中心管控接口的 CreateVolume 和 DeleteVolume 就是 Provision 和 Delete 存储卷的一个接口,
ControllerPublishVolume 和 ControllerUnPublishVolume 则分别是 Attach 和 Detach 的接口。
-
CSI
的系统结构:
CSI
是通过 CRD
的形式实现的,所以 CSI
引入了这么几个对象类型:VolumeAttachment
、CSINode
、CSIDriver
以及 CSI Controller Server
与 CSI Node Server
的一个实现。
在 CSI Controller Server
中,有传统的类似 Kubernetes 中的 AD Controller
和 Volume Plugins
,VolumeAttachment
对象就是由它们所创建的。此外,还包含多个 External Plugin
组件,每个组件和 CSI Plugin
组合的时候会完成某种功能。例如:
External Provisioner 和 Controller Server 组合的时候就会完成数据卷的创建与删除功能
External Attacher 和 Controller Server 组合起来可以执行数据卷的挂载和操作
External Resizer 和 Controller Server 组合起来可以执行数据卷的扩容操作
External Snapshotter 和 Controller Server 组合则可以完成快照的创建和删除
CSI Node Server
中主要包含 Kubelet
组件,包括 VolumeManager
和 VolumePlugin
,它们会去调用 CSI Plugin
去做 Mount
和 Unmount
操作;另外一个组件 Driver Registrar
主要实现的是 CSI Plugin
注册的功能。
-
CSI
对象:
主要介绍 3 种对象:VolumeAttachment
、CSIDriver
、CSINode
。
第二个对象是 VolumeAttachment
,VolumeAttachment
描述一个 Volume
在 Pod
使用中挂载、卸载的相关信息。例如,对一个 Volume
在某个节点上的挂载,通过 VolumeAttachment
对该挂载进行跟踪。AD Controller
创建一个 VolumeAttachment
,而 External-attacher
则通过观察该 VolumeAttachment
,根据其状态来进行挂载和卸载操作。
第二个对象是 CSIDriver
,它描述了集群中所部署的 CSI Plugin
列表,需要管理员根据插件类型进行创建。在 CSI Driver
中定义了它的名字,在 spec
中还定义了 attachRequired
和 podInfoOnMount
两个标签。
attachRequired 定义一个 Plugin 是否支持 Attach 功能,主要是为了对块存储和文件存储做区分。比如文件存储不需要 Attach 操作,因此将该标签定义为 False
podInfoOnMount 则是定义 Kubernetes 在调用 Mount 接口时是否带上 Pod 信息
第三个对象是 CSINode
,它是集群中的节点信息,由 node-driver-registrar
在启动时创建。它的作用是每一个新的 CSI Plugin
注册后,都会在 CSINode
列表里添加一个 CSINode
信息。
- CSI 组件之
Node-Driver-Registrar
:
Node-Driver-Registrar
主要实现了 CSI Plugin
注册机制。实现过程如下:
第一步:在启动的时候有一个约定,比如说在 /var/lib/kuberlet/plugins_registry 这个目录每新加一个文件,就相当于新加了一个 Plugin。
启动 Node-Driver-Registrar,它首先会向 CSI-Plugin 发起一个接口调用 GetPluginInfo,这个接口会返回 CSI 所监听的地址以及 CSI-Plugin 的一个 Driver name;
第二步,Node-Driver-Registrar 会监听 GetInfo 和 NotifyRegistrationStatus 两个接口;
第三步,会在 /var/lib/kuberlet/plugins_registry 目录下启动一个 Socket,生成一个 Socket 文件 ,如 diskplugin.csi.alibabacloud.com-reg.sock,
此时 Kubelet 通过 Watcher 发现这个 Socket 后,它会通过该 Socket 向 Node-Driver-Registrar 的 GetInfo 接口进行调用。
GetInfo 会把刚获得的的 CSI-Plugin 的信息返回给 Kubelet,该信息包含了 CSI-Plugin 的监听地址以及它的 Driver name;
第四步,Kubelet 通过得到的监听地址对 CSI-Plugin 的 NodeGetInfo 接口进行调用;
第五步,调用成功之后,Kubelet 会去更新一些状态信息,比如节点的 Annotations、Labels、status.allocatable 等信息,同时会创建一个 CSINode 对象;
第六步,通过对 Node-Driver-Registrar 的 NotifyRegistrationStatus 接口的调用告诉它已经把 CSI-Plugin 注册成功了。
- CSI 组件之
External-Attacher
:
External-Attacher
主要是通过 CSI Plugin
的接口来实现数据卷的挂载与卸载功能,它通过观察 VolumeAttachment
对象来实现状态的判断。VolumeAttachment
对象则是通过 AD Controller
来调用 Volume Plugin
中的 CSI Attacher
来创建的。CSI Attacher
是一个 In-Tree
类,也就是说这部分是 Kubernetes 完成的。
当 VolumeAttachment
的状态是 False
时,External-Attacher
就去调用底层的一个 Attach
功能;若期望值为 False
,就通过底层的 ControllerPublishVolume
接口实现 Detach
功能。同时,External-Attacher
也会同步一些 PV
的信息在里面。
-
CSI
部署:
CSI
的 Controller
分为两部分,一个是 Controller Server Pod
,一个是 Node Server Pod
。
一般只需要部署一个 Controller Server
,如果是多备份的,可以部署两个。Controller Server
主要是通过多个外部插件来实现的,比如说一个 Pod
中可以定义多个 External
的 Container
和一个包含 CSI Controller Server
的 Container
,此时不同的 External
组件会和 Controller Server
组成不同的功能。
而 Node Server Pod
是个 DaemonSet
,它会在每个节点上进行注册。Kubelet
会直接通过 Socket
的方式直接和 CSI Node Server
进行通信、调用 Attach/Detach/Mount/Unmount
等。
Driver Registrar
只是做一个注册的功能,会在每个节点上进行部署。
-
CSI
使用:
CSI
中定义了 driver
、volumeHandle
、volumeAttribute
、nodeAffinity
:
driver 就是定义是由哪一个插件来去实现挂载
volumeHandle 主要是指示 PV 的唯一标签
volumeAttribute 用于附加参数,比如 PV 如果定义的是 OSS,那么就可以在 volumeAttribute 定义 bucket、访问的地址等信息在里面
nodeAffinity 则可以定义一些调度信息。与 Flexvolume 类似,还可以通过 selector 和 Label 定义一些绑定条件
PV
和 PVC
示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: csi-pv
labels:
alicloud-pvname: csi-pv
spec:
capacity:
storage: 25Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
csi:
driver: diskplugin.csi.alibabacloud.com
volumeHandle: d-wz9bkj7jhbb4jo77n1tz
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.diskplugin.csi.alibabacloud.com/zone
operater: In
values:
- cn-hangzhou-a
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-disk
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 25Gi
selector:
matchLabels:
alicloud-pvname: csi-pv
SC
和 PVC
示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-disk-ssd-hangzhou-a
provisioner: diskplugin.csi.alibabacloud.com
reclaimPolicy: Delete
parameters:
type: cloud_ssd
region: cn-hangzhou
zoneid: cn-hangzhou-a
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: disk-ssd1
spec:
storageClassName: csi-disk-ssd-hangzhou-a
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 25Gi