You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 26 | 容器化:分布式数据库要不要上云,你想好了吗?
你好我是王磊你也可以叫我Ivan。
今天,我想和你分享的话题是分布式数据库的容器化部署。当数据库遇到容器,我知道这一定是个很有争议的话题。但是,在容器化技术大规模落地的背景下,这也是一个无法回避的话题。
容器化技术可以将资源虚拟化从而更灵活快速地调配。容器镜像为应用打包提供了完美的解决方案也为DevOps理念的落地扫清了技术障碍。可以说容器已经成为现代软件工程化的基础设施容器化已经成为一个不可逆的发展趋势。
但是具体谈到数据库的容器化我们又有太多的纠结。常见的反对意见就是数据库因为有状态、高I/O消耗和稳定运行等要求所以不适合容器化部署。
那么随着技术的快速发展这些理由是不是还成立呢为了说清楚这个问题我们先来介绍一些Kubernetes的基本概念。
## Kubernetes基本概念
1. ### Container
容器化就是将物理机划分为若干容器Container应用程序是直接部署在容器上的并不会感知到物理机的存在。具体来说这个容器就是Docker它使用的主要技术包括Cgroup和Namespace。
Cgroup是控制组群Control Groups的缩写用来限制一个进程组能够使用的资源上限包括CPU、内存、磁盘、网络带宽等本质上实现了资源的隔离。Namespace修改了进程视图使当前容器处于一个独立的进程空间无法看到宿主机上的其他进程本质上实现了权限上的隔离。这两项其实都是Linux平台上的成熟技术甚至在Docker出现前已经被用在Cloud Foundary的PaaS平台上。
Docker能够快速崛起的重要原因是它通过容器镜像可以将文件系统与应用程序一起打包。这个文件系统是指操作系统所包含的文件、配置和目录。这样就不会出现各种环境参数差异导致的错误应用的部署变得非常容易完美解决了应用打包的问题。
容器的本质是进程,但复杂的应用系统往往是一个进程组。如果为每个进程建立一个容器,那么容器间就有非常密切的交互关系,这样管理起来就更加复杂。
那么,有什么简化的办法吗?
Kubernetes给出的答案就是Pod。
2. ### Pod
在 Kubernetes 的设计中,最基本的管理单位是 Pod而不是容器。
Pod是Kubernetes在容器之上的一层封装它由运行在同一主机上的一个或者多个容器构成。而且同一个Pod中的容器可以共享一个Network Namespace和同一组数据卷从而高效的信息交换。因为具有了这些特性Pod通常会被类比为物理机。
Pod存在的意义还在于可以屏蔽容器之间可能存在依赖关系这样做到Pod在拓扑关系上的完全对等调度起来更加简单。
那么Pod又是如何被管理的呢这就要说到Kubernetes的整体架构。
3. ### 整体架构
总体上Kubernetes是一个主从结构Master-Slave。其中Master是一个控制面板每个Salve就是物理节点上代理KubeletMaster与Kubelet通讯完成对物理节点上Pod和Container的管理。外部网络可以直接通过节点上的Proxy调用容器中的服务不需要经过Master。
![](https://static001.geekbang.org/resource/image/4b/72/4b0b3a0f84c1005c6209e0b419571d72.jpg)
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仍然能访问之前保存的数据。
![](https://static001.geekbang.org/resource/image/2d/d1/2d729737d75e0419e9ec8accc27a6cd1.jpg)
远程存储虽然可以使用,但是它与分布式数据库的架构设计理念存在很大冲突。我们在课程中介绍过,分布式数据库都是采用本地磁盘存储的模式,并且对存储层做了很多针对性的优化。如果改为远程存储,这些设计将会失效,导致性能上有较大的损失。
事实上这就是很多人反对数据库容器化部署的最主要的原因数据密集型应用对I/O资源的消耗巨大远程存储无法适应这个场景。
针对这个不足Kubernetes尝试引入本地持久卷Local Persistent Volume来解决。简单来说就是一个Local PV 对应一块本地磁盘。开启Local PV特性后调度器会先获取所有节点与Local PV对应磁盘的关系基于这些信息来调度Pod。
![](https://static001.geekbang.org/resource/image/21/66/215a6d5cc03f95e7dcaf499e3fc03266.jpg)
与普通PV不同当挂载Local PV的节点宕机并无法恢复时数据可能就会丢失。这和物理机部署方案面临的问题是一样的而分布式数据库的多副本机制正好弥补了这一点。
不过Local PV推出得较晚直到V1.102018年发布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。
## 小结
那么,今天的课程就到这里了,让我们梳理一下这一讲的要点。
1. 容器可以更加精细的控制资源,并通过镜像功能实现应用打包部署,使开发、测试、生产等各个环境完美统一,因此得到了越来越广泛的使用。
2. 容器是单进程模型为了适应更复杂的应用Kubernetes又引入了Pod概念一个Pod包含了同一节点上的多个容器。Kubernetes通过调度管理Pod将复杂的应用系统快速容器化部署这种技术称为“容器编排”。因为容器的本质是进程所以Kubernetes也被成为下一代操作系统。
3. Kubernetes早期以支持无状态服务为主Pod完全对等调度过程简单。为了适应更复杂的应用部署模式Kubernetes也在不断完善对有状态服务的支持使用StatefulSet对象封装了相应的功能包括拓扑状态管理和存储状态管理。在1.14版本中发布了本地持久化卷特性,增强了存储状态管理能力。
4. 对于分布式系统来说有状态服务的管理非常复杂通过简单的Kubelet无法把握各个服务的真实状态所以就有了Operator这个扩展方式每个产品厂商可以拓展自定义控制器进行更有针对性的管理。目前Operator的接受程度在不断提高TiDB、CockroachDB都开发了自己的Operator。
今天容器化对于分布式数据库来说并不是一个默认选项。少数产品如TiDB和CockroachDB开始提供容器化部署方案这和他们正在开展的云服务商业模式有比较直接的关系。而更多的分布式数据库仍然使用物理机例如OceanBase和GoldenDB等。没有厂商的支持依靠用户自身的力量实现容器化还有比较大的困难。
我认为在未来的一两年里“数据库是否要容器化部署”仍然是一个有争议的话题。但数据库的容器化趋势应该是确定的因为Kubernetes提供支持越来越完善真正大规模落地只是时间早晚的问题。而随着分布式数据库普及数据库的节点规模必然会快速扩大运维成本随之提升这也给数据库容器化带来更强的推动力。
如果你想更积极地尝试数据库容器化可以把大数据应用作为一个风向标。这是因为大数据应用的可用性要求低于OLTP场景而集群规模更大上百甚至上千节点都是正常规模。这意味着大数据应用的容器化改造要承担的风险更小但收益更大。
![](https://static001.geekbang.org/resource/image/21/b4/21bf3396750b548a66bdbf7f7a18efb4.jpg)
## 思考题
课程的最后部分是我们今天的思考题。今天的课程中我们简要介绍了Kubernetes对有状态服务的支持情况从中不难发现资源调度是它非常核心的功能。我的问题是除了Kubernetes你还知道哪些集群资源调度系统它们在设计上有什么差异吗
欢迎你在评论区留言和我一起讨论,我会在答疑篇和你继续讨论这个问题。如果你身边的朋友也对数据库的容器化部署这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。