这篇文章上次修改于 686 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

前言

对于K8s来说,数据持久化一直是一大难题,因为伴随着pods的迁移和重建,数据不可能一直在一个节点上呆着,必须保证在重建的时候数据不丢失(数据持久化)。伴随着这个需求,各种存储卷插件也在社区的努力下慢慢迭代,最终成长为我们今日所见的,发达的CSI存储标准。

正文

公主连结——早期K8s的卷插件

不同于今日高度标准化的CSI(Container Storage Interface, 容器存储接口),在早期K8s版本(v1.0.0左右)的时候,社区就开发出了各种适用于K8s的卷插件,这些插件可以将外部存储连接到K8s,因为它们本身与K8s高度绑定,所以这些早期的卷插件被称为“in-tree plugins”。

公主焊接——将插件编译进K8s核心

然而,许多人认为这些插件存在局限性(例如大规模部署不便),因此他们另辟蹊径,将这些插件以补丁的形式焊死在K8s核心中。借此,他们编译了自己的K8s版本,并将其安装在自己的服务器中。

FlexVolume——焊接了但没有完全焊死

随着时间的流逝,K8s社区逐渐意识到,直接编译存在严重的问题————“焊死”插件会导致人们都需要独立编译维护一个K8s核心以适应需求。换句话说,人们需要一个“钓鱼竿”来坐在钓箱上舒舒服服地钓鱼,而不是在水中拿着树枝,用千奇百怪的方法叉鱼。
基于此,K8s社区开发了FlexVolume插件和其配套的驱动程序,它是一个用于调用第三方FlexVolume驱动程序的方法与变量的逻辑封装。

同编译的区别

从本质上来说,FlexVolume和前文提到直接将插件编译进K8s核心的方式有相似性——它们都在内核中包含了存储卷逻辑。
但FlexVolume的通用性更强,它并没有“焊死”一种存储插件,而是将一些与某一外部存储强关联的逻辑(例如说私有协议、特定API的调用和实现)摘除,做成了FlexVolume插件和其配套的驱动程序。
逻辑示意图

FlexVolume驱动程序

那么,什么是FlexVolume驱动程序呢? FlexVolume驱动程序可以是一个二进制文件、Python脚本甚至Bash脚本,总而言之它是一个可执行文件。FlexVolume插件可以通过命令行参数调用它,同时它能以JSON格式返回包含预定义字段的报文。
命令行参数可以同时提供多个,但根据约定,第一个命令行参数将作为方法,其余参数则作为调用这个方法的参数。

部署难题

倘若要使用FlexVolume插件,必须先部署FlexVolume驱动程序到集群,但部署它就意味着这个集群中的所有节点的某个预定义路径中,都得含有这个FlexVolume驱动程序。预定义路径一般是固定的,但在不同的K8s发行版中,预定义路径可能有所不同。
正如上面所说的,将FlexVolume驱动程序部署到集群中的所有节点,是一项十分艰巨的任务——某些大企业的集群可能有上百个需要部署的节点。但俗话说只要思想不滑坡,办法总比困难多,只要愿意做其实这也是能部署的。
但不幸的是,即使你有坚定的意志或者充足的人手来完成这些事,当添加新节点、自动水平缩放亦或是节点故障需要替换维护的时候,集群中大概率会出现新节点。而这些新节点往往不带有FlexVolume驱动程序,在这种情况下除非你手动复制它,否则你压根不可能在这些节点上使用PVC,数据持久化更无从谈起。

及时雨——DaemonSet

你是个好人

上述问题其实可以通过K8s的DaemonSet解决,当集群创建新节点的时候,它将自动获取DaemonSet中的新容器,这个容器可以挂载节点的目录作为本地卷,然后将FlexVolume驱动程序所需的文件拷贝到本地卷,也就是节点的目录内。

但我们不合适

尽管DaemonSet解决了新节点需要手动添加FlexVolume驱动程序的问题,但是很多驱动实际上依赖于宿主机(节点)自身的一些软件包,例如CephFS/RBD的驱动本身需要宿主机带有ceph-common软件包才能进行调用。而事实上无论用什么样的方法,K8s都无法通过现有资源在新节点上自动执行shell(以安全的方法),这也就意味着这些软件包还是得手动配置。


in-tree插件的致命缺点

上述的几种插件,经常被统称为“in-tree plugin”,即in-tree插件。前文提到过它们与k8s本身保持高度绑定关系,这意味着倘若要升级这些插件,就必须直接连着k8s集群版本一起升级。
我的上帝,你想想倘若有一天,你发现现有的插件需要进行必要的升级,而这个需要升级的插件刚好是in-tree插件,则这个升级必须重启整个K8s集群。倘若这是在生产环境,你就只能苦哈哈地找老板和客户说明情况,然后顶着因为造成生产环境事故而被炒鱿鱼的风险升级集群了!

CSI——救世主

在目睹上述问题后,K8s社区急需一种能够优雅地解决数据持久化问题的方法,于是CSI应运而生,K8s社区在K8s 1.9中发布了它的Alpha版本。

CSI是什么?

不同于FlexVolume,CSI不是一种插件,它是一种用于创建自定义组件以与数据存储一同使用的成熟标准。

CSI如何工作?

CSI使用由第三方开发的CSI驱动程序(例如Ceph的ceph-csi)。这个驱动程序通常由下列两部分组成:

Controller——控制器

Controller是管理PV(持久卷)的控制器,通常(绝大多数情况下)通过StatefulSet形式部署。

Node——节点

Node能够将PV(持久卷)安装到集群其他节点,与FlexVolume驱动程序的部署有异曲同工之妙——它也是通过DaemonSet在其他节点上部署的。同时它通过gRPC协议与Controller通信。

CSI的优点是什么?

不再需要手动编写返回

由于开发人员实现了驱动程序容器化,不像FlexVolume,它已经不再需要人们手动创建JSON格式返回。

通过集群管理的驱动程序

CSI驱动程序统一部署在K8s集群而非单个节点上,这意味着一切都发生在由K8s集群部署和控制的容器内部,这将更易于管理。

套接字请求

CSI插件避免了FlexVolume插件与驱动程序间荒谬至极的命令行参数+JSON返回通信,而是采用更加正规的IPC套接字通信。


总结

正如前文所言,CSI的优势完美地弥补了此前几种方式的不足,作为一种用于创建自定义组件以与数据存储一同使用的成熟标准,它一经推出就受到广大开发者的热烈欢迎。
由于它本身的优势,即使诸如Ceph、AWS EBS、S3这种已经有了自己的存储插件的外部存储,也已经实现了CSI驱动程序并投入生产环境使用。
在2019年初的时候,in-tree插件已经被弃用(移除)了,虽然社区仍然会维护这些插件,但更多的新功能只会被添加到CSI而不是这些老旧的插件。
CSI标准的大规模应用,也标志着K8s终于从个人私有集群上的玩物,逐渐成长为了一个能在大型生产环境胜任容器化管理的多面手,是K8s从青涩到成熟的标志。