前面我们从应用的角度分析了,Kubernetes为了给在其上运行的容器化服务提供存储能力所引入的抽象出来的资源对象。接下来我们看一下Kubernetes与存储系统的交互机制,以及其与特定存储系统的一步步的解耦过程。
简单来说,我们对存储系统的核心需求有两个:申请存储空间并将其最终挂载到应用容器中。对用户来说,只需要创建资源对象(PVC)。而真正替我们实现这两个核心需求的,是集群中的存储相关控制器。存储相关控制器会“观察”到资源字段的变化,并触发相应的动作来完成存储申请和挂载等操作。
下面我们结合图10-3来介绍一个包含PVC的Pod的创建过程,以及各组件的交互细节。

图10-3 集群存储插件
(1)用户通过API Server创建包含PVC的Pod对象。
(2)调度器把这个Pod分配给某个节点。
(3)Kubelet开始等待Volume Manager准备好存储设备。
(4)PV Controller调用相应Volume Plugin申请存储并创建PV与PVC绑定。
(5)Attach-Detach Controller或者Kubelet的Volume Manager通过Volume Plugin将存储设备挂载到节点上。
(6)Volume Manager等待存储设备挂载完成后,将Volume挂载到Pod可访问的目录下。
(7)Kubelet启动Pod并将存储挂载到相应的容器中。
总的来说就是通过PV Controller监控PV、PVC、SC等资源对象,然后调用相应的存储插件去申请存储空间,并通过Attach-Detach Controlle或Kubelet Volume Manager将相应存储空间挂载到指定节点上,然后Pod在启动的过程中将其挂载到容器可访问的目录上。
早期与特定存储交互的逻辑是直接写到Kubernetes的代码中的(如图10-3中的GCE、Azure存储插件),这种被称为in-tree的方式,随着支持Kubernetes的云厂商的不断增加,会对Kubernetes本身的维护和发展造成难以控制的影响。因此从Kubernetes 1.8开始,Kubernetes Storage SIG停止接受in-tree Volume Plugin,并建议所有存储提供商使用out-of-tree Volume Plugin。目前有两种推荐的实现方式:FlexVolume和容器存储接口CSI。
FlexVolume把Kubelet对它的调用转化为对可执行程序命令行的调用,其基本思路就是把自己实现的卷插件程序放到指定的路径,供Kubelet创建Pod过程中的特定阶段调用,这样通过二进制命令行形式扩展存储插件能够提供的功能非常有限,部署也很不方便。
而CSI通过规定一组标准的网络Client调用接口(gRPC接口),让存储提供商去提供网络服务端的调用实现,这样存储系统就完全是外置的服务进程,甚至可以“跑”在容器中,其架构如图10-4所示。

图10-4 集群存储CSI架构