12 KiB
26 | 容器化:分布式数据库要不要上云,你想好了吗?
你好,我是王磊,你也可以叫我Ivan。
今天,我想和你分享的话题是分布式数据库的容器化部署。当数据库遇到容器,我知道这一定是个很有争议的话题。但是,在容器化技术大规模落地的背景下,这也是一个无法回避的话题。
容器化技术可以将资源虚拟化,从而更灵活快速地调配。容器镜像为应用打包提供了完美的解决方案,也为DevOps理念的落地扫清了技术障碍。可以说,容器已经成为现代软件工程化的基础设施,容器化已经成为一个不可逆的发展趋势。
但是,具体谈到数据库的容器化,我们又有太多的纠结。常见的反对意见就是数据库因为有状态、高I/O消耗和稳定运行等要求,所以不适合容器化部署。
那么,随着技术的快速发展,这些理由是不是还成立呢?为了说清楚这个问题,我们先来介绍一些Kubernetes的基本概念。
Kubernetes基本概念
-
Container
容器化就是将物理机划分为若干容器(Container),应用程序是直接部署在容器上的,并不会感知到物理机的存在。具体来说,这个容器就是Docker,它使用的主要技术包括Cgroup和Namespace。
Cgroup是控制组群(Control Groups)的缩写,用来限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络带宽等,本质上实现了资源的隔离。Namespace修改了进程视图,使当前容器处于一个独立的进程空间,无法看到宿主机上的其他进程,本质上实现了权限上的隔离。这两项其实都是Linux平台上的成熟技术,甚至在Docker出现前已经被用在Cloud Foundary的PaaS平台上。
Docker能够快速崛起的重要原因是,它通过容器镜像可以将文件系统与应用程序一起打包。这个文件系统是指操作系统所包含的文件、配置和目录。这样,就不会出现各种环境参数差异导致的错误,应用的部署变得非常容易,完美解决了应用打包的问题。
容器的本质是进程,但复杂的应用系统往往是一个进程组。如果为每个进程建立一个容器,那么容器间就有非常密切的交互关系,这样管理起来就更加复杂。
那么,有什么简化的办法吗?
Kubernetes给出的答案就是Pod。
-
Pod
在 Kubernetes 的设计中,最基本的管理单位是 Pod,而不是容器。
Pod是Kubernetes在容器之上的一层封装,它由运行在同一主机上的一个或者多个容器构成。而且,同一个Pod中的容器可以共享一个Network Namespace和同一组数据卷,从而高效的信息交换。因为具有了这些特性,Pod通常会被类比为物理机。
Pod存在的意义还在于可以屏蔽容器之间可能存在依赖关系,这样做到Pod在拓扑关系上的完全对等,调度起来更加简单。
那么Pod又是如何被管理的呢?这就要说到Kubernetes的整体架构。
-
整体架构
总体上,Kubernetes是一个主从结构(Master-Slave)。其中Master是一个控制面板,每个Salve就是物理节点上代理Kubelet,Master与Kubelet通讯完成对物理节点上Pod和Container的管理。外部网络可以直接通过节点上的Proxy,调用容器中的服务,不需要经过Master。
Master内部又包括API Server、Scheduler、Controller和ETCD四个部分,它们的职责分工和这一讲的内容没有直接关系,为了降低学习难度,这里就不做具体介绍了。如果你想再深入了解Kubernetes相关知识,可以在张磊老师的专栏找到更多资料。
有状态服务(StatefulSet)
对于无状态服务的管理,用户可以定义好无状态服务的副本数量,Kubernetes调度器就会在不同节点上启动多个 Pod,实现负载均衡和故障转移。多个副本对应的 Pod 是无差别的,所以在节点出现故障时,可以直接在新节点上启动新的Pod替换已经失效的Pod。整个过程不涉及状态迁移问题,管理起来很简单。
但是,作为应用系统的基石,数据库和存储系统都是有状态服务。如果将它们排除在外,那容器化的价值将大打折扣。所以,Kubernetes 在V1.3 版本开始推出PetSet用于管理有状态服务,并在V1.5版本更名为StatefulSet。
通常,我们说数据库是一个有状态的服务,是指服务要依赖持久化数据,也就是存储状态。而对于StatefulSet来说,状态又分为两部分,除了存储状态,还有系统的拓扑状态。
拓扑状态
拓扑状态是指集群内节点之间的关系,在Kubernetes中就是Pod之间的关系。
对于应用服务器来说,因为它们互相之间是不感知、不依赖的,所以可以随意创建和销毁。但数据库就不一样了,即使是单体数据库,也会有主从复制的关系,从节点是依赖于主节点的。
而分布式数据库的节点角色更加复杂。除了CockroachDB这样的P2P架构,多数分布式数据库中的节点拓扑关系是不对等的,会明确的划分为计算节点、数据节点、元数据和全局时钟等。
拓扑状态管理的第一步是标识每个Pod。我们已经知道,Pod随时会被销毁、重建,而重建之后IP会发生变化,这将导致无法确定每个Pod的角色。StatefulSet采用记录域名的方式,再通过DNS解析出Pod的IP地址。这样Pod就具有了稳定的网络标识。
同时,为了确保体现Pod之间的依赖关系,StatefulSet还引入顺序的概念,将Pod的拓扑状态,也就是启动顺序,按照 Pod 的“名字 + 编号”的方式固定了下来。
存储状态
数据库的核心功能是存储,我们再来看看如何实现存储状态的管理。
早期版本中,提供了两种存储方式,分别是本地临时存储和远程存储。顾名思义,本地临时存储中的数据会随着Pod销毁而被清空,所以无法用做数据库的底层持久存储。
远程存储可以保证数据持久化,所以是一种可选方案。具体来说,就是使用持久化卷(Persistent Volume)作为数据的存储载体。当Pod进行迁移时,对应的PV也会重新挂载,而 PV 的底层实现是分布式文件系统,所以新的Pod仍然能访问之前保存的数据。
远程存储虽然可以使用,但是它与分布式数据库的架构设计理念存在很大冲突。我们在课程中介绍过,分布式数据库都是采用本地磁盘存储的模式,并且对存储层做了很多针对性的优化。如果改为远程存储,这些设计将会失效,导致性能上有较大的损失。
事实上,这就是很多人反对数据库容器化部署的最主要的原因,数据密集型应用对I/O资源的消耗巨大,远程存储无法适应这个场景。
针对这个不足,Kubernetes尝试引入本地持久卷(Local Persistent Volume)来解决。简单来说,就是一个Local PV 对应一块本地磁盘。开启Local PV特性后,调度器会先获取所有节点与Local PV对应磁盘的关系,基于这些信息来调度Pod。
与普通PV不同,当挂载Local PV的节点宕机并无法恢复时,数据可能就会丢失。这和物理机部署方案面临的问题是一样的,而分布式数据库的多副本机制正好弥补了这一点。
不过Local PV推出得较晚,直到V1.10(2018年发布)Kubernetes才相对稳定地支持调度功能,在2019年3月的V1.14中正式发布。
通过对拓扑状态和存储状态的管理,StatefulSet初步解决了有状态服务的管理问题,但其中的一些关键特性(比如Local PV)还不够成熟。另外,由于Kubernetes的抽象程度比较高,所以在真正实现分布式数据库的部署还需要很多复杂的控制。
Operator
控制逻辑的复杂性是与具体软件产品相关的,比如一个常见的问题就是对于Pod状态的判断。
Kubernetes 判断节点故障依据是每个节点上的 Kubelet 服务上报的节点状态。但是,Kubelet作为一个独立的进程,从理论上说,它能否正常工作与用户应用并没有必然联系。那么就有可能出现,Kubelet无法正常启动,但对应节点上的容器还可以正常运行。
这时,如果不处置原来的Pod,立即在其他节点创建新的Pod,应用系统可能就会出现异常。所以,调度过程还需要结合应用本身的状态来进行,其中的复杂性无法被统一封装,于是也就催生了一系列的Operator。
Operator 的工作原理就是利用了 Kubernetes 的自定义资源,来描述用户期望的部署状态;然后在自定义控制器里,根据自定义 API 对象的变化,来完成具体的部署和运维工作。简单来说,Operator 就是将运维人员对软件操作的知识代码化。
etcd-operator是最早出现的Operator,用于实现etcd的容器部署。Operator封装了有状态服务容器化的操作复杂性,对于应用系统上云的有重要的意义,有的分布式数据库厂商也开发了自己的Operator,比如TiDB和CockroachDB。
小结
那么,今天的课程就到这里了,让我们梳理一下这一讲的要点。
- 容器可以更加精细的控制资源,并通过镜像功能实现应用打包部署,使开发、测试、生产等各个环境完美统一,因此得到了越来越广泛的使用。
- 容器是单进程模型,为了适应更复杂的应用,Kubernetes又引入了Pod概念,一个Pod包含了同一节点上的多个容器。Kubernetes通过调度管理Pod将复杂的应用系统快速容器化部署,这种技术称为“容器编排”。因为容器的本质是进程,所以Kubernetes也被成为下一代操作系统。
- Kubernetes早期以支持无状态服务为主,Pod完全对等,调度过程简单。为了适应更复杂的应用部署模式,Kubernetes也在不断完善对有状态服务的支持,使用StatefulSet对象封装了相应的功能,包括拓扑状态管理和存储状态管理。在1.14版本中发布了本地持久化卷特性,增强了存储状态管理能力。
- 对于分布式系统来说,有状态服务的管理非常复杂,通过简单的Kubelet无法把握各个服务的真实状态,所以就有了Operator这个扩展方式,每个产品厂商可以拓展自定义控制器,进行更有针对性的管理。目前,Operator的接受程度在不断提高,TiDB、CockroachDB都开发了自己的Operator。
今天,容器化对于分布式数据库来说并不是一个默认选项。少数产品如TiDB和CockroachDB开始提供容器化部署方案,这和他们正在开展的云服务商业模式有比较直接的关系。而更多的分布式数据库仍然使用物理机,例如OceanBase和GoldenDB等。没有厂商的支持,依靠用户自身的力量实现容器化,还有比较大的困难。
我认为在未来的一两年里,“数据库是否要容器化部署”仍然是一个有争议的话题。但数据库的容器化趋势应该是确定的,因为Kubernetes提供支持越来越完善,真正大规模落地只是时间早晚的问题。而随着分布式数据库普及,数据库的节点规模必然会快速扩大,运维成本随之提升,这也给数据库容器化带来更强的推动力。
如果你想更积极地尝试数据库容器化,可以把大数据应用作为一个风向标。这是因为大数据应用的可用性要求低于OLTP场景,而集群规模更大,上百甚至上千节点都是正常规模。这意味着,大数据应用的容器化改造要承担的风险更小,但收益更大。
思考题
课程的最后部分是我们今天的思考题。今天的课程中,我们简要介绍了Kubernetes对有状态服务的支持情况,从中不难发现,资源调度是它非常核心的功能。我的问题是,除了Kubernetes你还知道哪些集群资源调度系统?它们在设计上有什么差异吗?
欢迎你在评论区留言和我一起讨论,我会在答疑篇和你继续讨论这个问题。如果你身边的朋友也对数据库的容器化部署这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。