当 K8s 集群达到万级规模,阿里巴巴如何解决系统各组件性能问题?

2023-11-04

作者 | 阿里云容器平台高级技术专家 曾凡松(逐灵)

本文主要介绍阿里巴巴在大规模生产环境中落地 Kubernetes 的过程中,在集群规模上遇到的典型问题以及对应的解决方案,内容包含对 etcd、kube-apiserver、kube-controller 的若干性能及稳定性增强,这些关键的增强是阿里巴巴内部上万节点的 Kubernetes 集群能够平稳支撑 2019 年天猫 618 大促的关键所在。

背景

从阿里巴巴最早期的 AI 系统(2013)开始,集群管理系统经历了多轮的架构演进,到 2018 年全面的应用 Kubernetes ,这期间的故事是非常精彩的,有机会可以单独给大家做一个分享。这里忽略系统演进的过程,不去讨论为什么 Kubernetes 能够在社区和公司内部全面的胜出,而是将焦点关注到应用 Kubernetes 中会遇到什么样的问题,以及我们做了哪些关键的优化。

file

在阿里巴巴的生产环境中,容器化的应用超过了 10k 个,全网的容器在百万的级别,运行在十几万台宿主机上。支撑阿里巴巴核心电商业务的集群有十几个,最大的集群有几万的节点。在落地 Kubernetes 的过程中,在规模上面临了很大的挑战,比如如何将 Kubernetes 应用到超大规模的生产级别。

罗马不是一天就建成的,为了了解 Kubernetes 的性能瓶颈,我们结合阿里的生产集群现状,估算了在 10k 个节点的集群中,预计会达到的规模:

  • 20w pods
  • 100w objects

file

我们基于 Kubemark 搭建了大规模集群模拟的平台,通过一个容器启动多个(50个)Kubemark 进程的方式,使用了 200 个 4c 的容器模拟了 10k 节点的 kubelet。在模拟集群中运行常见的负载时,我们发现一些基本的操作比如 Pod 调度延迟非常高,达到了惊人的 10s 这一级别,并且集群处在非常不稳定的状态。

file

当 Kubernetes 集群规模达到 10k 节点时,系统的各个组件均出现相应的性能问题,比如:

  1. etcd 中出现了大量的读写延迟,并且产生了拒绝服务的情形,同时因其空间的限制也无法承载 Kubernetes 存储大量的对象;
  2. API Server 查询 pods/nodes 延迟非常的高,并发查询请求可能地址后端 etcd oom;
  3. Controller 不能及时从 API Server 感知到在最新的变化,处理的延时较高;当发生异常重启时,服务的恢复时间需要几分钟;
  4. Scheduler 延迟高、吞吐低,无法适应阿里业务日常运维的需求,更无法支持大促态的极端场景。

etcd improvements

为了解决这些问题,阿里云容器平台在各方面都做了很大的努力,改进 Kubernetes 在大规模场景下的性能。

首先是 etcd 层面,作为 Kubernetes 存储对象的数据库,其对 Kubernetes 集群的性能影响至关重要。

file

  • 第一版本的改进,我们通过将 etcd 的数据转存到 tair 集群中,提高了 etcd 存储的数据总量。但这个方式有一个显著的弊端是额外增加的 tair 集群,增加的运维复杂性对集群中的数据安全性带来了很大的挑战,同时其数据一致性模型也并非基于 raft 复制组,牺牲了数据的安全性。

  • 第二版本的改进,我们通过将 API Server 中不同类型的对象存储到不同的 etcd 集群中。从 etcd 内部看,也就对应了不同的数据目录,通过将不同目录的数据路由到不同的后端 etcd 中,从而降低了单个 etcd 集群中存储的数据总量,提高了扩展性。

  • 第三版本的改进,我们深入研究了 etcd 内部的实现原理,并发现了影响 etcd 扩展性的一个关键问题在底层 bbolt db 的 page 页面分配算法上:随着 etcd 中存储的数据量的增长,bbolt db 中线性查找“连续长度为 n 的 page 存储页面”的性能显著下降。

