容器与持久数据
【编者的话】如何持久化保存容器的数据,这是自 docker 诞生之日起就一直存在的问题。本文介绍了多个与此相关的项目,包括 Raft 算法, Etcd , CockroachDB , Flocker 和高可用 PostgresSQL 集群 Governor 等。
越来越多的用户想容器化所有的基础设施,不仅包括 web 服务器、队列和代理服务器,还包括数据库服务器、文件存储和缓存等。在 CoreOS Fest 的应用容器规范( appc )专家小组会议上,听众的第一个问题就是:对于有状态的容器,该怎么办?所谓有状态,是指这些容器需要持久保存某些数据(即状态),不能随便丢弃这些数 据。 Google 公司的 Tim Hockin 是这样回答的:「最好保证所有容器都是无状态的。当然,最终还是要提供保存状态的内部机制」。
如何持久化保存容器的数据,这是自 docker 诞生之日起就一直存在的问题。在 docker 的初始设计中,数据与容器共生共灭,人们很难把容器从一台机器迁移到另一台机器。在 docker 1.0 中,唯一与状态有关的概念是volume:容器能够存取的外部文件系统,完全脱离 docker 的管制。人们普遍的看法是不应该把数据放到容器中。
根据应用容器规范专家小组的说法,在最新的 appc 规范 中,整个容器镜像和所有的初始化文件是不可更改的,但指定的配置目录或者挂载除外。这体现的思路是管理员关心的数据应该存放在特定的、通过网络挂载的目录中,由容器编排框架管理这些目录。
这也难怪有的读者认为 Docker 公司和 CoreOS 公司从一开始就决定忽略容器数据的持久化存储问题。当然,容器领域的很多开发者也有这种感觉,他们决心弥补这方面的缺陷。在 ContainerCamp 和 CoreOS Fest 大会上,发布了多个解决容器数据持久存储问题的项目。其中一种解决方案是把容器数据持久保存在可靠的分布式存储中,管理员不用再考虑容器数据的迁移问题。
Raft 共识算法
一个可靠的分布式存储,必须确保所有节点的数据最终(如果不是立即)是一致的。单节点的数据库很容易做到这一点。如果是横跨多个节点的数据库,每个节点都 有可能失效,就需要一套复杂的逻辑保证写操作的一致性。这就是「共识」:多个节点就共享的状态达成一致。 Diego Ongaro 是 Raft 、 LogCabin 和 RAMCloud 的开发者,他向观众介绍了共识算法的工作原理。
1989 年, Leslie Lamport 提出了 Paxos 共识算法 。在随后的二十多年里,这是唯一的共识算法。但是 Paxos 算法也存在一些问题,其中最主要的一个问题是它非常之复杂。 Ongaro 说:「也许只有 5 个人真正懂得 Paxos 算法的方方面面」。如果不理解 Paxos 协议,人们没办法编写验证 Paxos 算法实现的正确性的测试。这意味着, Paxos 算法的不同实现存在很大的差别,你没办法证明某个实现是否正确,他说。
为此, Ongaro 和斯坦福大学教授 John Ousterhout 一起设计了一个既简单又容易测试和解释的共识算法,叫做 Raft 算法,含义是希望「逃离 Paxos 之岛」。想了解 Raft 算法的细节,请阅读 Ongaro 的 博士论文 。 Ongaro 说:「在决定每一步的设计取舍时,我们都会问自己:哪一种设计更容易解释?」
实现 Raft 算法 的项目有很多,其中最主要的一个项目是 CoreOS 公司的 ectd 。项目多,说明 Raft 算法确实容易理解。 Ongaro 花半小时介绍了 Raft 算法的核心内容,又展示了一个完全用 JavaScript 编写的 Raft 模型。
在Raft 集群中,每个节点都有一个共识模块和一个状态机。状态机存储的是用户关心的数据,包括记录数据状态改变历史的序列化日志。共识模块的作用是保证当前节点的 日志与其它节点的日志保持一致。为了做到这一点,所有的状态改变(写操作)只能由一个领导节点负责实施:它首先发送消息给其它节点,要求确认这次写操作; 只有大多数节点(叫做一个 quorum)确认本次写操作,领导节点才会真正地写入数据。这是一种两阶段提交模式,分布式数据库已经用了很久。
每个节点还有一个倒计时时钟,倒计时的时长是随机的,比较长。如果当前节点不可用了,最先完成倒计时的节点会发消息给其它节点,请求重新选举领导节点。如果大多数节点都确认了这条消息,那么这个节点就成为新的领导节点。新领导节点发送日志消息时,会为消息的term域设置一个新值,表明写操作发生时谁是领导节点。这能够避免日志冲突。
当然,真正的 Raft 算法不像上面描述的这么简单,例如,它还要考虑日志丢失或者多次重复发送的情况。 Ongaro 能用不到半小时的时间解释清楚 Raft 的核心设计,这意味着很多开发者都可以把 Raft 应用到开发的软件中。例如, etcd、 CockroachDB 和 Consul 项目用到了 Raft ,同时很多编程语言都有 Raft 库了,包括Python, C, Go, Erlang, Java 和 Scala 。
更新:在下面的评论中, Josh Berkus 提供了 Raft 的最新信息。
Etcd 和 Intel
Intel 公司 SDI(Software Defined Infrastructure, 软件定义基础设施) 部门的 Nic Weaver 讲述了他们公司对 etcd 的改进。 Intel 对 docker 和 CoreOS 非常感兴趣,有了它们,每个管理员能够管理大量机器。借助云托管,企业很容易扩展自己的 IT 系统,拥有大量的服务。这时候,光靠配置管理可不够, Intel 认为容器能够进一步提高企业的扩展水平。
因此, Intel 还要求公司的团队帮助改进容器基础设施软件。在第一篇文章中曾经提到, Intel 与 Supermicro 一起发布了 Tectonic 集群服务器套件 。除此之外, Intel 还做了一些软件方面的工作,它选择 etcd 作为突破口,试图提供构建真正大型 ectd 集群的解决方案。以 etcd 为基础的容器基础设施管理工具能够管理成千上万的容器,这意味着 ectd 节点的数目也随之增长,写操作的次数增长得更快。
Intel 的团队发现, etcd 集群包含的节点越多,就变得越慢。原因是 Raft 算法要求:每一个成功的写操作,都必须有 50% 以上的节点把数据同步到磁盘上并且返回成功的确认。这样,即使只是少数几个节点遇到存储慢的问题,也会拖慢整个集群的写入速度。如果去掉磁盘同步的需求, 这会消掉一个有可能拖慢集群的因素,同时也带来了风险:如果数据中心停电,整个集群的状态也无法恢复了。
Intel 的解决方法发利用了 Xeon 处理器的一个特性:异步内存自我刷新( asynchronous DRAM self-refresh, ADR )。这是一小块专门的内存,宕机时这里的信息不会丢失,系统重启后可以继续读取这些信息。 ADR 本是为专用存储设备设计的,不过 Linux 系统也提供一个 ADR API ,所以像 ectd 这样的应用程序也可以使用 ADR 。
Intel 团队修改 etcd ,用 ADR 作为日志的写入缓冲区,取得的效果非常显著。整个集群的写操作时间占总时间的比例从 25% 降至 2% ,每秒钟的写入次数翻倍,达到 10,000 次以上。修改的代码即将合并到 ectd 项目中。
CockroachDB
Raft 共识算法不仅可以用在像 etcd 这样的键值存储中,还可以用来构建功能完整的数据库。虽然 ectd 很适合保存配置信息,它并不适合作为应用程序的数据库系统,因为它没有提供事务、复杂查询处理等功能。 Cockroach 实验室的 Spenser Kimball 介绍了他们的工作,一个名为 CockroachDB 的新型数据库。为什么起这个名字呢?因为这种数据库就像「小强一样,你杀不死的。在这里杀死它,它会从别的地方重新冒出来」,他解释说。
CockroachDB 模仿的是 Google Megastore 的设计。当 Kimball 还在 Google 工作时非常熟悉 Megastore 项目。设计的主要思想是支持整个集群范围的一致性和可用性,包括对事务的支持。CockroachDB 计划在分布式键值存储之上添加 SQL 支持,类似于 Google 的 Spanner 项目,这样它就能提供事务、SQL 和键值三种最主要的数据库使用模式。 Kimball 说:「我们希望用户专心于业务应用的构建,不必费心寻找临时的分布式数据库解决方案」。
部署后的 CockroachDB 数据库由分布在服务器集群上的一组容器组成。数据库的键-值地址空间被划分为多个区间( range ),每个区间被复制到一部分节点,通常是 3 个或者 5 个,这些节点组成一个 Raft 共识组。整个集群包含多个 Raft 组, Kimball 称之为MultiRaft。这样,整个集群能够包含更多的数据,有利于数据库的扩展。
每个节点运行时,默认使用的事务模式是「序列化」( serializable ),即所有的事务都必须能够按照日志的顺序重现。如果一个序列化事务执行失败,节点能够恢复到之前的状态。这样,无需不必要的加锁机制,就能实现分布式事务。
至此, CockroachDB 好像已经解决了所有人的分布式基础设施的数据持久存储问题。但是, CockroachDB 的主要问题是还没有发布稳定版,很多规划好的功能特性,例如 SQL 支持,还没开始写。也许将来 CockroachDB 能够解决持久化存储的很多问题,现在还是要先看看其它的解决方案。
高可用 PostgresSQL
由于分布式数据库还没有达到生产环境真正可用的程度,开发者们选择现在流行的数据库进行增强,使之具备容错性,更方便容器化使用。两个构建高可用 PostgresSQL 的项目分别是 ClusterHQ 的 Flocker 和 Compose.io 的 Governor 。
ClusterHQ 公司 CTO Luke Mardsen 在 ContainerCamp 上展示了 Flocker 。 Flocker 是一个数据卷管理工具,它能够把数据库容器从一台物理主机迁移到另外一台物理主机。此前,因为有状态,重新部署数据库容器是一个有挑战的问题。现在有了 Flocker,容器编排框架能够以几乎相同的方式重新部署无状态服务和数据库容器。
Flocker 使用 ZFS on Linux 实现容器在不同物理机器之间的迁移。 Flocker 在专门的 ZFS 目录下创建 docker 卷,这样,用户就能用导出 ZFS 快照的方式实现 docker 卷的移动和复制。用户通过一个简单的声明式命令行接口执行上述操作。
Flocker 被设计成 docker 的一个插件。问题是目前 docker 还不支持插件。因此, Flocker 团队为 docker 创建了一个名为 Powerstrip 的插件基础设施。不过,这个工具还没有合并到 docker 主分支。只有合并之后, Flocker 项目才能够提供统一的管理接口。
Flocker 解决的是容器的迁移问题。 Compose 创建的 Governor 项目——根据 Chris Winslett 的讲法——解决的是容器的可用性问题。 Governor 是一个编排原型,实现了一个自我管理的、复制的 PostgresSQL 集群。你可以把它视为 Compose 基础设施的简化版。
Compose 是一个 SaaS( Software as a Service, 软件即服务) 托管公司,它提供的所有服务都必须是完全自动化的。为了提供 PostgresSQL 部署服务,必须支持自动化的数据库副本部署和故障恢复。由于用户拥有完全的数据库访问权限, Compose 希望解决方案不需要修改 PosgresSQL 的代码或者用户的数据库。
Winslett 指出,不能用 PostgresSQL 数据库集群的主节点存储集群的状态,因为必须保证主节点和副本节点的数据完全相同(译者注:如果主节点存储了集群状态,而副本节点中没有保存,二者的信息就不完全相同了)。开始时选择分布式高可用信息服务 consul 保存集群的状态。但是, consul 要求每个数据节点有 40GB 虚拟内存,低配置的云服务器节点显然达不到这样的要求。因此, Winslett 用更简单的 etcd 替换掉 consul ,这也大大简化了故障恢复的处理逻辑。
每个容器都有一个控制 PostgreSQL 的 Governor 守护进程。启动时, Governor 守护进程查询 ectd ,找出当前的主节点,然后从主节点复制数据到当前节点。如果目前还没有主节点, Governor 守护进程会请求 etcd 赋予它一个主节点密钥, ectd 确保只有一个节点能够得到这个密钥。当一个节点得到密钥成为新的主节点后,其它节点开始复制从这个节点的数据。主节点密钥是有生存时间( time-to-live, TTL )的。如果当前主节点失效,很快就会进行一次新选举,保证集群很快有一个新的主节点。
有了 Governor , Compose 管理 PostgreSQL 的方式跟管理 MongoDB 和 Elasticsearch 这样的多主、非关系型数据库的方式差不多。在 Governor 系统中, etcd 保存 PostgreSQL 节点的配置,用容器编排系统部署 PostgreSQL 节点,不需要手动管理,每个容器也不需要特别的处理。
结论
当然,与此相关的项目和演讲还有很多。例如,在 CoreOS Fest 期间, Sean McCord 的演讲讨论了 docker 容器如何使用分布式系统 ceph 作为块设备,以及如何用容器运行 ceph 的每一个节点。这种方法还比较初步。不过,如果容器服务需要大规模的文件存储,使用 ceph 是一种可行的候选方案。 Alex Crawford 还介绍了新的 CoreOS 引导配置工具 Cloudconfig 。
随着 Linux 容器生态系统从测试实例、 web 服务器拓展到数据库和文件存储,可以想见,会继续出现解决容器数据持久存储问题的新方法和新工具。参加 CoreOS Fest 和 ContainerCamp ,你能清楚地感受到 Linux 容器技术还不是很成熟。让我们共同期待,在接下来的一年中出现更多的相关项目和方法。