详解支持 kubernetes CSI的持久化容器存储

2019年01月 · XSKY

​《如何为容器提供持久化存储?》文章中我们已经介绍了Kubernetes存储系统的一些核心概念,其中包括:

  • StorageClass (SC):存储类,类似存储池的概念,包括了存储池的一些信息;
  • PersistenVolume (PV):持久卷,独立的存储资源对象,其生命周期与Pod无关;
  • PersistenVolumeClaim (PVC): 声明,代表计算任务对存储资源的需求。

这种存储结构是为了解决

  • 有状态的容器实例需要持久化卷;
  • 容器实例动态提供新卷;
  • 不同类型的应用需要不同访问模式的存储卷;
  • 解耦计算资源和存储资源。

但是对于接触Kubernetes不久的人而言,可能仍然存在不少疑惑,卷(Volume),持久卷(PersistoneVolume),存储类(StorageClass),供给器(Provisioner), 容器存储接口(CSI),动态供应(Dynamic provisioning)等等到底是什么?存储提供商如何提供自定义存储服务?下面我们将一步一步深入了解。

一、Kubernetes 存储对接如何选择

如果有使用过Docker经验的话,就会发现在Docker中就有Volume的概念,Volume本质上是容器上挂载的某个目录 [1]。

1、Volume(卷)

Kubernetes同样也有Volume的概念,它属于Pod内部共享资源存储,生命周期和Pod相同,与Container无关,即使Pod上的容器停止或者重启,Volume不会受到影响,但是如果Pod终止,那么这个Volume的生命周期也将结束。

2、Persistent Volume(持久卷)

显然,这样的存储无法满足有状态服务的需求,于是有个Persistent Volume(PV)即持久卷,故名思义,持久卷是能将数据进行持久化存储的一种资源对象。PV,它是独立于Pod的一种资源,是一种网络存储,它的生命周期和Pod无关。云原生的时代,PV的种类也包括很多,包括XSKY全面支持的iSCSI,RBD,NFS,以及CSI, CephFS, OpenSDS, GlusterFS, Cinder等网络存储。

可以看出Volume和Persistent Volume的最大区别是生命周期,PV是一种独立的存储,管理员只需要在存储上创建足够的PV,就可以提供给计算节点中的Pod来使用,而且数据是持久存储的。这种手动创建PV来提供给Pod使用的方式也叫做静态供应。

3、Dynamic Provisioning(动态供应)

iSCSI,RBD,NFS等PV分别代表了各种类型的存储资源,供集群消费,PersistentVolumeClaim(PVC)就是对这些存储资源的请求。PVC消耗的资源是PV,这很好理解,就像Pod消耗Node资源一样。于是,在Kubernetes中Pod可以不直接使用PV,而是通过PVC来使用PV。

通过PVC可以将Pod和PV解耦,Pod不需要知道确切的文件系统和支持它的持久化引擎,可以把PVC理解为存储的抽象,把底层存储细节给隔离了。另一方面,如果使用PV作为存储的话,需要集群管理员事先创建好这些PV,但是如果使用PVC的话则不需要提前准备好PV,而是通过StorageClass把存储资源定义好,Kubernetes会在需要使用的时候动态创建,这种方式称为动态供应 (Dynamic Provisioning)。

详细流程分析如下:

(1)Pod加载存储卷,请求PVC

(2)PVC根据存储类型(此处为rbd)找到存储类StorageClass

(3)Provisioner根据StorageClass动态生成一个持久卷PV

(4)持久卷PV和PVC最终形成绑定关系

(5)持久卷PV开始提供给Pod使用

简单总结下,如果在Kubernetes中运行有状态服务,比如数据库MySQL,MongoDB或者中间件Redis,RabbitMQ等,那么就需要用持久卷(PV),从而不用担心随着Pod的终止而丢失数据(相比使用了普通Volume)。 另外,从以上对比可看出,直接使用PV适合变动较少,不会频繁修改的场景,是比较直接的使用方式。

二、如何实现动态供应

面对计算层的多种存储需求,要考虑如何高效且灵活的提供存储服务,于是就有了动态供应的策略。动态供应能按需分配资源,大大减轻了运维工作,是目前最为推荐的一种方式,在很多企业生产环境中都有它的应用。

1、如何实现动态供应

显然动态供应比静态供应灵活更多,而且这种方式还解耦了Kubernetes系统的计算层和存储层,更重要的是它给存储供应商提供了可插拔式的开发模型,存储供应商只需要根据这个模型开发相应的卷插件即可为Kubernetes提供存储服务。

有如下三种方法实现卷插件:

  • In-tree Volume Plugin
  • Out-of-tree Provisioner
  • Out-of-tree CSI Driver

第一种,Kubernetes内部代码中实现了一些存储插件,用于支持一些主流网络存储,叫作In-tree Volume Plugin。

第二种 Out-of-tree Provisioner:如果官方的插件不能满足要求,存储供应商可以根据需要去定制或者优化存储插件并集成到Kubernetes系统。

第三种是容器存储接口CSI (Container Storage Interface),是Kubernetes对外开放的存储接口,实现这个接口即可集成到Kubernetes系统中。CSI特性在刚过去的12月正式GA,同时社区也宣布未来将不再对In tree/Out of tree继续开发,并将已有功能全部迁移到CSI上,所以对于存储供应商和使用者来说,第三种CSI是更推荐的解决方案。

2、几种卷插件对比

In-tree Volume Plugin

如上图所示,In-tree Volume Plugin是Kubernetes自带的,属于Kubernetes的一部分,由Kubernetes一起发布和维护,所有存储插件代码都集成在Kubernetes中,第三方存储供应商难以集成。

