重磅解读:K8s Cluster Autoscaler 模块及对应华为云插件 Deep Dive
摘要:本文将解密 K8s Cluster Autoscaler 模块的架构和代码的 Deep Dive,及 K8s Cluster Autoscaler 华为云插件。
背景信息
基于业务团队(Cloud BU 应用平台)在开发 Serverless 引擎框架的过程中完成的K8s Cluster Autoscaler华为云插件。 目前该插件已经贡献给了 K8s 开源社区,见下图:
本文将会涉及到下述内容:
1. 对 K8s Cluster Autoscaler 模块的架构和代码的 Deep Dive,尤其是核心功能点的所涉及的算法的介绍。
2. K8s Cluster Autoscaler 华为云插件模块的介绍。
3. 作者本人参与 K8s 开源项目的一点心得。(如:何从开源社区获取信息和求助,在贡献开源过程中需要注意的点)
直入主题,这里不再赘述 K8s 的基本概念。
什么是 K8s Cluster Autoscaler (CA)?
什么是弹性伸缩?
顾名思义是根据用户的业务需求和策略,自动调整其弹性计算资源的管理服务,其优势有:
1. 从应用开发者的角度:能够让应用程序开发者专注实现业务功能,无需过多考虑系统层资源
2. 从系统运维者的角度:极大的降低运维负担, 如果系统设计合理可以实现“零运维”
3. 是实现 Serverless 架构的基石,也是 Serverless 的主要特性之一
在具体解释 CA 概念之前,咋们先从宏观上了解一下 K8s 所支持的几种弹性伸缩方式(CA 只是其中的一种)。
K8s 支持的几种弹性伸缩方式:
注: 为了描述精确性,介绍下面几个关键概念时,先引用 K8S 官方解释镇一下场 :)。"简而言之"部分为作者本人的解读。
VPA (Vertical Pod Autoscaler)
A set of components that automatically adjust the amount of CPU and memory requested by Pods running in the Kubernetes Cluster. Current state - beta.
简而言之: 对于某一个 POD,对其进行扩缩容(由于使用场景不多,不做过多介绍)
HPA(Horizontal Pod Autoscaler) - Pod 级别伸缩
A component that scales the number of pods in a replication controller, deployment, replica set or stateful set based on observed CPU utilization (or, with beta support, on some other, application-provided metrics).
简而言之: 对于某一 Node, 根据预先设置的伸缩策略(如 CPU, Memory 使用率某设定的阀值),增加/删减其中的 Pods。
HPA 伸缩策略:
HPA 依赖 metrics-server 组件收集 Pod 上 metrics, 然后根据预先设定的伸缩策略(如:CPU 使用率大于 50%),来决定扩缩容 Pods。计算 CPU/Memory 使用率时,是取所有 Pods 的平均值。关于具体如何计算的,点击此处有详细算法介绍。
注:metrics-server 默认只支持基于 cpu 和 memory 监控指标伸缩策略
图中下半部门 Prometheus 监控系统和 K8s Prometheus Adapter 组件的引入是为了能够使用自定义的 metrics 来设置伸缩策略,由于不是本文的重点,这里不做过多介绍, K8s 官方文档有个 Walkthrough 案例一步一步在实操中掌握和理解该模块。如果用户只需要依据 cpu/memory 的监控指标来设置伸缩策略,只要 deploy 默认的 metrics-server 组件(其安装对 K8s 来说就是一次 deployment,非常方便, 上面的链接里有安装步骤)
CA (Cluster Autoscaler)- Node 级别伸缩
A component that automatically adjusts the size of a Kubernetes Cluster so that: all pods have a place to run and there are no unneeded nodes.
简而言之: 对于 K8S 集群,增加/删除其中的 Nodes,达到集群扩缩容的目的。
Kubernetes(K8s) Cluster Autoscaler(CA)模块源码解析:
前面做了这么多铺垫,是时候切入本文主题了。下面我将主要从架构和代码两个维度来揭开 CA 模块的神秘面纱,并配合 FAQ 的形式解答常见的问题。
CA 整体架构及所含子模块
如上图所示, CA 模块包含以下几个子模块, 详见K8S CA模块在Github的源码:
autoscaler: 核心模块,包含核心 Scale Up 和 Scale Down 功能(对应 Github 里 core Package)。
1. 在扩容时候:其 ScaleUp 函数会调用 estimator 模块来评估所需节点数
2. 在缩容时:其 ScaleDown 函数会调用 simulator 模块来评估缩容的节点数
estimator: 负责计算扩容需要多少 Node (对应 Github 里 estimator Package)
simulator: 负责模拟调度,计算缩容节点 (对应 Github 里 simulator Package)
expander: 负责扩容时,选择合适的 Node 的算法 (对应 Github 里 expander Package),可以增加或定制化自己的算法
cloudprovider: CA 模块提供给具体云提供商的接口 (对应 Github 里 cloudprovider Package)。关于这个子模块后面也会着重介绍,也是我们华为云 cloudprovider 的扩展点。
1. autoscaler 通过该模块与具体云提供商对接(如上图右下角方框所示 AWS, GCE 等云提供商),并可以调度每个云提供商提供的 Node.
通过对 K8s CA 模块的架构和源码的织结构的介绍,我总结有以下几点最佳实践值得学习和借鉴, 可以适用在任何编程语言上:
1. SOLID 设计原则无处不在,具体反映在:
1. 每个子模块仅负责解决某一特定问题 - 单一职责
2. 每个子模块都预留有扩展点 - 开闭原则
3. 每个子模块的接口隔离做的很清晰 - 接口分离原则
…
清晰的子模块包的组织结构
关于 CA 模块的用户常见问题
1. VPA 更新已经存在的 Pod 使用的 resources
2. HPA 更新已经存在的 Pod 副本数
3. 如果没有足够的节点在可伸缩性事件后运行 POD,则 CA 会扩容新的 Node 到集群中,之前处于 Pending 状态的 Pods 将会被调度到被新管理的 node 上
2. CA 何时调整 K8S 集群大小?
何时扩容: 当资源不足,Pod 调度失败,即存在一直处于 Pending 状态的 Pod(见下页流程图), 从 Cloud Provider 处添加 NODE 到集群中
何时缩容: Node 的资源利用率较低,且 Node 上存在 Pod 都能被重新调度到其它 Node 上去
3. CA 多久检查一次 Pods 的状态?
CA 每隔 10s 检查是否有处于 pending 状态的 Pods
4. 如何控制某些 Node 不被 CA 在缩容时删除?
0. Node 上有 Pod 被 PodDisruptionBudget 控制器限制。PodDisruptionBudgetSpec
1. Node 上有命名空间是 kube-system 的 Pods。
2. Node 上 Pod 被 Evict 之后无处安放,即没有其他合适的 Node 能调度这个 pod
3. Node 有 annotation: “http://cluster-autoscaler.kubernetes.io/scale-down-disabled”: “true”
4. Node 上存有如下 annotation 的 Pod:“http://cluster-autoscaler.kubernetes.io/safe-to-evict”: “false”.点击见详情
若想更进一步了解和学习,请点击这里查看更完整的常见问题列表及解答。
CA 模块源码解析
由于篇幅关系,只对核心子模块深入介绍,通过结合核心子模块与其他子模块之间如何协调和合作的方式顺带介绍一下其他的子模块。
CA 模块整体入口处
程序启动入口处:kubernetes/autoscaler/cluster-autoscaler/main.go
CA 的 autoscaler 子模块
如上图所示,autoscaler.go 是接口,其默认的实现是 static_autoscaler.go, 该实现会分别调用 scale_down.go 和 scale_up.go 里的 ScaleDown 以及 ScaleUp 函数来完成扩缩容。
那么问题来了,合适 ScaleUp 和 ScaleDown 方法会被调用呢,咋们按照顺序一步一步来捋一下, 回到 CA 整体入口,那里有一个 RunOnce(在 autoscaler 接口的默认实现 static_autoscaler.go 里)方法,会启动一个 Loop 一直运行 listen 和 watch 系统里面是否有那些处于 pending 状态的 Pods(i.e. 需要协助找到 Node 的 Pods), 如下面代码片段(static_autoscaler.go 里的 RunOnce 函数)所示, 值得注意的是,在实际调用 ScaleUp 之前会有几个 if/else 判断是否符合特定的条件:
对于 ScaleDown 函数的调用,同理,也在 RunOnce 函数里, ScaleDown 主要逻辑是遵循如下几步:
1. 找出潜在的利用率低的 Nodes (即代码里的 scaleDownCandidates 数组变量)
2. 然后为 Nodes 里的 Pods 找到“下家”(即可以被安放的 Nodes,对应代码里的 podDestinations 数组变量)
3. 然后就是下面截图所示,几个 if/else 判断符合 ScaleDown 条件,就执行 TryToScaleDown 函数
通过上面的介绍结合代码片段,我们了解到何时 ScaleUp/ScaleDown 函数会被调用。接下来,我们来看看当这两个核心函数被调用时,里面具体都发生了什么。
先来看一下 ScaleUp:
从上图代码片段,以及我里面标注的注释,可以看到,这里发生了下面几件事:
1. 通过 cloudprovider 子模块(下面专门介绍这个子模块)从具体云提供商处获取可以进行扩容的的 NodeGroups
2. 把那些 Unschedulable Pods 按照扩容需求进行分组(对应上面代码里的对 buildPodEquivalenceGroups 函数的调用)
3. 把第 1 步得到的所有可用的 NodeGroups 和第 2 步得到的待分配的 Pods, 作为输入,送入给 estimator 子模块的装箱算法(该调用发生对上图中 computeExpansionOption 函数调用内部) ,得到一些候选的 Pods 调度/分配方案。由于 estimator 子模块的核心就是装箱算法,下图就是实现了装箱算法的 Estimate 函数,这里实现有个小技巧,就是算法开始之前,先调用 calculatePodScore 把两维问题降为一维问题(即 Pod 对 CPU 和 Memory 的需求),然后就是传统的装箱算法,两个 for loop 来给 Pods 找到合适的 Node. 至于具体如何降维的,详见binpacking.estimator.go里的calculatePodScore函数源码。
4. 把第 3 步得到的一些方案,送入给 expander 子模块,得到最优的分配方案(对应代码片段中 ExpanderStrategy.BestOption 的函数调用)expander 提供了下面截图中的集中策略,用户可以通过实现 expander 接口的 BestOption 函数,来实现自己的 expander 策略
CA 的 cloudprovider 子模块
与具体的云提供商(i.e. AWS, GCP, Azure, Huawei Cloud)对接来对对应云平台上的 Node Group(有的云平台叫 Node Pool)里的 Node 进行增删操作已达到扩缩容的目的。其代码对应于与之同名的 cloudprovider package。详见 Github 代码。 没个云提供商,都需要按照 k8s 约定的方式进行扩展,开发自家的 cloudprovider 插件,如下图:
下文会专门介绍华为云如何扩展该模块的。
华为云 cloudprovider 插件开发及开源贡献心得
华为云 cloudprovider 插件如何扩展和开发的?
下图是华为 cloudprovider 插件的大致的代码结构, 绿色框里是 SDK 实际是对 CCE(云容器引擎 CCE) 进行必要操作所需要的 (对 Node Pool/Group 里的 Node 进行增加和删除)。 按理说我们不需要自己写这一部分,不过由于咋们云 CCE 团队的 SDK 实在是不完善,所以我们开发了一些必要的对 CCE 进行操作的 SDK。重点是红色框中的代码:
huaweicloud_cloud_provider.go 是入口处,其负责总 huaweicloud_cloud_config.go 读取配置,并实例化 huaweicloud_manager.go 对象。huaweicloud_manager.go 对象里通过调用蓝色框部门里的 CCE SDK 来获取 CCE 整体的信息。 CCE 整体的信息被获取到后,可以调用 huaweicloud_node_group.go 来完成对该 CCE 绑定的 Node Group/Pool 进行 Node 的扩缩容已达到对整体 CCE 的 Node 伸缩。
如何从开源社区获取所需资源及开源过程中需要注意的点?
我刚开始接受该项目的时候,一头雾水,不知道该如何下手。K8s 关于这一块的文档写的又不是很清楚。以往的经验以及 K8s Github README 中提供的信息,我加入他们的 Slack 组织,找到相应的兴趣组 channel( 对应我的情况就是 sig-autoscaling channel),提出了我的问题(如下面截图)。 基于 K8s 代码仓的大小,如果没找到合适的扩展点,几乎无法改动和扩展的。
划重点: 现在几乎所有的开源组中都有 Slack 群组,加入找到相应的兴趣组,里面大牛很多,提出问题,一般会有人热心解答的。 邮件列表也可以,不过我认为 Slack 高效实时一点,强烈推荐。对于我本人平常接触到的开源项目,我一般都会加入到其 Slack 中,有问题随时提问。 当然,中国贡献的开源项目,好多以微信群的方式沟通 :)譬如咋们华为开源出去的微服务框架项目 ServiceComb,我也有加微信群。总之, 对于开源项目,一定要找到高效的和组织沟通的方式。
另外,对于贡献代码过程中,如果使用到了三方开源代码,由于版权和二次分发的问题,尽量避免直接包含三方源代码, 如果实在需要,可以对其进行扩展,并在新扩展的文件附上华为的版权信息与免责声明。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/d26b441cebb5b229a6efa35f4】。文章转载请联系作者。
评论