为了解决该问题,我们设计了基于 segregrated hashmap 的空闲页面管理算法,hashmap 以连续 page 大小为 key, 连续页面起始 page id 为 value。通过查这个 segregrated hashmap 实现 O(1) 的空闲 page 查找,极大地提高了性能。在释放块时,新算法尝试和地址相邻的 page 合并,并更新 segregrated hashmap。更详细的算法分析可以见已发表在 CNCF 博客的博文:

https://www.cncf.io/blog/2019/05/09/performance-optimization-of-etcd-in-web-scale-data-scenario/

file

通过这个算法改进,我们可以将 etcd 的存储空间从推荐的 2GB 扩展到 100GB,极大的提高了 etcd 存储数据的规模,并且读写无显著延迟增长。除此之外,我们也和谷歌工程师协作开发了 etcd raft learner(类 zookeeper observer)/fully concurrent read 等特性,在数据的安全性和读写性能上进行增强。这些改进已贡献开源,将在社区 etcd 3.4 版本中发布。

API Server improvements

Efficient node heartbeats

在 Kubernetes 集群中,影响其扩展到更大规模的一个核心问题是如何有效的处理节点的心跳。在一个典型的生产环境中 (non-trival),kubelet 每 10s 汇报一次心跳,每次心跳请求的内容达到 15kb(包含节点上数十计的镜像,和若干的卷信息),这会带来两大问题:

  1. 心跳请求触发 etcd 中 node 对象的更新,在 10k nodes 的集群中,这些更新将产生近 1GB/min 的 transaction logs(etcd 会记录变更历史);
  2. API Server 很高的 CPU 消耗,node 节点非常庞大,序列化/反序列化开销很大,处理心跳请求的 CPU 开销超过 API Server CPU 时间占用的 80%。

file

为了解决这个问题,Kubernetes 引入了一个新的 build-in Lease API ,将与心跳密切相关的信息从 node 对象中剥离出来,也就是上图中的 Lease 。原本 kubelet 每 10s 更新一次 node 对象升级为:

  1. 每 10s 更新一次 Lease 对象,表明该节点的存活状态,Node Controller 根据该 Lease 对象的状态来判断节点是否存活;
  2. 处于兼容性的考虑,降低为每 60s 更新一次 node 对象,使得 Eviction_ _Manager 等可以继续按照原有的逻辑工作。

因为 Lease 对象非常小,因此其更新的代价远小于更新 node 对象。kubernetes 通过这个机制,显著的降低了 API Server 的 CPU 开销,同时也大幅减小了 etcd 中大量的 transaction logs,成功将其规模从 1000 扩展到了几千个节点的规模,该功能在社区 Kubernetes-1.14 中已经默认启用。

API Server load balancing

在生产集群中,出于性能和可用性的考虑,通常会部署多个节点组成高可用 Kubernetes 集群。但在高可用集群实际的运行中,可能会出现多个 API Server 之间的负载不均衡,尤其是在集群升级或部分节点发生故障重启的时候。这给集群的稳定性带来了很大的压力,原本计划通过高可用的方式分摊 API Server 面临的压力,但在极端情况下所有压力又回到了一个节点,导致系统响应时间变长,甚至击垮该节点继而导致雪崩。

下图为压测集群中模拟的一个 case,在三个节点的集群,API Server 升级后所有的压力均打到了其中一个 API Server 上,其 CPU 开销远高于其他两个节点。

file

解决负载均衡问题,一个自然的思路就是增加 load balancer。前文的描述中提到,集群中主要的负载是处理节点的心跳,那我们就在 API Server 与 kubelet 中间增加 lb,有两个典型的思路:

  1. API Server 测增加 lb,所有的 kubelets 连接 lb,典型的云厂商交付的 Kubernetes 集群,就是这一模式;
  2. kubelet 测增加 lb,由 lb 来选择 API Server。

file
在这里插入图片描述

