正文
Kubernetes 最早是作为一个纯粹的容器编排系统而诞生的,用户部署好 Kubernetes 集群之后,直接使用其内置的各种功能部署应用服务。由于这个 PaaS 平台使用起来非常便利,吸引了很多用户,不同用户也提出了各种不同的需求,有些特性需求 Kubernetes 直接在其核心代码里面实现了,但是有些特性并不适合合并到主干分支,为满足这类需求,Kubernetes 开放出一些 API 供用户自己扩展,实现自己的需求。
当前 Kubernetes 已经发展到 v1.8,其内部的 API 变得越来越开放,使其更像是一个跑在云上的操作系统。用户可以把它当作一套云的 SDK 或 Framework 来使用,而且可以很方便地开发组件来扩展满足自己的业务需求。对有状态服务的支持就是一个很有代表性的例子。
Kubernetes 项目最早期只支持无状态服务 (Stateless Service) 来管理的,无状态服务通过 ReplicationController 定义多个副本,由 Kubernetes 调度器来决定在不同节点上启动多个 Pod,实现负载均衡和故障转移。对于无状态服务,多个副本对应的 Pod 是等价的,所以在节点出现故障时,在新节点上启动一个 Pod 与失效的 Pod 是等价的,不会涉及状态迁移问题,因而管理非常简单。
但是对于有状态服务 (Stateful Service),由于需要将数据持久化到磁盘,使得不同 Pod 之间不能再认为成等价,也就不能再像无状态服务那样随意进行调度迁移。Kubernetes v1.3 版本提出 PetSet 的概念用来管理有状态服务并于 v1.5 将其更名为 StatefulSet。
StatefulSet 明确定义一组 Pod 中每个的身份,启动和升级都按特定顺序来操作。另外使用持久化卷存储 (PersistentVolume) 来作为存储数据的载体,当节点失效 Pod 需要迁移时,对应的 PV 通过 umount / mount 方式跟着一起迁移到新节点,或者直接使用分布式文件系统作 PV 底层存储使 Pod 在迁移后仍然能访问到之前的数据。同时 Pod 在发生迁移时,其网络身份例如 IP 地址是会发生变化的,很多分布式系统不能接受这种情况。所以 StatefulSet 在迁移 Pod 时可以通过绑定域名的方式来保证 Pod 在集群中网络身份不发生变化。
然而现实中一些分布式系统更为复杂,StatefulSet 也显得捉襟见肘。举例来说,某些分布式系统的节点在加入集群或下线时还需要做些额外的注册和清理操作,或者滚动升级要考量版本兼容性等。基于这个原因 CoreOS 公司提出了 Operator 概念,并实现了 etcd-operator 和 prometheus-operator 来管理 Etcd 和 Prometheus 这样的复杂分布式系统。用户可以开发自己的 Operator,在 Kubernetes 之上实现自定义的 Controller,将有状态服务的领域特定的运维知识编码进去,从而实现对特定分布式系统的管理。同时 Operator 本身也是跑在 Kubernetes 中的一个 Pod(deployment),对 Kubernetes 系统并无侵入性。
针对 TiDB 这种复杂的分布式服务,我们开发了 tidb-operator 等一系列组件,来管理 TiDB 集群实例在 Kubernetes 平台上的创建、销毁、扩缩容、滚动升级和故障转移等运维操作。同时在上层封装一个 tidb-cloud-manager 组件,提供 RESTful 接口,实现与云平台的控制台打通。这样也就实现了一个 DBaaS (数据库即服务)架构的基本形态。
由于 TiDB 对磁盘 I/O 有比较高的要求,通过 PV 挂载网络盘性能上会有明显的性能损耗。另外 tikv 本身维护了数据多副本,这点和分布式文件系统的多副本是有重复的。所以我们要给 Pod 上挂载本地磁盘,并且在 Kubernetes 上面把 Local PV 管理起来,作为一种特定的资源来维护。Kubernetes 长期以来官方一直没有提供 Local PV 支持,本地存储只支持 hostPath 和 emptyDir 两种方式。