背景
Kubernetes CSI演变过程
- kubernetes v1.9 版本——Alpha版本
- kubernetes v1.10版本——Beta版本
- kubernetes v1.12版本——GA版本
什么是Kubernetes CSI?
CSI,即容器存储接口,Kubernetes为了满足存储厂商作为云存储接入的统一存储接口,容器存储接口定义了动态卷供给、卷挂载、卷扩容、卷快照等功能。CSI的实现是一种“Out Tree”的方式,但在存储使用上如同“In Tree”的存储一样。通过CSI的方式,新增存储的适配不在核心代码中进行维护与优化,从而大大减轻了Kubernetes的核心开发人员工作量。
为什么Kubernetes要引入CSI?
- 减轻Kubernetes核心代码负担
- 降低新增存储插件代码的发布风险
- 提高存储插件的扩展性
- 发展多样化的云存储
CSI插件注册
注册流程图
本系列主要面向的阅读对象为初步学习CSI、着手开发CSI插件以及深入理解CSI原理等用户,并在后续会持续更新CSI其他模块的原理解析。本篇基于node-driver-registrar源码进行分析,结合Kubernetes中Kubelet、Volume模块,完成CSI插件的整个注册过程,如下图所示:
注册流程说明
CSI模块
这里的CSI模块主要包含node-driver-registrar和csi-driver,一般情况下它们以DaemonSet形式部署在Kubernetes上,并存在于同一个Pod中。 简单介绍一下这2个模块的作用:
- csi-driver的作用主要是对卷进行附加、分离、挂载、卸载等操作,除此之外,还为其它模块提供插件信息和能力的GRPC服务(即IdentityServer);
- node-driver-registrar的主要作用则是获取插件信息并通过GRPC服务(即RegistrationServer)向Kubelet提供插件的注册信息。 流程:
- csi-driver容器在启动时,会创建一个socket文件,通过该文件创建基于socket协议的grpc服务IdentityServer,IdentityServer提供插件信息、插件能力以及探针功能。
# csi插件的socket文件
/var/lib/kubelet/csi-plugins/<driverName>/csi.sock
- node-driver-registrar容器是作为一个sidecar容器存在,通过csi-driver创建的socket文件,向csi-driver发起插件信息请求。
- 获取信息后,node-driver-registrar将创建一个socket文件,提供注册信息服务RegistrationServer。
# 注册表socket文件,格式:<driverName>-reg.sock
/var/lib/kubelet/plugins_registry/<driverName>-reg.sock
Kubernetes-Kubelet模块
Kubelet模块中包含PluginManager,它的主要作用是通过PluginWatcher监听插件注册表目录下的socket文件创建与删除事件,来更新DesiredStateOfWorld和ActualStateOfWorld缓存,最终缓存信息被Volume模块进行消费。
# 注册表目录
/var/lib/kubelet/plugins_registry/
流程:
- PluginWatcher监听到注册表目录有新的插件创建,通过grpc请求服务端RegistrationServer校验插件是否规范。
- 校验成功后,会将需要添加的插件以"map[string]PluginInfo"类型存入DesiredStateOfWorld和ActualStateOfWorld缓存等待被消费。
- PluginManager通过Volume模块中的RegistrationHandler注册插件信息,不管成功与失败,RegistrationHandler都会通知node-driver-registrar插件是否成功被注册。
- PluginWatcher监听到注册表目录有插件删除,会从ActualStateOfWorld缓存中剔除已注册的插件。
Kubernetes-Volume模块
Volume模块中包含RegistrationHandler,主要处理缓存中的插件注册与注销操作。通过NodeInfoManager进行插件的安装与卸载,该行为实质是对资源CSINode和Node的设置,最终用于卷调度。 处理类型:
- 注册插件
- DesiredStateOfWorld缓存 > ActualStateOfWorld缓存。
- 注销插件
- ActualStateOfWorld缓存 > DesiredStateOfWorld缓存。
- 同一个插件,ActualStateOfWorld缓存的注册时间 ≠ DesiredStateOfWorld缓存的注册时间。
流程:
# 示例
driverNames:
- testplugin1.csi.abc.com
- testplugin2.csi.abc.com
nodes:
- node-1
- RegistrationHandler处理ResgisterPlugin操作,触发NodeInfoManager插件安装,资源CSINode添加drivers记录、资源Node添加annotations。
# 资源CSINode插件安装
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: node-1
ownerReferences:
- apiVersion: v1
kind: Node
name: node-1
uid: e3954ac2-3742-457c-a94c-173e54e2fa35
spec:
drivers:
- name: testplugin1.csi.abc.com
nodeID: node-1
topologyKeys:
- kubernetes.io/hostname
# 添加testplugin2.csi.abc.com信息至drivers
- name: testplugin2.csi.abc.com
nodeID: node-1
topologyKeys:
- kubernetes.io/hostname
# 资源Node插件安装
apiVersion: v1
kind: Node
metadata:
name: node-1
annotations:
# 添加testplugin2.csi.abc.com信息至annotations
csi.volume.kubernetes.io/nodeid: '{"testplugin1.csi.abc.com":"node-1", "testplugin2.csi.abc.com":"node-1"}'
- RegistrationHandler处理DeResgisterPlugin操作,触发NodeInfoManager插件卸载,资源CSINode更新drivers记录、资源Node更新annotations。
# 资源CSINode插件卸载
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: node-1
ownerReferences:
- apiVersion: v1
kind: Node
name: node-1
uid: e3954ac2-3742-457c-a94c-173e54e2fa35
spec:
drivers:
- name: testplugin1.csi.abc.com
nodeID: node-1
topologyKeys:
- kubernetes.io/hostname
# 添加testplugin2.csi.abc.com信息被删除
# 资源Node插件卸载
apiVersion: v1
kind: Node
metadata:
name: node-1
annotations:
# 添加testplugin2.csi.abc.com信息被删除
csi.volume.kubernetes.io/nodeid: '{"testplugin1.csi.abc.com":"node-1"}'
总结
至此,CSI插件的整个注册过程已经完成,Kubernetes和CSI很好的利用了C/S模式,通过GRPC实现通信。有人可能会问Kubernetes为什么不直接对/var/lib/kubelet/csi-plugins目录进行扫描,还需要通过node-driver-registrar组件? 我认为,node-driver-registrar组件的存在,一方面与csi-driver业务实现进行分离,另一方面在Kubelet注册组件之前保证CSI插件是可用的。另外,最重要的是/var/lib/kubelet/plugins_registry/目录是Kubelet统一插件注册表目录,而/var/lib/kubelet/csi-plugins仅仅只是插件中的一种,通过这种方式Kubernetes无需维护不同插件的逻辑,而是由插件提供方来将插件进行统一注册。