容器正逐渐成为云上应用的标准部署单元,容器该如何解决持久化存储的需求?
容器编排系统已成当红炸子鸡,在无状态的容器中,存储系统面临哪些新的挑战?
容器与持久化存储系统是融合架构还是分离架构?
张朝潞
有容云平台存储架构师
张朝潞,有容云平台存储架构师。曾工作于UIT、华三、腾讯、专注分布式存储的研究和开发,对云计算存储解决方案方面有很深的技术造诣和行业理解。
一.容器对存储插件的定义
以 Docker 为例, Docker 对存储卷定义了一组简单的接口,外部存储只要实现简单的接口便可以和外部存储对接。
Docker daemon 和 plugin daemon 基于 UNIX 域套接字,使用 RESTful API 进行通信,下面是详细的 API:
Plugin.Activate : 握手。
VolumeDriver.Create : 创建卷。
VolumeDriver.Mount : 挂载卷。
VolumeDriver.Path : 获取卷的路径。
VolumeDriver.Unmount : 卸载卷。
VolumeDriver.Remove : 删除卷。
VolumeDriver.List : 获取 volume 列表。
VolumeDriver.Get : 获取 volume 信息。
VolumeDriver.Capabilities : 获取 volume 属性
从上面这组接口可以看出, Docker 容器是通过 mount 的方式将外部存储挂载到本地目录,尽量使内部应用程序对存储是无感知的,应用程序就像使用本地目录一样使用外部存储卷,而将外部存储的管理交给存储 Plugin 负责(如 Flocker、Rancher Convoy,REX-Ray 等)。
容器正逐渐成为云计算平台应用程序的标准部署单元,容器能轻易的将各式各样的应用程序及其 runtime 打包成统一的对象,于是编排调度系统能把各种应用程序当成统一的容器进行处理,大大简化编排调度系统的复杂度。结合 Docker 对存储插件的定义,不难看出 Docker 希望容器的运行环境独立而纯粹,不希望引入有状态和复杂的存储系统。
二、存储插件
Convoy 作为一个 Docker volume plugin,支持不同的后端存储,为 Docker 提供 MountPoint,也就是一个的目录,可能是挂载了后端存储、本地块设备或者就是本地目录。
Convoy 的代码从结构、风格和使用的库,都与 Docker 十分相似,并且比 Docker 简单很多。
(小编:下面展开介绍一下 convoy 源代码的部分模块,不需要了解细节的读者可以直接跳到第三部分:容器、应用程序、持久化存储。)
Convoy 在源码级别上值得留意的点,我认为有两点:①插件式结构与 interface 的运用。② 作者对事物的抽象能力与方法。
1、convoy daemon (convoy / daemon)
图 1 中黑色部分
daemon 是主要的功能模块,可以接收来自 convoy client 和 Docker 的命令,对 backend 存储进行了抽象,以便统一管理。下面先从 daemon 的启动开始。
1.1、daemon 进程启动
1)执行命令:
convoy daemon --drivers glusterfs --driver-opts glusterfs.servers=192.168.0.3 --driver-opts glusterfs.defaultvolumepool=vol2
2) convoy 程序解析参数,获得 daemon 子命令,调用到 daemon.Start 函数(convoy/daemon/daemon.go), Start 函数中主要围绕 daemon struct 建立所需要环境和配置。
3) Driver 初始化,优先从配置文件读取信息忽略命令行输入的参数,如果配置文件不存在则根据命令行参数初始化。
图 2. convoy 配置文件内容
遍历 DriverList,找到配置文件或命令行指定的 Driver,执行初始化函数 Init,并添加到 daemon.ConvoyDrivers 中。
4)根据 convoy 的工作目录的内容,更新管理元数据,图 1 中也有相应的模块。
NameUUIDIndex: volume name : volume UUID
SnapshotVolumeIndex : snapshot UUID : volume UUID
图 3.convoy 工作目录和 volume 配置文件
5) Router 注册: Router 提供两部分的路由,并将 daemon 的 Router 指向该 Router。
(1) 处理 convoy client 的命令 Client Request Router,处理客户端发送的 HTTP request。
(2) 处理来自 Docker 的请求 Docker Volume Plugin Router, convoy 本身就是 Docker 的 volume plugin,提供了如下的接口。
6) HTTP server 启动,根据 sockFile = /var/run/convoy/convoy.sock 和 上一步骤的 Router,启动 HTTP server。
2.2、daemon 的请求处理逻辑
Daemon 启动后便可以处理请求( convoy client 或 Docker),主要处理逻辑 Router 收到 HTTP 请求,将请求分发给各个模块: Docker、 volume、 snapshot、 backup。这个 4 个逻辑模块根据 driver name( 指定的或者默认的 ) 从 daemon.ConvoyDrivers 中获取对应的 Driver。 ConvoyDrivers 中的 Driver 是实现了 ConvoyDriver interface 的结构。
图 4.convoy daemon 请求处理逻辑
从图 4 中可以看出 ConvoyDriver 接口规定了 3 组接口对应 volume, snapshot, backup 的操作,用于操作 backend storage。逻辑处理最终调用这些接口访问 Backend Storage。
2.3、ConvoyDriver implement
截止到 0.4.3 版本, convoy 支持 4 种后端存储(实现了 ConvoyDriver 接口),如下表。
下面来说说,convoy 是如何对后端存储进行抽象和管理,它使用了 4 种结构 Driver, Volume, Snapshot, Device。
Driver:主要实现了 ConvoyDriver 接口,提供对 Volume, Snapshot, Backup 等功能。
Volume:管理提供到 Docker 或者 convoy client 的 Volume。
Snapshot:用于管理 Volume 的快照。
Device:管理后端存储提供的存储空间,如: devicemapper 的 device ; glusterfs 的 volume ; vfs 的目录等。
图 5.ConvoyDriver 的实现
图 5 Device 结构内容,记录了该 Driver 的后端存储的信息。
2.4、objectstore 提供实现备份的框架
Objectstore 模块是实现 BackupOperations 接口所需要的基本功能,目前实现了两种备份后端: S3 和 VFS。
它提供了两种备份方案: DeltaBlockBackup(增量块)和 BackupFile(备份文件)。
devicemapper 使用 DeltaBlockBackup 方式备份,实现了 DeltaBlockBackupOperations 接口。
vfs 使用 BackFile 方式备份。
Volume, Snapshot, Backup 用于管理备份存储的数据。
ObjectStoreDriver 后端备份存储需要实现的接口。
S3ObjectStoreDriver, VfsObjectStoreDriver 实现 ObjectStoreDriver。
Ebs 在实现 BackupOperations 接口时,使用 ebs 自身的 client 来实现 Backup。 ebs 本身就是一个分布式存储系统,不再需要额外的 objectstore 对其进行备份。
图 6. objectstore 框架
通过 vfs 备份的目录结构:
volume.cfg 的内容,保存图 6 中的 Volume 结构
backup_[ID].cfg,保存图 6 中的 Backup 结构
blocks 目录保存了 snapshot 存储的真实数据,以 block 的形式存储在不同目录。
三、容器、应用程序、持久化存储
容器与持久化存储,在我看来本不该拿来一起讨论,二者关联性是比较弱的。
容器是一种打包方式,基于这种打包方式带来了一系列的好处,如部署、程序运行环境统一,编排,调度等,诚然这些貌似与外部持久化存储真心没太大关系。
对持久化存储真正有需求的是容器里面的应用程序,应用程序对存储的需求是多种多样的。
基于容器化应用程序带来的好处,运维工程师都是期望能将更多的应用程序容器化,以减轻运维负担。
对于无状态应用程序,容器化几乎带来的只有好处。
对于一些有状态的应用程序,如数据库,需要进行容器化时,便面临持久化存储的问题。下面是一个外部持久化存储解决 MySQL 容器化问题的例子。
三台运行 MySQL 数据库的主机将持久化存储系统的虚拟磁盘映射上, MySQL 将数据写入这些虚拟磁盘中。
当其中一个 MySQL 数据库发生故障时,在新的主机上将故障主机的虚拟磁盘映射上,供 MySQL 使用,可以快速恢复数据库故障。
此时,将 MySQL 数据库容器化将变得十分简单,编排调度系统,能够快速发现 MySQL 集群异常,并快速调度其他主机上,减少故障时间。
由上述讨论,其实无论容器在或不在,存储还是存储。当然为了适应容器的快速迁移(相对于虚拟机),多种多样的应用程序对存储也提出了细粒度控制、应用感知、快速创删等新的需求,但存储作为以稳定性为重的基础设施,依然万变不离其宗。
四、持久化存储系统的选择
持久化存储系统可分为开源存储系统和商业存储系统。通常商业存储系统会由厂商解决所有问题,这里就不谈商业化存储了。
开源分布式存储方案如下:
块存储: ceph rbd, sheepdog
文件存储: glusterfs, cephfs, HDFS
对象存储: OpenStack swift, ceph rgw
块存储、文件存储、对象存储三种存储访问方式不同的存储系统,最合适容器的,我想应该是对象存储系统,对象存储系统是通过 URL 访问的,应用程序只需要知道对象存储系统的 URL 就可以直接访问存储系统,这种方式更贴近容器的无状态、临时性和快速调度。
为什么选择分布式存储系统?
1、云计算时代,传统存储不能满足虚拟化、容器对存储的需求
传统存储缺少灵活性,虚拟机、容器的部署及其负载是快速变化的,并且容器还是快速迁移的。
传统存储缺少自动化
传统存储缺少细粒度控制
传统存储的配置是非常严格的
2、构建存储的 TCO( 总拥有成本 ) 十分高昂
数据量成指数级增长,但存储的预算却没有相应的增长,传统存储的价格是无法承受之痛。
数据规模快速增长,企业往往需要过度预算,过度采购,因为传统存储的扩展,升级和替换是十分昂贵的。
3、高昂的存储系统运营成本 (OPEX)
需要专业的存储管理团队,不仅需要学习专业的存储知识,还要学习存储厂商指定的技巧。
处理存储系统问题是相当花费时间。
当然开源分布式存储系统,只解决了第 1, 2 点,第 3 点并没有得到有效的解决,反而有点加深的趋势。
Q&A
提问:请问有没有对 Flocker、 Rancher Convoy, REX-Ray 进行对比,性能,可用性及易用性?
张朝潞: Docker volume plugin 只做管理,数据流并不经过这三种插件,我们对比过 Flocker 和 convoy, flocker 成熟一些,但是两者都没达到我们项目的要求,因为这些插件开发起来比较简单,我们就自己做了一套,主要是解决高可用的问题。
提问:请问实际环境中分布式存储和容器应用运行需要注意哪些问题,数据库程序在容器中运行结合分布式存储做持久化有实际的生产环境案例吗?
张朝潞:分布式存储作为基础设施必须要稳定,目前看来容器并没有对存储提出革命性的变革。但借助外部存储可以使系统更大程度的容器化,从而得到一系列容器带来的好处。容器结合 Ceph 的案例我们确实遇到过,但是是非关键数据小范围上生产环境,难点还是在存储上。
提问:贵司是 K 党还是 M 党?技术选型是怎么考虑的?
张朝潞:我们深入研究过 Kuberntes 和 Rancher,最终选用 k8s,技术选型要根据公司内部研发人员的技术长处和兴趣点,无论是 K 或是 M 都能解决问题。
提问: gluster 与 Ceph 的区别是啥?应用场景有哪些不同?
张朝潞: gluster 支持文件接口, Ceph 支持块,文件,对象接口;简单的 gluster 比较简单,社区版本就已经足够稳定,但是海量小文件问题比较突出。 Ceph 随着 OpenStack 已经十分火了,社区很活跃,也有不少存储厂商基于 Ceph 做分布式存储产品。
提问: convoy 不支持 Ceph?并没有说明 Docker 怎么与 Ceph 结合使用 ?
张朝潞: convoy 不支持 Ceph,并且已经不再更新, Docker 跟 Ceph 结合非常简单,或者说 Docker 跟所有外部存储结合都非常简单,就像分享内容提到的满足几个接口就行了。
提问:对象存储,和块存储是不是有不同的技术选型?
张朝潞:当然两者应用场景不一样,块存储主要是在虚拟化环境、数据库场景。对象存储优势在于大规模海量数据和 HTTP 接口上。 针对不同的应用场景选型,如果需要非常大规模的,跨区域的场景建议使用 OpenStack swift,简单好用。如果应用场景既需要块存储,对象存储只需要中小规模的情况,一个 Ceph 搞定所有,也是不小的诱惑。如果公司内部没有专业的研发和运维团队,谨慎使用开源存储上生产。
提问:开源化存储 Ceph 和 sheepdog 的 IOPS 能达到多少?这两种存储的使用场景。
张朝潞:具体的 IOPS 跟物理的存储介质和如何设置 Cache 的关系太大了, Ceph 用的最多的地方就是块存储, sheepdog 是块存储。个人感觉, Ceph 的设计比较学院派,大而全,数据拆分很细,元数据很多,维护难度较大,但是社区活跃,找人比较容易一些。 Sheepdog 体量小,架构复杂度较低,但出问题估计找人难度大。两者都可以块存储,应用场景类似。
提问: Docker 已经提供了 volume 功能, convoy 做为 Docker 的插件,在那方面有做改进或者优化?而且我们在实际部署 Docker 应用的时候,一般都没有考虑过存储的问题,都是通过存储工程师分配好的文件(包含共享存储),为何要引入 convoy?
张朝潞: Docker 的 volume 是使用本地存储系统,通过 volume plugin 机制访问外部存储,如 convoy 就是 Docker volume plugin 的一种实现。
提问:贵司用 Docker 做块存储?为何不用 Ceph 呢?
张朝潞: Ceph 复杂程度太大,自用都需要专门的运维与研发团队。想要产品化还是自研靠谱。
提问:能说下 Ceph 的瓶颈在哪方面吗?
张朝潞:磁盘和网络,计算机体系结构中最慢的两个部件。 Ceph 的代码级没有什么可以优化的空间。当然 Ceph 的强一致性,造成 IO 路径加长会影响性能。
提问:我对最后那个 MySQL 的例子比较感兴趣,我的理解是三个 MySQL 实例同时连接到存储,然后一个挂了后,另起一个实例,我的问题是,这之前的三个存储是共享数据的 cluster 集群环境嘛?如果是怎么保证数据的一致性和写入冲突问题?
张朝潞:多个 MySQL 最好不要挂同一个存储空间,数据的性能瓶颈在于存储端,多个挂一个存储卷并不会提升性能。通常都是有上层业务来实现分表分库,负载均衡,从而避免一致性问题。例子中是每个 MySQL 实例挂载一个单独卷。