Out-of-tree Provisioner

其中挂载组件还是复用,只是把Provisioner组件从Kubernetes中挪出来,以供实现定制化或者自定义的高级功能。

创建流程(绿色):状态更新,监听状态,调用外部供给器,结合后端存储服务,创建一个PV对象,这一步借助外部的Provisioner组件来完成。

挂载流程(橙色):监听事件,插件挂载卷等操作由原有挂载组件继续负责。

Container Storage Interface (CSI)

一直以来,存储插件的测试、维护等事宜都由Kubernetes社区来完成,即使有贡献者提供协作也不容易合并到主分支发布。另外,存储插件需要随Kubernetes一同发布,如果存储插件存在问题有可能会影响Kubernetes其他组件的正常运行。

鉴于此,Kubernetes和CNCF决定把容器存储进行抽象,通过标准接口的形式把存储部分移到容器编排系统外部去。CSI的设计目的是定义一个行业标准,该标准将使存储供应商能够自己实现,维护和部署他们的存储插件。这些存储插件会以Sidecar Container形式运行在Kubernetes上并为容器平台提供稳定的存储服务。

如上CSI设计图:浅绿色表示从Kubernetes社区中抽离出来且可复用的组件,负责连接XSKY CSI插件(右侧)以及和Kubernetes集群交互:

  • Driver-registrar: 使用 Kubelet注册 CSI 驱动程序的 sidecar 容器,并将 NodeId (通过 GetNodeID 调用检索到 CSI endpoint)添加到 Kubernetes Node API 对象的 annotation 里面;
  • External-provisioner: 监听 Kubernetes PersistentVolumeClaim 对象的 sidecar 容器,并触发对 CSI 端点的 CreateVolume 和DeleteVolume 操作;
  • External-attacher: 可监听 Kubernetes VolumeAttachment 对象并触发 ControllerPublish 和 ControllerUnPublish 操作的 sidecar 容器,负责attache/detache卷到node节点上。

右侧橘黄色表示XSKY实现的存储插件驱动,分别有三个服务:

  • CSI identify: 标志插件服务,并维持插件健康状态;
  • CSI Controller: 创建/删除,attaching/detaching,快照等;
  • CSI Node: attach/mount、umount/detach。

动态供应方式总结:通过对比Kubernetes的In-tree Volume Plugin,以及Out-of-tree Provisioner和CSI三种方式,在对接比较常见的存储时,可以使用不需要改动的In-tree方案,因为开箱即用,但是缺点也非常明显,只支持有限的存储类型,可拓展性较差甚至有版本限制,另外官方宣布以后新特性将不再添加到其中。

相比之下,使用Out-of-tree Provisioner或者CSI则可以实现和Kubernetes的核心组件解耦,并能支持更多的存储类型和高级特性,因而也是推荐使用的一种供应方式。由于后者对编排系统而言是非侵入式插件部署,因而更受存储供应商的青睐。

三、XSKY基于CSI的容器存储方案

Kubernetes作为最流行的容器编排系统之一,用户的接受度高,XSKY也密切关注Kubernetes社区的发展。从最初的In-tree 插件支持,后来又支持Out-of-tree provisioner,到现在最新的CSI,XSKY都是同步跟进支持的。

前面已经非常详尽的对CSI技术原理进行了剖析,XSKY基于CSI的容器存储方案,具体如下图所示:

1、统一存储:XSKY基于CSI接口提供iSCSI和NFS两种容器存储方案,无论是块存储还是文件存储,都可以通过CSI的方式去使用;

2、稳定性:CSI方案将存储和计算两种资源完全解耦,实现了资源的隔离,同时CSI插件以容器化部署,因此稳定性更进一步得到保障;

3、兼容性:CSI插件以容器化部署,不会受到操作系统版本和存储集群的限制,消除环境差异所带来的不确定性以及其他约束;

4、易运维:不需要复杂的集群配置修改和准备工作,只需填好CSI容器配置,拉取镜像,一键可达;

5、无缝升级:如果之前使用的RBD,iSCSI,NFS对接,XSKY也提供完善可靠的无缝升级方案,从In-tree/Out-of-tree升级到CSI接口上;

6、丰富的特性支持:不仅仅满足客户存储的基本需求,在实际使用中,XSKY提供的存储插件有很多非常重要和倍受青睐的高级特性,支持动态扩容,根据实际需求动态地扩容和缩容。除此之外,还会有Snapshots快照以及QoS管理等其他的特性,进一步完善容器生态;

7、高性能:在容器的使用场景,通常会有非常多的Pod或者在Pod上会挂载较多的卷,需要消耗一定的资源,得益于后端存储的IO全路径性能优化以及资源的有效管理,我们仍然能保证非常高的性能。特别如今云原生时代,非常适合要求轻量,快速的容器云平台,一分钟内就能启动上百个pod,不但存储IO性能良好,并发创建也是经得起考验的。

四、总结

通过非常详尽的分析Kubernetes存储机制,从静态供应存储到动态供应,对比动态供应几种方案,以及从数据流的角度深入到每个存储组件的细节中,XSKY希望可以为用户提供更加可靠和完善的存储服务。

目前XSKY已经能为容器编排平台包括且不限于Kubernetes,Rancher和OpenShift提供RBD,iSCSI和NFS存储。同时这也是XSKY不断完善周边生态重要的一步,正如XSKY的使命:Make Data Alive(赋予数据无限可能)。

参考文献:[1] https://kubernetes.io/docs/concepts/storage/volumes/​​​​