通过压测环境验证发现,增加 lb 并不能很好的解决上面提到的问题,我们必须要深入理解 Kubernetes 内部的通信机制。深入到 Kubernetes 中研究发现,为了解决 tls 连接认证的开销,Kubernetes 客户端做了很多的努力确保“尽量复用同样的 tls 连接”,大多数情况下客户端 watcher 均工作在下层的同一个 tls 连接上,仅当这个连接发生异常时,才可能会触发重连继而发生 API Server 的切换。其结果就是我们看到的,当 kubelet 连接到其中一个 API Server 后,基本上是不会发生负载切换。为了解决这个问题,我们进行了三个方面的优化:

  1. API Server:认为客户端是不可信的,需要保护自己不被过载的请求击溃。当自身负载超过一个阈值时,发送 409 - too many requests 提醒客户端退避;当自身负载超过一个更高的阈值时,通过关闭客户端连接拒绝请求;
  2. Client:在一个时间段内频繁的收到 409 时,尝试重建连接切换 API Server;定期地重建连接切换 API Server 完成洗牌;
  3. 运维层面,我们通过设置 maxSurge=3 的方式升级 API Server,避免升级过程带来的性能抖动。

如上图左下角监控图所示,增强后的版本可以做到 API Server 负载基本均衡,同时在显示重启两个节点(图中抖动)时,能够快速的自动恢复到均衡状态。

List-Watch & Cacher

List-Watch 是 Kubernetes 中 Server 与 Client 通信最核心一个机制,etcd 中所有对象及其更新的信息,API Server 内部通过 Reflector 去 watch etcd 的数据变化并存储到内存中,controller/kubelets 中的客户端也通过类似的机制去订阅数据的变化。

file

在 List-Watch 机制中面临的一个核心问题是,当 Client 与 Server 之间的通信断开时,如何确保重连期间的数据不丢,这在 Kubernetes 中通过了一个全局递增的版本号 resourceVersion 来实现。如下图所示 Reflector 中保存这当前已经同步到的数据版本,重连时 Reflector 告知 Server 自己当前的版本(5),Server 根据内存中记录的最近变更历史计算客户端需要的数据起始位置(7)。

这一切看起来十分简单可靠,但是…

file

在 API Server 内部,每个类型的对象会存储在一个叫做 storage 的对象中,比如会有:

  1. Pod Storage
  2. Node Storage
  3. Configmap Storage

每个类型的 storage 会有一个有限的队列,存储对象最近的变更,用于支持 watcher 一定的滞后(重试等场景)。一般来说,所有类型的类型共享一个递增版本号空间(1, 2, 3, …, n),也就是如上图所示,pod 对象的版本号仅保证递增不保证连续。Client 使用 List-Watch 机制同步数据时,可能仅关注 pods 中的一部分,最典型的 kubelet 仅关注和自己节点相关的 pods,如上图所示,某个 kubelet 仅关注绿色的 pods (2, 5)。

因为 storage 队列是有限的(FIFO),当 pods 的更新时队列,旧的变更就会从队列中淘汰。如上图所示,当队列中的更新与某个 Client 无关时,Client 进度仍然保持在 rv=5,如果 Client 在 5 被淘汰后重连,这时候 API Server 无法判断 5 与当前队列最小值(7)之间是否存在客户端需要感知的变更,因此返回 Client too old version err 触发 Client 重新 list 所有的数据。为了解决这个问题,Kubernetes 引入 Watch bookmark 机制:

file

bookmark 的核心思想概括起来就是在 Client 与 Server 之间保持一个“心跳”,即使队列中无 Client 需要感知的更新,Reflector 内部的版本号也需要及时的更新。如上图所示,Server 会在合适的适合推送当前最新的 rv=12 版本号给 Client,使得 Client 版本号跟上 Server 的进展。bookmark 可以将 API Server 重启时需要重新同步的事件降低为原来的 3%(性能提高了几十倍),该功能有阿里云容器平台开发,已经发布到社区 Kubernetes-1.15 版本中。

Cacher & Indexing

