背景
本文分享的是 Bytedoc 3.0 关于集群交给方面的内容以及一些云原生的实践:怎么将 Bytedoc 3.0 与云原生的才能结合起来,交给用户一个开箱即用的集群,与软件层的才能相匹配,最大化展示 Bytedoc 3.0 具有的“弹性”才能。
面临的问题
数据库服务的运用者有两方:用户(事务)和 DBA(运维),运维才能不断增强,才能给用户更好的服务体验。
用户需求
运维需求
方针与思路
方针
为了解决上述问题,Bytedoc 3.0 软件层已经完结了相关的才能,咱们需求在交给层供给类似的才能,以匹配整体的“弹性”才能:
方针 1:数据库能快速扩展/康复从库,秒级扩展数据库的读才能。扩容从库实例自动化程度要高,扩容速度尽可能快;
方针 2:核算资源能够按需扩展。对核算密集型事务,能快速扩展核算资源,而且能分配更多的资源供用户运用,匹配实际负载;
方针 3:提高核算/存储资源运用率,降低事务运用本钱。
方针 4:更规范的交给才能与更高的交给质量,给用户供给更好的数据库服务。
完结思路
咱们经过“Kubernetes 云原生”化来完结咱们的方针:
Kubernetes 是业界通用的,用于自动布置,扩展和办理容器化应用程序的开源编列系统。简略地说,它能够系统化地打包、运行、办理你的服务,在这里是 Bytedoc 数据库服务。这使得咱们能够结合已有的运维经历和业界通用服务交给/办理解决计划,供给更好的、更高质量的数据库服务,以发挥 Bytedoc 3.0 十足的”弹性”才能
在ByteDoc 1.0时期,大多数数据库服务实例是直接布置到虚拟机上的,资源的分配受限于虚拟机规格的区分,无法灵敏地、按需求分配机器资源,导致大部分的机器资源闲暇出来,无法得到有用运用;一起,由于核算与存储的资源绑定,在 1.0 的自研布置平台中,完结容器化布置好不简单,虚拟机布置的计划就一直保存下来。
直到ByteDoc 3.0的出现,将数据下沉存储层,交给服务时,更多重视核算层方面的资源分配,使得容器化布置方式可行。结合 Kubernetes 对容器布置与办理的解决计划,咱们将容器大部分自动化办理操作交由给 Kubernetes 操控器办理,专注于 ByteDoc 3.0 服务编列进程,如集群布置、从库扩容等,以充分发挥 3.0 的”弹性”才能。
云原生实践
服务云原生化,实际上是一个“搬迁”的进程,将原有服务打包、运行、办理才能,以 Kubernetes 供给的解决计划为规范,重现出来。
在 Kubernetes 中,咱们把 ByteDoc 3.0 集群界说为一种定制资源(CustomResource)。供给数据库服务,实际上便是创立了一种资源;和 K8s 内置的工作负载资源一样,定制资源也需求一个操控器来进行办理,一般把这类操控器称作 Operator。
所以,ByteDoc 3.0 云原生实践的关键是构建 ByteDoc Operator。
Kubernetes 基本概念
容器化布置
前面提到,Kubernetes 是一个容器编列系统,首要的职责是办理容器生命周期,所以实际的应用程序应该提早打包成一个容器镜像,以便于交给给 K8s 来运用。
Pod
kubernetes.io/zh/docs/con…
咱们打包好的容器最终会运行在 Pod 中,由 Pod 办理起来。
Pod 是 K8s 中内置的最基本的资源之一,也是最小布置的核算单元,能够类比于一些同享机器资源的容器组。
假如一个 Pod (内的容器)因异常导致退出,一般状况下,这个 Pod 会被删去毁掉,高档 workload 会新建一个全新的 Pod 来替代它,而不是“重启”该 Pod。
高档 workload
一般状况下,咱们构建的 Operator 不会去直接创立 Pod;而是运用 K8s 供给的高档 workload 资源,他们会帮咱们把 Pod 创立出来,并供给一些基本的资源办理才能。
Deployment:
- 维护一组给定数量的、完全相同的 Pod,一般用于无状况服务。
StatefulSet:
- 相同维护一组 Pod,特别的是,每个 Pod 都有安稳的网络标明;在此情景下,假如一个 Pod 被毁掉重建,在外部运用者看来,该 Pod 是进行“重启”了,所以一般用于有状况的服务。
怎么与 K8s 交互
Kubernetes 操控面的中心是 API 服务器。API 服务器负责供给 HTTP API,以供用户、集群中的不同部分和集群外部组件彼此通讯。
一般而言,咱们与 K8s 交互是经过“声明式”,而非“动作指令式”。
#sample.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:nginx-deployment
spec:
selector:
matchLabels:
app:nginx
minReadySeconds:6
template:
metadata:
labels:
app:nginx
spec:
containers:
-name:nginx
image:nginx:1.14.2
ports:
-containerPort:80
ByteDoc 3.0 在 Kubernetes 上的架构
(ByteDoc 经典架构,由三部分组成)
mongos:
- 代理层,负责转发、简略处理用户恳求;
- 需求感知 Config Server,从中获取 Shard 的拓扑;
config server:
- 仿制集方式,区分主从;
- 存储集群元信息,包含每个 shard 内实例的地址;
shard:
- 仿制集方式,区分主从;
- server 层,处理用户恳求;
- mongos – deployment,对应无状况的服务
- config server/shard – statefulset,对应有状况的服务
关于 mongo 的仿制集,仿制集成员需求用一个网络标明(host、dns、服务发现、headless service 等)注册进装备中,才能与其他仿制集成员进行通讯;所以在这里,咱们运用 StatefulSet 供给的安稳的网络标明作为成员名称,保证数据库实例(对应的 pod)毛病康复后,以相同的身份加入到仿制集中,正常地供给服务。
联邦方式
多机房场景下的架构
实际上,上述 ByteDoc 3.0 集群是在单机房下的架构,在线上出产环境中,还要考虑多机房容灾的场景;在多机房场景下,咱们希望一个 3 副本的集群,每个核算实例是分别布置在不同的机房,但是这些副本又是需求在同一个仿制集中的,在参考了几种跨 K8s 计划后,咱们采取了下面的多机房架构:
有哪些候选的跨 K8s 的计划?
-
社区供给的 Federation V1 计划(deprecated,后续改进为 V2 计划)
首要供给两个首要才能来支撑办理多个集群:跨集群同步资源 & 跨集群服务发现
-
云原生 SRE 团队供给的通用 operator 计划
...
spec:
charts:
-name:nginx
chart:charts/nginx
values:'"replicaCount":"{{.replica_count|default1}}"'
cluster:
-placement:
-names:
-cluster-sample
execJobs:
-name:ls
command:
-ls
-"-l"
mustBefore:
-chart.nginx
var:
replica_count:'2'
- 界说一系列的 chart、K8s 集群、履行使命、变量
- 经过通用 operator 在多个 K8s 集群上完结资源布置、使命履行
- MongoDB 官方供给的跨 K8s 计划
- 经过 mongodb 额外的 k8s-agent 完结跨 K8s 集群场景下,仿制集与集群的构建
- 代码未开源
为什么选择了现在这种计划?
咱们当时的计划实际上和社区的 Federation 计划比较类似,经过以下的才能建立完好的 ByteDoc 3.0 集群:
- 资源仿制:operator 衔接 worker K8s,创立基本相同的资源
- 服务发现:分详细的网络场景,假如运用 overlay 网络,能够运用社区支撑的无头服务(headless service);假如是走 underlay 网络,则需求额外的服务发现才能
- 组建 ByteDoc 集群:将实例组建为集群的中心逻辑,比较复杂,不太简单经过使命的方式完结
因此,咱们选择这种 Meta Operator 的计划,经过 worker K8s 创立资源,后续 Operator 完结集群的建立工作。
到达希望状况
spec:
global:
chartVersion:"1.0.0.0"
bytedocVersion:"3.0"
image:"example_image"
mongos:
replicaCount:2
resources:
limits:
cpu:"4"
memory:2Gi
requests:
cpu:"1"
memory:1Gi
shard:
shardsCount:1
replicaCount:4
...
config:
replicaCount:3
...
placement:
-name:vdc1
vdc:"vdc1"
那么 ByteDoc Operator 究竟履行了什么逻辑呢?Operator 其实也满意 K8s 操控器的规划理念,也便是,每个资源方针有一个希望状况,operator 需求把资源方针调整至希望状况,这个调整进程称为 Reconcile。
再仔细探求一下,ByteDoc 集群分为 3 个组件,只需 Mongos、config server、shard 三个组件都到达希望状况,最终将组件“衔接”起来,整个集群也到达希望状况了;
所以咱们能够将整个集群的 Reconcile,分解为对若干个模块 Reconcile,当每个小的模块都到达希望状况,整个集群也到达了希望状况,bytedoc operator 大致是基于这样的规划理念。
所以,咱们的 operator 大致流程如下:
状况办理 – Status
kubernetes.io/zh/docs/con…
Status 望文生义便是用来存储一个资源方针当时状况的,能够充当状况机或小型存储运用。能够用来完结:
- 一次性操作的完结状况:如初始化是否完结
- 镜像/服务版别
- 组件 status
- 集群晋级进度操控
- 备份进度操控
- 资源改变是否完结
- 一般记录在字段observedGeneration
- 当咱们对集群 CR 做改变时,metadata 中的 generation 计数会 +1,operator 需求在到达这次改变的希望状况时,设置持平的observedGeneration计数,以便利查询 CR 改变已完结。
- 等等
status:
replica_set:
ll2022-shard-0:
inline:
added_as_shard:true
initialized:true
read_only_ready:true
ready:3
size:3
status:ready
placement:
vdc1:
ready:3
size:3
status:ready
vdc:vdc1
...
资源模板化办理 – Helm
上面提到,ByteDoc 每个组件实际上用了 K8s 内置的资源来办理的,一般状况下,咱们不需求从头编写整个 CR 的 yaml 文件,更多的是调整一个固定模板里的动态参数,比如 mongos 的实例数,CPU、Mem 资源约束等。
从此动身,工程完结上就有两种方向:
- 将 yaml 模板硬编码到代码中,经过变量替换,生成方针的资源文件;
- 将 yaml 模板以文件的方式寄存,经过字符替换,生成方针的资源文件;
模板烘托与资源发布
咱们选用的是第二种方法的一个较为高雅的版别,用 Helm (code)办理内置资源文件的烘托和发布 在 operator 镜像中,咱们依照 helm 的规范,维护了一系列 charts 文件。
在创立资源时,运用 helm sdk,将参数烘托到 charts 模板中,生成实际的资源 yaml,接着发布到 worker K8s 集群中,operator 只需求等候资源完全 ready 就能够继续履行改变了。
模板版别办理
另一个高雅的当地在于,此计划能够寄存不同版别的 charts 模板,按版别区分为不同的目录;
当咱们需求发布一个新版别的 charts 模板时,旧版别的服务并不会受到影响;而新创立的服务能够运用新版别的 charts 模板。
避免误操作
引入 K8s 帮助咱们交给/办理集群有很多便利之处,但实际上也是一把双刃剑,能够不经意地误操作多数集群,导致数据库服务可用性受损。
可能的场景包含:
- 误删去集群资源方针 -> 导致服务下线
- 误缩容实例至 0 -> 无实例供给服务
- 误删去 CRD -> 导致所有对应资源方针删去,所有服务下线
Finalizers
kubernetes.io/zh/docs/con…
这是一个 K8s 避免误删去原生的解决计划,它是一个标明位,任何涉及删去的操作,都需求查看是否存在 finalizers。
- 当 finalizer 存在时,经过 API Server 的删去资源的恳求都会被 block 住,直到 finalizer 被铲除;
- 一般 finalizer 符号由对应的 operator 办理,在每次改变发生时,查看并添加 finalizer 符号;
- 当接收到删去恳求时,判别是否满意删去条件。在咱们的场景下,一般需求等候 7 天的删去冷静期,所以假如遇到误删去操作,咱们是有 7 天的时间进行康复的;
实例数量缩容下限
这是一个很简略的计划,在接收到 CR 缩容改变时,查看其数量是否 > 0,不接受 = 0 的缩容,避免极端状况下,无可用实例的状况。
容灾才能
实践作用