共计 6628 个字符,预计需要花费 17 分钟才能阅读完成。
简介:2021 年双十一是阿里巴巴团体的外围利用全面云化的第二年。往年在保障稳定性的前提下,次要摸索如何利用云原生的技术劣势,降低成本,晋升资源利用率。在往年大促中,针对外围集群采纳独享共享实例混部,对立了底层资源,联合交易业务云盘化使得混部单元大促成本降落 30%+。
引言
2021 年双十一是阿里巴巴团体的外围利用全面云化的第二年。往年在保障稳定性的前提下,次要摸索如何利用云原生的技术劣势,降低成本,晋升资源利用率。在往年大促中,针对外围集群采纳独享共享实例混部,对立了底层资源,联合交易业务云盘化使得混部单元大促成本降落 30%+。
云原生数据库管控
随着云原生技术的遍及,越来越多的利用开始运行在 Kubernetes 上,Kubernetes 有一统利用交付界面的趋势。数据库产品外部也在全面推动云原生,冀望通过资源池化及与云基础设施深度集成开释数据库产品能力,数据库管控本全面基于 Kubernetes 来编排 DB 实例。因为繁多的 Kubernetes 集群规模限度无奈满足 DB 实例的部署规模,所以咱们设计了一套多集群的调度编排零碎,可能反对多个 Kubernetes 集群的资源调度。
接入 Kubernetes 集群的 Node,数据库不同的产品会在以下 2 种模式下依据业务特定自行抉择:
- 单实例独占 ECS Node:节点实例独占,资源调度工作有 ECS 实现,特点是节点间隔离性较好,然而因为 ECS 的资源严格限度,也导致了一些在稳定性和性能的问题
- ECS 资源池模式:数据库团队保护一个由较大规格的 ECS 组成的资源池,由数据库的二次调度在资源池内进行调度。特点是能够在多 Pod 间协调资源,晋升产品的稳定性和性能。
对于第二种,构建 ECSPool 由数据库团队进行二次调度的场景下,要求采纳 cgroup 隔离组的模式进行实例 Pod 的资源限度,对于云上的客户而言,存在两种实例状态:
- 独享:采纳 cgroup 外面 cpu-set 的模式来进行 cpu 资源的绑定
- 共享:采纳 cgroup 隔离组的外面的 cpu-share 来进行资源隔离,并肯定水平上镜像 cpu 资源的超卖。
底层的 ECS 资源池会依据两种不同隔离模式严格划分,分为独占资源池和共享资源池。某一个 Node,一旦调配了独享实例就不再容许调配共享实例,严格划分了两个独立的资源池。
个别状况下,资源池由阿里云数据库团队来进行对立调度。因为节点池足够大,辨别不同的资源池并不会产生资源利用不佳的状况。但对于服务于企业级的客户而言,因为不与其余客户进行混合部署,这种严格辨别的模式资源利用率就不是最佳。举个例子:客户由 4 个实例,2 个采纳共享模式,2 个须要采纳独占模式;依照之前的调度逻辑,客户则必须洽购 4 台机器能力实现实例的部署。因而,为了解决下面的问题,咱们心愿可能将外围实例和非核心实例混部部署在同一台机器上。同时外围实例采纳 CPUSet 的形式保障独享局部 CPU 外围,非核心实例采纳 CPUShare 的形式共享残余的 CPU 外围,实现资源的充分利用。
CPUSet 和 CPUShare 模式混部计划
独享共享混部的前提是独享实例的性能不受影响,因为必须对共享实例应用的资源进行限度。在 Kubernetes 中,通过 cgroup 能够限度 pod 能够应用的 cpu 和 memory 的上上限。其中,对于内存的限度比较简单,给 pod 设置一个内存最大可用内存,一旦超过内存下限就会触发 OOM。对于 cpu 的限度,Kubernetes 采纳 cfs quota 来限度过程在单位工夫内可用的工夫片。当独享和共享实例在同一台 node 节点上的时候,一旦实例的工作负载减少,可能会导致独享实例工作负载在不同的 cpu 外围上来回切换,影响独享实例的性能。所以,为了不影响独享实例的性能,咱们心愿在同一个 node 上,独享实例和共享实例的 cpu 可能离开绑定,互不影响。
Kubernetes 原生混部实现
在 linux 中,通过 cgroup 的 cpuset 子系统能够实现绑定过程到指定的 CPU 下面,docker 也有相干的启动参数来反对绑定容器到指定的 CPU 上。
--cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
Kubernetes 从 1.8 版本当前,提供了 CPU Manager 个性来反对 cpuset 的能力。从 Kubernetes 1.12 版本开始到目前的 1.22 版本,该个性还是 Beta 版。CPU Manager 是 kubelet 中的一个模块,次要工作就是给某些合乎绑核策略的 containers 绑定到指定的 cpu 上,从而晋升这些 CPU 敏感型工作的性能。
目前 CPU Manager 反对两种 Policy,别离为 none 和 static,通过 kubelet –cpu-manager-policy 设置,将来会减少 dynamic policy 做 Container 生命周期内的 cpuset 动静调整。
none: 为 cpu manager 的默认值,相当于没有启用 cpuset 的能力。cpu request 对应到 cpu share,cpu limit 对应到 cpu quota。static: 容许为节点上具备某些资源特色的 pod 赋予加强的 CPU 亲和性和独占性。kubelet 将在 Container 启动前调配绑定的 cpu set,调配时还会思考 cpu topology 来晋升 cpu affinity
static 策略管理一个共享 CPU 资源池,最后该资源池蕴含节点上所有的 CPU 资源。可用的独占性 CPU 资源数量等于节点的 CPU 总量减去通过 –kube-reserved 或 –system-reserved 参数保留的 CPU。从 1.17 版本开始,CPU 保留列表能够通过 kublet 的 ‘–reserved-cpus’ 参数显式地设置。通过 ‘–reserved-cpus’ 指定的显式 CPU 列表优先于应用 ‘–kube-reserved’ 和 ‘–system-reserved’ 参数指定的保留 CPU。通过这些参数预留的 CPU 是以整数形式,按物理内 核 ID 升序从初始共享池获取的。共享池是 BestEffort 和 Burstable pod 运行 的 CPU 汇合。Guaranteed pod 中的容器,如果申明了非整数值的 CPU requests,也将运行在共享池的 CPU 上。只有 Guaranteed pod 中指定了整数型 CPUrequests 的容器,才会被调配独占 CPU 资源。
Kubernetes 作为通用的容器编排平台,其提供的绑核性能,具备肯定的局限性:
- 须要显示开启,且 pod 的 QoS 必须是 Guaranteed 级别,数据库实例资源超卖,pod 的 request 和 limit 设置不同。
- kubelet 的 CPU 调配策略固定且不反对灵便扩大,不能满足独共享混部的调度场景。
- 不反对动静放开绑核限度。在大促的时候,为了进步数据库性能,有时须要长期放开实例的资源限度,kubelet 不反对放开绑核。
调度平台混部实现计划
Kubernetes 默认的绑核能力,其中绑核策略是放在 kubelet 外面,不易批改和扩大,而针对不同的业务场景,绑核策略可能不同,放在调度测对立治理更加适合。因为 kubelet 仅负责执行绑核,而具体的绑核策略须要由下层业务来指定;因而在创立 CPUSet 模式的 pod 的时候,咱们给 pod 打上 annotaion,kubelet 在启动容器之前,就会将 pod 绑定指定的 CPU 上。
annotations:
alloc-spec: |
{"cpu": "Spread"}
alloc-status: '{"cpu":[0,1]}'
alloc-spec:指定具体的调配策略,反对以下几种模式:
- Spread:在物理核上打散绑定逻辑核,在同物理核查端的逻辑上合适搁置压力小的利用,对立调度也提供了利用间物理核重叠束缚的编排算法。
- SameCoreFirst:同物理核绑定优先,尽量讲逻辑核调配在雷同的物理核上。
- BindAllCPUs:绑定所有的 CPU 核。
- alloc-status:绑定的具体 cpu 核数。
当调度器在节点上调配或者开释 CPUSet 模式的 Pod 之后,CPUShare Pod 可应用的 CPU 核数量将发生变化,能够通过批改节点的 annotation 来告知 kubelet 可 share 节点的范畴:
kind: Node
metadata:
name: xx
annotations:
cpu-sharepool: '{"cpuIDs":[0,1,2,3,4,5,6,99,100,101,102,103]}'
要实现独享共享混部,只须要给独享实例指定为 CPUSet 模式,共享实例指定为 CPUShare 模式,就能够实现共享和独享实例 CPU 离开绑定,互不烦扰。但该计划有以下限度:对 Kubernetes 原生的 kubelet 组件进行了定制开发,这就限度了在通用环境下的部署。
基于调度器和扩大控制器的混部实现计划
下面剖析了 Kubernetes 原生和调度平台的混部实现计划。从剖析后果看,都无奈齐全满足数据库这边对于实现独享共享混部计划的绑核需要,咱们心愿在不批改 Kubernetes 源码的根底上,可能反对不同的绑核策略。
在介绍咱们的实现计划前,先介绍下混部在数据库管控架构下次要波及到的三个组件。
- 调度器:调度用具有全局的资源视图,可能依据业务指定的调度策略,调配不同的 CPU 外围。以后反对的 CPU 调度策略包含:独共享、NUMA 亲和性和反亲和性,IO 多队列散布等
- 控制器:负责将调度器调配的 CPU 外围设置到 pod 的 annotation 上,并对业务提供查问接口
- cgroup-controller:以 daemonset 的形式部署在每个 node 节点上,watch 本节点的 pod 资源,当发现 pod 的 annotation 上有绑核信息时,批改 pod 对应的 cgroup cpuset 配置,实现绑核。
这套架构和原生 Kubernetes 集群相比,最大的区别有两个中央,第一:多 Kubernetes 集群调度,解决数据库规模化对单集群的束缚;第二:数据库资源调度与实例创立逻辑解耦,资源调度产生在 pod 的创立之前。在实例创立的时候,先通过调度器调度分配资源,胜利后,业务才会发动工作流拉起实例。
调度器
调度器的外围流程和 Kubernetes 调度器流程相似(Filter -> Ranker -> Assume -> Process pod)
- Filter : 过滤不符合要求的 Node
- Ranker : 计算 Node 分值并抉择分值最高的 Node
- Sync Process Pod:异步解决资源申请(云盘、平安组、ENI 等)
在独共享混部中,调度器负责为独享实例调配具体的 CPU 外围,CPU 外围的调配策略反对业务自定义。在团体的独共享混部场景中,次要实现了以下逻辑流程:
Node CPU data/state 初始化和同步
IO 队列散布信息和具体的物理机型号无关,node 节点在上线的时候,会由将 node 的 IO 队列信息打到 node 的 annotation 上,调度器会 watch 到 node 节点的 IO 队列信息,并进行有效性校验并初始化。当调度器会 watch 到 annotation 变动,且满足以下两个条件会更新 CPU 信息。
- CPU 是弹升
- 原 IO 队列 CPU 散布和新的 IO 队列雷同 CPU 散布完全一致
独享实例绑核 IO 队列打散
在抉择独享实例 CPU 外围的时候,会思考按 IO 队列打散。
打散策略依赖 node 的 cpu 调配优先级(上图箭头指向是优先级由高到低)
混部 Filter 编排
调度器解耦不同 Filter,以插件化编排的形式实现定义不同业务的 Pipline
利用维度全副做反亲和解决
无论独享还是共享,同一利用在同一台机器上只有 1 个节点,确保集群维度在单机故障时的影响最多 1 /N
resourceSet.spec.scheduleConfig.policy.machineMaxList.N.key = ${APP_NAME}
resourceSet.spec.scheduleConfig.policy.machineMaxList.N.value = 1
申请一个 inver 业务的 Pod(machineMaxKey=inve、value=1)过程如下
共享独享 CPU 混合调度
未调配的主机(104C),预留 8C,最大可调配是 96C。调度两个 32C 的共享实例,再释迁徙一个 32C 的共享实例流程如下图
单个 ECS 独享实例可售最大 CPU 比例为 2 /3:即 96 核物理 CPU 可售卖的最大独享 CPU 为 64 核,残余 32 核按固定超卖比超卖给其余实例 (独享实例不可占用整台机器)
可售独享实例 CPU 计算: 可售 CPU=min(a,b)
- a. 可售 CPU 规格 = 单机可售物理 CPU-max(共享实例规格)*2
- b. 可售 CPU 规格 =【单机可售 CPU – sum(共享实例已售 CPU)】/ 超卖比
控制器
控制器为业务屏蔽了底层 Kubernetes 资源接口,业务所有的操作全副由控制器来对立进行转换。在独共享混部计划外面,控制器负责将调度器调配的绑核信息更新到 pod 的 annotation 上,并在 pod 启动的时候校验启动的绑核信息是否正确。同时在大促场景中,一旦独享实例呈现性能瓶颈,须要可能长期占用整机的 cpu 资源,控制器负责向业务提供长期放开 CPU 绑核的限度的接口。
# 独享实例 pod 须要指定的 annotation
annotations:
cpu-isolate-mode: exclusive #指定为独享实例
custom-cgroup-info: '{"engine":{"cpuset":"2-3"}}' #指定具体容器绑定的 cpu 外围
exclude-reserve-cpuset: "true" #是否须要扣除 node 上预留的 cpu 外围
release-isolate-mode: "true" #是否须要长期放开绑核限度,如果设置了能够绑定除了预留所有 cpu 外围,大促时候的应急预案
cgroup-controller
cgroup-controller 是 Kubernetes 外面的一个扩大控制器,它通过 daemonset 的形式部署在 Kubernetes 集群外面,它将宿主机上的 /sys/fs/cgroup/ 目录挂载到容器内,在 watch 到 pod 须要绑核时,依据 pod 的 qos 等级,找到对应的 cgroup 配置门路,间接批改 cgroup 的配置文件。
volumes:
- hostPath:
path: /var/run/
type: "Directory"
name: docker-sock
- hostPath:
path: /sys/fs/cgroup/
type: "Directory"
name: cgroups
在独共享混部的计划外面,cgroup-controller 除了须要可能为独享实例绑定 CPU 外围以外,还须要保护共享实例的 CPU 资源池。cgroup-controller 会记录以后节点上独享 CPU 和共享 CPU 的外围散布,当节点上的独享实例发生变化时,动静调整 node 上共享 pod 的可用的 CPU 列表。例如,以后 node 节点预留 CPU:[0-1],调度了一个独享实例占用 CPU:[2-3],cgroup-controller 会计算以后 node 节点上的共享 CPU= 总的 CPU-node 预留 - 独享 CPU,并将共享 pod 全副绑定到共享 CPU 外围上。当再调度一个独享 pod,cgroup-controller 会更新共享 CPU 外围,并刷新主机上所有共享 POD 绑定的 CPU 外围。
cgroup-controller 通过间接批改 pod 的 cgroup 配置来实现动静调整 pod 的资源配额,尽管不须要浸入 kubernetes 源码,但也存在以下几个问题:
cgroup-controller 只能在容器启动后,能力批改容器的资源配额
当独享实例发现变动时,会动静刷新共享实例的 CPU 绑核,对共享实例的性能可能存在肯定的影响
cgroup-controller 可能间接操作 DB 实例 cgroup 的资源限额,可能间接影响 DB 实例的数据面,所以在具体执行批改时会做一些必要的安全检查。比方在设置独享实例的绑定的 CPU 时,会校验绑定的外围是否满足 IO 队列散布,绑定的 CPU 外围是否超过单节点可售卖独享 CPU 的外围数等。
原文链接
本文为阿里云原创内容,未经容许不得转载。