除 List-Watch 之外,另外一种客户端的访问模式是直接查询 API Server,如下图所示。为了保证客户端在多个 API Server 节点间读到一致的数据,API Server 会通过获取 etcd 中的数据来支持 Client 的查询请求。从性能角度看,这带来了几个问题:

  1. 无法支持索引,查询节点的 pod 需要先获取集群中所有的 pod,这个开销是巨大的;
  2. 因为 etcd 的 request-response 模型,单次请求查询过大的数据会消耗大量的内存,通常情况下 API Server 与 etcd 之间的查询会限制请求的数据量,并通过分页的方式来完成大量的数据查询,分页带来的多次的 round trip 显著降低了性能;
  3. 为了确保一致性,API Server 查询 etcd 均采用了 Quorum read ,这个查询开销是集群级别,无法扩展的。

file

为了解决这个问题,我们设计了 API Server 与 etcd 的数据协同机制,确保 Client 能够通过 API Server 的 cache 获取到一致的数据,其原理如下图所示,整体工作流程如下:

  1. t0 时刻 Client 查询 API Server;
  2. API Server 请求 etcd 获取当前的数据版本 rv@t0;
  3. API Server 请求进度的更新,并等待 Reflector 数据版本达到 rv@t0;
  4. 通过 cache 响应用户的请求。

file

这个方式并未打破 Client 的一致性模型(感兴趣的可以自己论证一下),同时通过 cache 响应用户请求时我们可以灵活的增强查询能力,比如支持 namespace nodename/labels 索引。该增强大幅提高了 API Server 的读请求处理能力,在万台规模集群中典型的 describe node 的时间从原来的 5s 降低到 0.3s(触发了 node name 索引),其他如 get nodes 等查询操作的效率也获得了成倍的增长。

Controller failover

在 10k node 的生产集群中,Controller 中存储着近百万的对象,从 API Server 获取这些对象并反序列化的开销是无法忽略的,重启 Controller 恢复时可能需要花费几分钟才能完成这项工作,这对于阿里巴巴规模的企业来说是不可接受的。为了减小组件升级对系统可用性的影响,我们需要尽量的减小 controller 单次升级对系统的中断时间,这里通过如下图所示的方案来解决这个问题:

  1. 预启动备 controller informer ,提前加载 controller 需要的数据;
  2. 主 controller 升级时,会主动释放 Leader Lease,触发备立即接管工作。

file

通过这个方案,我们将 controller 中断时间降低到秒级别(升级时 < 2s),即使在异常宕机时,备仅需等待 leader lease 的过期(默认 15s),无需要花费几分钟重新同步数据。通过这个增强,显著的降低了 controller MTTR,同时降低了 controller 恢复时对 API Server 的性能冲击。该方案同样适用于 scheduler。

Customized scheduler

由于历史原因,阿里巴巴的调度器采用了自研的架构,因时间的关系本次分享并未展开调度器部分的增强。这里仅分享两个基本的思路,如下图所示:

  1. Equivalence classes:典型的用户扩容请求为一次扩容多个容器,因此我们通过将 pending 队列中的请求划分等价类的方式,实现批处理,显著的降低 Predicates/Priorities 的次数;
  2. Relaxed randomization:对于单次的调度请求,当集群中的候选节点非常多时,我们并不需要评估集群中全部节点,在挑选到足够的节点后即可进入调度的后续处理(通过牺牲求解的精确性来提高调度性能)。

file

总结

阿里巴巴通过一系列的增强与优化,成功将 Kubernetes 应用到生产环境并达到了单集群 10000 节点的超大规模,具体包括:

  1. 通过将索引和数据分离、数据 shard 等方式提高 etcd 存储容量,并最终通过改进 etcd 底层 bbolt db 存储引擎的块分配算法,大幅提高了 etcd 在存储大数据量场景下的性能,通过单 etcd 集群支持大规模 Kubernetes 集群,大幅简化了整个系统架构的复杂性;
  2. 通过落地 Kubernetes 轻量级心跳、改进 HA 集群下多个 API Server 节点的负载均衡、ListWatch 机制中增加 bookmark、通过索引与 Cache 的方式改进了 Kubernetes 大规模集群中最头疼的 List 性能瓶颈,使得稳定的运行万节点集群成为可能;
  3. 通过热备的方式大幅缩短了 controller/scheduler 在主备切换时的服务中断时间,提高了整个集群的可用性;
  4. 阿里巴巴自研调度器在性能优化上最有效的两个思路:等价类处理以及随机松弛算法。

