毋庸置疑,容器与容器编排已经成为目前 IT 人员最为关注的技术之一并得到快速的普及。根据 Gartner 的调查(1),截止到 年,仅有 10% 的 CIO 对容器使用没有任何的计划,而 27% 的 CIO 已经计划将容器应用于生产环境。
1. Gartner IOCS Conference polling results
最初的容器主要应用于无状态应用,并不需要持久化的存储,但随着容器被原来越多的采用,以及其配合 Kubernetes 带来的强大的自动化管理能力,MongoDB、MySQL 和 PostgreSQL 等有状态应用被越来多的运行于容器之上,对持久化存储提出更多需求和更高的要求。
SmartX 分布式块存储(内部代号:SMTX ZBS)由 SmartX 自主开发,作为 SmartX 超融合的核心引擎,ZBS 已经被大量应用于金融、制造业、通信、地产等行业的私有云建设、虚拟化整合等场景,承载用户生产以及开发业务。其稳定性、易用性和丰富的存储特性已经经过长时间检验,并获得大量行业头部认可。
日前,SMTX ZBS 的 CSI 驱动已正式加入到 K8s 官方的驱动列表,企业客户不仅可以继续使用 SMTX ZBS 构建私有云和超融合系统,亦可使用其为 K8s 提供持久化存储,支持数据库等有状态应用,进一步加速 K8s 在企业内部更多场景落地。
Kubernetes 官网截图
以下部分概述了 CSI 的机制以及 SMTX ZBS 与 CSI 的接口实现。
概念与定义
1.K8s 的持久化存储支持
在支持持久化存储方面,K8s 提供了内嵌原生 Driver 的方式连接外部的常见存储系统例如 NFS、iSCSI、CephFS、RBD 等来满足不同业务的需求。但由于存储生态本身也在不断演进,使用 K8s 内嵌的方式来支持不断变化的存储系统在成本和时效上都会对 K8s 项目自身带来巨大的挑战。
所以和其他服务管理系统一样,K8s 逐渐的将存储系统的具体实现从主项目中分离出来,通过定义接口的方式允许第三方厂商自行接入存储服务。在这个道路上也经历了两个阶段:
1. Flex Volume, 自 1.2 版本引入。
第三方存储服务提供商直接在 K8s Server 上部署符合 Flex Volume 规范的 Driver,利用 K8s 暴露出的 mount/unmount/attach/detach 等关键 API 实现存储系统的接入。这个模式主要的问题是,在这个形态下第三方厂商的 Driver 有权限接入物理节点的根文件系统,这会带来安全上的隐患。
2. Container Storage Interface (CSI), 自 1.9 版本引入,目前已经进入 GA 阶段(1.13)。
CSI 定义了容器场景所需要的存储控制原语和完整的控制流程,并且在 K8s 的 CSI 实现中,所有的第三方 Driver 和 K8s 的其他服务扩展一样,以服务容器的形态的运行,不会直接影响到 K8s 的核心系统稳定性。
2.存储对象
CSI 定义的存储对象为持久化卷,称之为 Volume。包括两种类型:
Mounted File Volume,Node 将会把 Volume 以指定的文件格式 Mount 到 Container 上,从 Container 的角度看到的是一个目录;
Raw Block Volume, 直接将 Volume 以 Block Device(磁盘)的形态暴露给 Container,对于某些可以直接操作磁盘的服务,这个形态可以省去文件系统的开销获得更好的性能。
Raw Block Volume 目前还处于 Beta 阶段,所以下文的过程描述和 SMTX 的 CSI Driver 目前的实现方式均针对 Mounted File Volume。
3.Plugin
CSI 将一个实现了 CSI 服务的 Driver 称之为 Plugin。根据提供的功能不同将它分为两种类型:
Controller Plugin,负责存储对象(Volume)的生命周期管理,在集群中仅需要有一个即可;
Node Plugin,在必要时与使用 Volume 的容器所在的节点交互,提供诸如节点上的 Volume 挂载/卸载等动作支持,如有需要则在每个服务节点上均部署。
存储服务商可以根据自身需求实现不同的的 Plugin 组合。例如对于以 NFS 形式提供的存储服务,可以仅实现 Controller Plugin 实现资源的创建和访问权限控制,每个节点均可以通过标准的 NFS 方式获得服务,无需通过 Node Plugin 来实现挂载/卸载等操作。而以 iSCSI 形式提供的存储服务,就需要 Node Plugin 在指定节点上,通过挂载 LUN,格式化,挂载文件系统等一系列动作完成 iSCSI LUN 至容器可见的目录形式的转化。
4.Volume 生命周期
一个典型的 CSI Volume 生命周期如下图(来自 CSI SPEC)所示:
Volume 被创建后进入 CREATED 状态,此时 Volume 仅在存储系统中存在,对于所有的 Node 或者 Container 都是不可感知的;
对 CREATED 状态的 Volume 进行 Controlller Publish 动作后在指定的 Node 上进入 NODE_READY 的状态,此时 Node 可以感知到 Volume,但是 Container 依然不可见;
在 Node 对 Volume 进行 Stage 操作,进入 VOL_READY 的状态,此时 Node 已经与 Volume 建立连接。Volume 在 Node 上以某种形式存在;
在 Node 上对 Volume 进行 Publish 操作,此时 Volume 将转化为 Node 上 Container 可见的形态被 Container 利用,进入正式服务的状态;
当 Container 的生命周期结束或其他不再需要 Volume 情况发生时,Node 执行 Unpublish Volume 动作,将 Volume 与 Container 之间的连接关系解除,回退至 VOL_READY 状态;
Node Unstage 操作将会把 Volume 与 Node 的连接断开,回退至 NODE_READY 状态;
Controller Unpublish 操作则会取消 Node 对 Volume 的访问权限;
Delete 则从存储系统中销毁 Volume。
CSI 要求状态转化操作是幂等的,并在原则上保证 Volume 的状态转化是有序进行的。
根据存储使用方式和内部实现的不同,状态机可以略有区别,但对应操作必须是成对出现的。例如在不需要额外建立 Node 与 Volume 之间连接的 Stage/Unstage 阶段时,状态机就可以直接通过 Controller Publish/Unpublish 在 NODE_READY 与 PUBISHED 之间转化,而无需经过 VOL_READY 阶段。Plugin 向 CSI 注册时必须声明自身支持哪些语义。
5.RPC
CSI 要求 Plugin 支持的 RPC 包括:
Identity Service:认证服务,Controller 和 Node Plugin 均需要支持
GetPluginInfo, 获取 Plugin 基本信息
GetPluginCapabilities,获取 Plugin 支持的能力
Probe,探测 Plugin 的健康状态
Controller Service:控制服务
Volume CRUD,包括了扩容和容量探测等 Volume 状态检查与操作接口
Controller Publish/Unpublish Volume ,Node 对 Volume 的访问权限管理
Snapshot CRD,快照的创建和删除操作,目前 CSI 定义的 Snapshot 仅用于创建 Volume,未提供回滚的语义
Node Service:节点服务
Node Stage/Unstage/Publish/Unpublish/GetStats Volume,节点上 Volume 的连接状态管理
Node Expand Volume, 节点上的 Volume 扩容操作,在 volume 逻辑大小扩容之后,可能还需要同步的扩容 Volume 之上的文件系统并让使用 Volume 的 Container 感知到,所以在 Node Plugin 上需要有对应的接口
Node Get Capabilities/Info, Plugin 的基础属性与 Node 的属性查询
部署形态
CSI 使用 Sidecar 的方式实现 CSI Plugin 与 K8s 核心逻辑的解耦。Sidecar 代表监听了 CSI 指定 API 的标准容器,它与 CSI Plugin 共同组成一个 Pod 对外提供服务,它们之间通过 Socket 连接。在这个模式下,Sidecar 成为 CSI Plugin 与 K8s 之间连接的中介和隔离带。理想状态下二者可以在不直接交互和影响的情况下共同工作,解决了安全问题。
CSI 定义了如下几种 Sidecar:
external-provisioner:监听 Volume CRUD API,完成 Volume 的生命周期管理
external-attacher:监听 Controller[Publish|Unpublish]Volume API,实现 Node 和 Volume 的可见性控制
external-snapshotter:监听 Snapshot CRD API,完成 Snapshot 的生命周期管理
node-driver-register:监听 Node 基本信息查询 API,注册 Node Plugin,每个节点 Node Plugin 均需要通过 driver-register 注册自身才可以与 K8s 之间建立连接获取 Node Volume 相关请求
cluster-driver-register:用于向 K8s 注册 Plugin 整体支持的模式,包括是否跳过 Attach 阶段/是否在 Node Publish Volume 阶段时需要 K8s 提供 Pod 信息
livenessprobe:心跳检测,用于探测 Plugin 的存活状态
适用场景
在容器化发展的早期阶段,Container 多用于承担轻量型的无状态服务,对数据存储的需求大多通过本地的临时共享文件,或者用网络访问的方式将数据置于远端的日志收集或者 DB 等外部存储上。这种模式业务和数据之间从程序管理的角度看是松耦合的,互相独立,没有严格的依赖。
但是另一方面,这个模式下数据本身无法成为服务的一部分,并不能通过 K8s 统一管理。并且需要为每个应用打开通往远端存储服务的网络通道,这在安全性上有时并不是一个好的选择。
而基于持久化卷,将数据服务提供方也放入 K8s Pod 中(例如挂载持久化卷作为磁盘,上面部署容器运行 DB)作为完整应用的一部分,数据即可以做到与应用无缝的统一管理,所有应用内部 Pod 间的业务数据请求均可以在 K8s 提供的虚拟网络中进行。而基于 K8s 本身的高可用特性和 CSI Driver 的灵活配置能力也可以获得不逊色于外部存储的可靠性与性能。
SMTX ZBS x CSI
1.SMTX ZBS
SMTX ZBS 通过 iSCSI 的方式为 K8s 提供持久化存储。它的内部结构如下图所示:
SMTX ZBS 内部结构
在每个节点上部署有 Chunk Server 用于管理本地的 SSD/HDD 提供统一的高性能混合存储服务,在部分节点上部署 Meta 作为元数据管理服务,将 Chunk Server 组成高可靠集群。每个 Chunk Server 提供如 iSCSI Target 这样的协议接入服务,他们在接入上是逻辑等价的,即可以从任一一个 Chunk Server 访问到集群中所有的 Target 和 LUN。
2.ZBS CSI Driver
ZBS CSI Driver 的部署形态如下图所示:
ZBS CSI Driver 部署形态
每个 CSI Volume 与 ZBS iSCSI LUN 一一对应,它的生命周期如下:
1. Create Volume:Controller Plugin 收到创建请求之后会在 ZBS 中创建一个 iSCSI LUN ,如有必要则会自动再创建需要的 Target,ZBS 的实现中,iSCSI LUN 以及所属的 Target 均为逻辑对象,不与物理磁盘绑定。一个集群中允许创建 4096 个 Target,每个 Target 中最多允许创建 255 个 LUN,多个 CSI Volume 会处在相同的 Target 内;
2. Controller Publish Volume:目前 Kubernetes 使用 Open iSCSI 作为节点上的数据接入服务,Open iSCSI 在挂载 Target 时,会将所有 Target 内的 LUN 均挂载到主机上作为一个 Block Device(例如 /dev/sdx 这样的磁盘) 。为了避免让主机察觉到不需要也不应该访问的 LUN,ZBS 采用了近似 LUN Masking 的机制来达到这个目标。在初始 Volume 对应的 LUN 在创建时,不会允许任何 iSCSI initiator (iSCSI 的协议客户端)发现 LUN。在 Controller Publish Volume 阶段,ZBS Controller Plugin 会将指定 Node 上的 initiator 注册到 LUN 上,在这之后,关联 Node 的 iSCSI discovery 机制才可以在 Target 内发现并访问 LUN;
3. Node Stage Volume:ZBS Node Plugin 会将 LUN 通过 Open iSCSI 命令挂载至主机,呈现为一个磁盘;
4. Node Publish Volume:ZBS Node Plugin 对磁盘进行格式化(如果磁盘之前尚未被格式化,如已经格式化则为跳过对应步骤),将磁盘 Mount 到主机上提供给 Container 使用;
5. Node Unpublish Volume:ZBS Node Plugin 将磁盘上的文件系统 Unmount;
6. Node Stage Volume:ZBS Node Plugin 在主机上将断开磁盘的 iSCSI 链接;
7. Controller Unpublish Volume:ZBS Controller Plugin 向 ZBS 后端注销指定 Node 在 LUN 上的访问权限;
8. Delete Volume:ZBS Controller Plugin 请求 ZBS 删除对应的 LUN ,LUN 所占用的数据空间将会在存储系统中被回收。
ZBS CSI Driver 支持 Snapshot CRD 操作,Snapshot 的方式为 COW,相关请求仅涉及简单的元数据操作,所以关联接口会以同步模式快速响应。Snapshot 自身或者基于 Snapshot 创建的 Volume 都会立刻处于 Volume Ready 的状态。
3.数据链路
K8s 和 ZBS 之间的 iSCSI 数据链路如下图所示:
K8s 和 ZBS 之间的 iSCSI 数据链路
ZBS 使用 iSCSI Redirector 模式提供 iSCSI 接入服务,CSI Plugin 给 Driver 提供的 iSCSI 服务端地址为 iSCSI Redirector 地址,initiator 尝试连接 iSCSI Server(Target 端,ZBS 中由 Chunk 提供 Target 服务)时,Redirector 将会采用的 hash 的方式引导 initiator 重新连接向一个可用的 Chunk Server。
在重定向之后,所有的数据请求仅在 initiator 与 Chunk 之间进行,无需经过 Redirectord。ZBS 集群中所有 Chunk Server 在处理 iSCSI 接入请求时是逻辑等价的,即任一 Chunk 均可以提供集群中的任一个 Target LUN 的数据访问服务。重定向的方式可以有效的分散数据接入压力,充分利用集群性能。
4.可靠与可用性
SMTX ZBS 在设计之初就以高可靠、高可用、高性能为目标,在集群内部采用多副本,静默数据检查自动平衡和恢复等机制来保证数据的安全可靠。在这个基础之上,K8s CSI 模式获得比使用本地存储更高的安全性。
(1)异常处理
K8s 计算节点异常
Node A 上的 pod 将会被自动在其他节点(Node B)上拉起,会重新经历 Node B 上 Publish Volume 至挂载的动作。 Node B 会接入 Chunk server 提供 Pod 关联的 Volume 的 IO 服务,之前在 Node A 上已经写入 Volume 中的数据不会受到损失。
Chunk 节点异常
如果是计算节点当前连接的 Chunk 异常,则与 Chunk 节点间的链路将中断,计算节点会重新向 iSCSI Redirector 服务寻求一个新的接入节点迅速恢复服务。通常情况下这个影响的时间为秒级。
如果非当前连接的 Chunk 异常,可能会因为 IO 副本损失而产生短暂的延迟,iSCSI 链路本身不会受到影响,影响时间也为秒级。
iSCSI Redirector 节点异常
SMTX ZBS 提供 VIP(虚拟 IP) 服务,可以保证集群中有且仅有一个节点会持有该 VIP。iSCSI Redirector 运行在 VIP 节点上,当它异常时。自然会替换到新的 VIP 节点提供 iSCSI Redirector 服务。ZBS 保证所有的节点提供的 Redirector 服务是等价的。
(2)双活与异地备份
ZBS 在基本存储功能的基础上,还提供双活集群和异地备份的功能。借助这两个功能,K8s CSI Volume 可以获得同城跨数据中心的数据强一致安全性保证或者是自动的跨城市/数据中心的定期备份能力。
5. 整体部署形式
目前 SMTX 对 CSI 支持两种形态的部署形式,基于 VM 的融合模式与分离模式,后续将提供 SMTX K8s 原生融合模式的部署支持。
(1)分离模式
K8s 和 SMTX ZBS 分别是独立的物理集群,他们之间通过接入网络互相关联。接入网络需要独立于 K8s 中的业务网络和 ZBS 使用的存储网络。
(2)融合模式
融合模式下,K8s 运行在 SMTX OS 提供的虚拟机上。通过接入网络接入 SMTX OS 上的 ZBS 服务。这个部署方式可以更高效的利用物理资源。
参考资料:
CSI Design Doc:
/kubernetes/community/blob/master/contributors/design-proposals/storage/container-storage-interface.md
CSI SPEC:
/container-storage-interface/spec
CSI Driver Developer Documentation:
https://kubernetes-csi.github.io/docs/introduction.html
了解更多: