字节跳动开源 KubeAdmiral:基于 Kubernetes 的新一代多集群编排调度引擎
KubeAdmiral 是字节跳动开源的 K8s 多集群联邦解决方案。它在字节内部孵化多年,管理超过 21 万台机器、1000 万 Pod,并已在微服务、有状态服务、离线作业等场景成功落地。
来源 | KubeWharf 社区
项目地址 | github.com/kubewharf/kubeadmiral
随着越来越多企业拥抱云原生,全球云基础设施规模仍在加速增长,Kubernetes 社区版本单集群 5000 节点的规模已经无法满足企业级大规模应用场景,同时,更多公司选择使用多云架构满足降本增效、异地容灾、环境隔离等需求,多集群管理的必要性日渐显著。
在过去几年中,字节跳动的业务飞速发展,内部 Kubernetes 集群的数量也不断壮大,集群数量超过 500,应用的副本数从 0 到 20000 不等,其中最大的某个应用体量已超过 100 万 core。
早期出于隔离和安全的考虑,字节的各个业务线独占集群,但随着业务壮大,这些独占的集群形成资源孤岛,开始影响资源的弹性效率:1)各个业务线需要维护独立的 buffer;2)业务和集群深度绑定,业务感知大量的集群,并在集群之间为应用人肉分配资源,SRE 在运营资源上也需要深度感知业务和集群,最终导致资源在各个业务线之间的周转慢、自动化效率低以及部署率不够理想。
如何解耦应用和集群的绑定关系,将各个业务线的资源并池,成为了提升云原生底座的资源利用率以及交付效率的关键。
随着多云、混合云愈发成为业内主流形态,Kubernetes 成为了云原生的操作系统,实现了对基础设施的进一步抽象和规范,为应用提供更加统一的标准接口。在此基础上,我们引入 Kubernetes 集群联邦作为分布式云场景下的云原生系统底座,面向应用提供统一的平台入口,提升应用跨集群分发的能力,做好应用跨集群的分发调度,管理好多个云云原生场景下的基础设施。
面对多集群管理带来的挑战,字节跳动基础架构团队在 2019 年以社区 KubeFed V2 为基础开启集群联邦建设。
KubeFed V2 区分主控集群和成员集群,用户在主控集群中创建“联邦对象”,KubeFed 的多个 Controller 根据联邦对象在成员集群中分发资源。联邦对象上有 Template(对象模板)、Placement(目标集群)、Overrides(集群差异化)三个字段声明对象的部署情况。
apiVersion: types.kubefed.k8s.io/v1beta1kind: FederatedDeploymentmetadata:name: test-deploymentnamespace: test-namespacespec:template: # 定义 Deployment 的所有內容,可理解成 Deployment 与 Pod template 之间的关联。metadata:labels:app: nginxspec:...placement:# 分发到指定的两个集群中clusters:- name: cluster1- name: cluster2overrides:# 在cluster2中修改副本数为5- clusterName: cluster2clusterOverrides:- path: spec.replicasvalue: 5
但是,在具体落地时,我们发现 KubeFed 并不能满足生产环境的要求:
资源利用率低:KubeFed 的副本调度策略 RSP 只能为每个成员集群设置静态权重,无法灵活应对集群资源的变化,导致不同成员集群的部署水位不均。 变更不够平滑:扩缩容时经常出现实例分布不均的现象,导致容灾能力下降。 调度语意局限:只对无状态类资源有较好的支持,对于有状态服务、作业等多样化的资源支持不足,调度扩展性差。 接入成本高:需要通过创建联邦对象进行分发,不兼容原生 API,用户和上层平台需要完全改变使用习惯。
KubeAdmiral 命名引申自 Admiral(读音[ˈædm(ə)rəl]),本意为舰队司令,加上Kube(rnetes)前缀,寓意该工具具有强大的 Kubernetes 多集群编排调度能力。
1. 丰富的多集群调度能力
调度器是联邦系统的核心组件,它负责把资源分配到成员集群中,在副本调度场景也负责计算每个集群中应得的副本,其调度的逻辑直接影响联邦多集群容灾、资源效率、稳定性等重要功能。
KubeFed 提供了 RSP 调度器进行副本调度,但其定制性和扩展性非常有限,逻辑抽象性不足,要改变其行为必须通过修改代码完成,同时对于有状态服务、作业类资源等缺乏支持能力。
KubeAdmiral 引入了更丰富的调度语义,支持通过标签、污点等更灵活的方式选择集群,提供有状态、作业类资源调度能力,同时引入依赖跟随调度等优化。调度的语意可以通过如下所示的 PropagationPolicy 对象配置:
apiVersion: core.kubeadmiral.io/v1alpha1kind: PropagationPolicymetadata:name: mypolicynamespace: defaultspec:# 提供多种集群选择方式,最终结果取交集placement: # 手动指定集群与权重- cluster: Cluster-01preferences:weight: 40- cluster: Cluster-02preferences:weight: 30- cluster: Cluster-03preferences:weight: 40clusterSelector: # 类似Pod.Spec.NodeSelector,通过label过滤集群IPv6: "true"clusterAffinity: # 类似Pod.Spec.NodeAffinity,通过label过滤集群,语法比clusterSelector更加灵活- matchExpressions:- key: regionoperator: Invalues:- beijingtolerations: # 通过污点过滤集群- key: "key1"operator: "Equal"value: "value1"effect: "NoSchedule"schedulingMode: Divide # 是否为副本数调度stickyCluster: false # 仅在首次调度,适合有状态服务或作业类服务maxClusters: 1 # 最多可分发到多少个子集群,适合有状态服务或作业类服务disableFollowerScheduling: false # 是否开启依赖调度
apiVersion: core.kubeadmiral.io/v1alpha1kind: OverridePolicymetadata:name: examplenamespace: defaultspec:# 最终匹配的集群是所有rule匹配集群的交集overrideRules:- targetClusters:# 通过名称匹配集群clusters:- member1- member2# 通过标签selector匹配集群clusterSelector:region: beijingaz: zone1# 通过基于标签的affinity匹配集群clusterAffinity:- matchExpressions:- key: regionoperator: Invalues:- beijing- key: provideroperator: Invalues:- volcengine# 在匹配的集群中,使用jsonpatch语法修改第一个容器的镜像overriders:jsonpatch:- path: "/spec/template/spec/containers/0/image"operator: replacevalue: "nginx:test"
2. 调度能力可拓展
KubeAdmiral 参考 kube-scheduler 的设计,提供了可拓展的调度框架,将调度逻辑抽象成 Filter、Score、Select 和 Replica 四个步骤,并由多个相对独立的插件各自实现其在每个步骤的逻辑。
上述 YAML 中的 PropagationPolicy 中几乎每个字段都由一个独立的内置调度插件负责实现,各插件之间互不干扰,由调度器调用需要的插件进行全局的编排。
此外,KubeAdmiral 调度器也支持通过 HTTP 协议与外部插件交互,用户可以自行编写并部署定制化的调度逻辑,满足接入公司内部系统进行调度等需求。
3. 应用调度失败自动迁移
对于副本调度的资源,KubeAdmiral 会计算出每个成员集群应得几个副本,并将副本数字段覆盖后下发到各成员集群,这一过程称为联邦调度;资源下发后,各成员集群的 kube-scheduler 又会把资源对应的 pod 分配给相应的 node,这一过程成为单集群调度。
资源下发后,有时会出现因为节点下线、资源不足、节点亲和性无法满足等等情况造成单集群调度失败的情况,如果不做处理,业务可用实例会低于预期。KubeAdmiral 提供调度失败自动迁移的功能,开启后可以识别成员集群中不可调度的副本并迁移到可容纳多余副本的集群,实现多集群资源周转。
| 集群 | A | B | C |
|---|---|---|---|
| 权重 | 1 | 1 | 1 |
| 初次联邦调度实例数 | 2 | 2 | 2 |
| 单集群调度失败的副本 | 0 | 0 | 2 |
| 自动迁移后联邦调度实例数 | 3 | 3 | 0 |
4. 根据集群水位动态调度资源
对此,KubeAdmiral 引入了基于集群水位的动态权重调度,通过收集每个集群的资源总量与使用量计算出可用量,并将可用资源量作为副本调度的权重,最终达到各个 member 集群负载均衡,且所有 member 集群的部署率都维持在 95% 以上。
5. 副本分配算法改进
KubeFed 的副本分配算法在扩缩容时经常导致实例数偏离预期,例如:30 个实例分布在 A、B、C 三个成员集群,在 rsp.rebalance = false 情况下,用户想缩容到 15 个实例:
| 集群 | A | B | C |
|---|---|---|---|
| 权重 | 10 | 10 | 10 |
| 实例数 | 15 | 15 | 0 |
| 集群 | A | B | C |
|---|---|---|---|
| 权重 | 10 | 10 | 10 |
| 实例数 | 15 | 0 | 0 |
发生这种现象的原因是 KubeFed 的副本算法首先在集群中预分配当前存在的实例数,然后再将剩余的实例按照各个集群的权重分配,如果当前集群中存在的副本数过多,就会导致实例分布与权重严重偏离。
KubeAdmiral 对 KubeFed 的副本算法进行了优化,在保证扩缩容时不产生非预期迁移的情况下,使最终分发尽量趋近于权重分布。以从 30 个实例缩容到 15 个为例,简化的算法流程如下:
1. current distribution = [15, 15, 0], total replicas: 30
2. desired distribution = [5, 5, 5], total replicas: 15
3. distance = desired - current = [-10, -10, 5], total distance: 15
4. 对于缩容场景,去掉正数项 distance = [-10, -10, 0]
5. 以 distance 为权重,重新分配差值 15:[-7, -8, 0]
6. 最终调度结果:[-7, -8, 0] + [15, 15, 0] -> [8, 7, 0]
| 集群 | A | B | C |
|---|---|---|---|
| 权重 | 10 | 10 | 10 |
| 实例数 | 8 | 7 | 0 |
6. 支持原生资源
不同于 KubeFed 要求用户使用一套完全不兼容的新 API,KubeAdmiral 迎合 Kubernetes 单集群用户的使用习惯,提供了原生 Kubernetes API 的支持。用户创建原生资源(如 Deployment)后,由 Federate Controller 将其自动转化为联邦内部对象供其他 controller 使用,用户可以快速从单集群迁移到多集群架构,低门槛享受多集群带来的便利。
KubeAdmiral 并未止步于此。在单集群中,Kubernetes 的原生 controller 会更新一些资源的 status 反映其当前的状态,用户或上层系统经常依赖 status 查看部署情况、健康状态等信息。在多集群中,资源的 status 分散在多个集群中,用户要查看全局的状态,就必须逐个查看每个集群中资源的 status,造成视图碎片化、运维效率低下等问题。
为了解决这个问题,无缝支持原生资源,KubeAdmiral 提供了 status 汇聚的能力,Status Aggregator 将多个成员集群中资源的 status 进行合并与融合,并写回原生资源,让用户无需感知多集群拓扑,就可以一目了然地观测到资源在整个联邦中的状态。
继续完善有状态、作业类等资源的编排调度能力,沉淀出自动迁移、比价调度等高级能力,拥抱批量计算多云多集群时代的来临。 改善用户体验,提供开箱即用的解决方案,进一步减少用户的认知负担。
提高可观测性,对日志与监控指标进行优化,同时提高调度器的可解释性。
探索一键联邦化、多集群迁移等功能,完全释放多集群架构的潜力。
云厂商合作层面,火山引擎现已接入 KubeWharf 生态, 将以 KubeAdmiral 为基础,打造企业级多云多集群管理新模式——分布式云原生平台(DCP)。
多集群编排调度本质上并不简单,一个通用、完善的多集群联邦系统必定需要各种场景的打磨,我们期待有更多朋友关注与加入 KubeAdmiral 社区,也欢迎大家试用 KubeAdmiral 并给我们提出各种建议!
火山引擎云原生团队主要负责火山引擎公有云及私有化场景中 PaaS 类产品体系的构建,结合字节跳动多年的云原生技术栈经验和最佳实践沉淀,帮助企业加速数字化转型和创新。产品包括容器服务、镜像仓库、分布式云原生平台、函数服务、服务网格、持续交付、可观测服务等。