通过这一系列功能增强,阿里巴巴成功将内部最核心的业务运行在上万节点的 Kubernetes 集群之上,并经历了 2019 年天猫 618 大促的考验。

作者简介:

曾凡松(花名:逐灵),阿里云云原生应用平台高级技术专家。

有丰富的分布式系统设计研发经验。在集群资源调度这一领域,曾负责的自研调度系统管理了数十万规模的节点,在集群资源调度、容器资源隔离、不同工作负载混部等方面有丰富的实践经验。当前主要负责 Kubernetes 在阿里内部的规模化落地,将 Kubernetes 应用于阿里内部的最核心电商业务,提高了应用发布效率及集群资源利用率,并稳定支撑了 2018 双十一 及 2019 618 大促。

** “ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。”**

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当 K8s 集群达到万级规模,阿里巴巴如何解决系统各组件性能问题? 的相关文章

随机推荐

  • 对象相等比较

    String的相等比较 对于String类型而言 一般用 或者equales做相等比较 前者比较字符串的引用 后者比较字符串的值 字符串常量的值存储于常量池中 只要值相同 那么引用的就是同一个字符串常量 也就是说 和equals效果一样 字
  • faster rcnn 训练自己的数据集---踩坑记录!!!

    下载代码 git clone https github com jwyang faster rcnn pytorch git 也可以暴力下载 直接download压缩包 2 解压完 cd到faser rcnn pytorch文件夹中 再创建
  • C++快速排序和一些细节思考

    一 原理 选一个基准数 通常选需要排序数组的第一个元素 将该基准数从两端开始比较 找到从左边起比此基数大的数 从右边起比此基数小的数 然后交换两数 两端相遇后一轮截止 相遇的位置就是基准数的正确位置 且基准数左边都小于此基准数 右边都大于此
  • 固高运动控制卡QT和VS(MFC)的配置

    一 QT配置 第一步 将需要的文件保存在项目下 gts h gts dll gts lib 第二步 将 gts h 加入项目 第三步 在pro文件中添加 lib文件 添加外部库后 代码为 如果不对自己导入外部库即可 win32 LIBS L
  • 【RuoYi-Vue-Plus】问题笔记 07 - V3.5.0 Redisson 报错 Unable to send PING command over channel

    文章目录 前言 参考目录 问题说明 问题解决方法 前言 最近找了一下终于解决了 Redisson 的 RedisTimeoutException 报错问题 在此记录一下 参考目录 Redisson Issues 3273 Redisson
  • 浩辰CAD 2021:深度升级,全面提升用户体验!

    在全球新冠疫情背景下 全球经济发展速度明显减缓 国内国外的市场竞争更加激烈 各企业对于提升数字化 网络化 智能化发展水平的需求也愈发迫切 这就需要企业配备更加全面和系统化的数字化设计平台 提高创新研发能力和市场竞争力 快速响应市场需求 把握
  • UnityVR--机械臂场景4-礼物和圣诞树

    本文场景中被抓取的物体是礼物 使用机械臂抓取礼物 将礼物放置在圣诞树的某个位置 来装饰圣诞树 1 礼物的设置 礼物必须具备Collider和Rigidbody 因为需要手爪放开后 礼物会自由掉落的效果 还要将礼物设置为 Goods 的标签
  • 十句话,不黄不色,但很经典~~~~~~~~~~

    1 如果钱还宽裕 别养二奶 偷偷养几个贫困山区的学生 别让人家知道你是谁 要不然见面了多尴尬 多不好意思 但是你心里一定会觉得舒坦 比包二奶提心吊胆的要好得多 如果真想包也可以包一个 好事坏事一起做 人吗 本来就复杂 2 遇到夜里摆地摊的
  • 浅谈 C/C++ 的条件编译

    1 条件编译的时机 我们都知道vscode其实是一个编辑器 你要在上面跑C或者C 你需要配置编译器 拿编译器是怎样吧一个文本文件变成一个可执行文件的呢 那必然是经历以下这四步 预处理 宏替换 头文件的展开 去注释 条件编译 编译 将预处理后
  • go语言-数组指针

    1 数组指针 1 数组指针与指针数组 这俩概念原本在c语言中就是一个绕口令般的存在 尽管从类型角度来看两者并没什么相似的地方 但是在go语言中对这两个类型的设定做出了一些不同的规定 首先交代一下基本概念 数组指针 指的是一个指针 只不过这个
  • RPC的详解和使用

    目录 一 基础介绍 1 1 为什么需要RPC 1 2 RPC介绍 二 RPC通信实现原理 2 2 RPC调用过程 三 RPC框架的安装和使用 PHP 3 1 php目前流行的RPC框架有哪些 3 2 Hprose框架的使用案例 项目开发比较
  • Could not build wheels for mmcv-full, which is required to install pyproject.toml-based projects

    Could not build wheels for mmcv full which is required to install pyproject toml based projects 先安装mim 注意事项 需要降低mmcv版本 p
  • mbedTLS常用结构体

    ECP密钥对mbedtls ecp keypair brief ECP key pair structure A generic key pair that could be used for ECDSA fixed ECDH etc no
  • windows2016安装.netFramework 3.5

    2016服务器默认安装的是4 6 2的 net但是有时候我们经常会需要用到3 5版本 但是2016又不能像以前的版本一样直接下载安装 这里介绍2个办法进行安装 1 使用服务器安装工具安装 打开服务器管理器 选择添加角色和功能 下一步 选择第
  • 分布式深度学习技术-AllReduce

    如果只想了解核心思想 只需要关注红色字体部分即可了解AllReduce和Ring AllReduce算法的核心思想 Hello I am Yuichiro Ueno I participated in a summer internship
  • qt 自定义信号与槽

    在qt中 信号与槽 发送方和接收方都是可以自定义的 这里作为实验 我们定义两个类 一个是老师 一个是学生 当下课的时候 老师作为发送方发送信号 该下课了 作为接收方的学生则提供槽函数 回复老师 该干饭了 qt提供的机制令c 程序在其中可以较
  • 100天精通Python(基础篇)——第3天:变量

    文章目录 一 变量是什么 有什么作用 二 变量的定义格式是 三 变量的特征是 四 print语句如何输出多分内容 五 示例代码 一 变量是什么 有什么作用 变量就是在程序运行时 记录数据用的 二 变量的定义格式是 变量名 变量值 x 10
  • 基于51单片机的大气压检测系统

    一 硬件方案 本设计主要通过气压传感器BMP180获得当前位置的温度和大气压值 并通过LCD1602显示 并根据测量值与设置好的阀值进行对比进而控制蜂鸣器与LED灯进行提示使用者 主要由51单片机 最小系统 LCD1602液晶显示模块 BM
  • 计算机原理--进程管理之进程同步

    进程管理之进程同步 为什么需要进程间的同步 进程间同步的原则 线程同步 为什么需要进程间的同步 生产者 消费者问题 哲学家进餐问题 根源问题是 彼此之间没有通信 如果生产者通知消费者我已经完成意见生产 哲学家向旁边哲学家说我要进餐了 对竞争
  • 当 K8s 集群达到万级规模,阿里巴巴如何解决系统各组件性能问题?

    作者 阿里云容器平台高级技术专家 曾凡松 逐灵 本文主要介绍阿里巴巴在大规模生产环境中落地 Kubernetes 的过程中 在集群规模上遇到的典型问题以及对应的解决方案 内容包含对 etcd kube apiserver kube cont