近期常常被问到 CA 以及一些相关于 PDB 的事情,所以这边蛮写了下 CA 和 PDB 以及一些常见的问题
PDB
因为上下文的关系,所以优先解释下 PDB 是干啥的。全写是叫 Pod Disruption Budget(Pod 中断预算),白话解释就是需要干掉(走 驱逐/Evict 方式)某个标签选择/Selector(不了解的话自行找下 k8s 的标签的说明)的一波(可能没有,可以一个,可以一堆) Pod的时候最大可以干掉几个;正常点的解释就是说,这个选定的一组 Pod 内可以容忍几个实例在同一时间内不可用。当前 PDB 的 Selector 更多会是去选择一个服务(不是 k8s 的 Service)的标签(比如线上的 Deployment + 灰度 Deployment)。
主要是用来 cover 一些预期执行的驱逐,例如说 drain node。但就像是机器炸了,那真的就没救,可能就超预算了嘿嘿(PDB 无能为力啊,总不能说,PDB 拦住那个节点说「你不能 kernel panic」吧)
Pod 可设置最大不可用,也可以设置最小可用。两种场景看怎么用吧,不多说这个。
假设,当前选中的有总有 5 个 Pod,容许 1 个不可用,那么这时候如果已经驱逐掉一个但它还没正常提供服务,这时候要再干掉一个,那么 evict 的请求会被 block 住直到刚才那个 pod 起来或者超时。
Cluster Autoscaler
这个玩意是在 k8s 的 autoscaler
组件中,主要负责集群的扩缩容(广泛用于各种公有云,当然私有云也可以自行实现弹性能力),简称为 CA。以下简述几个概念:
- 节点池/节点组(NodeGroup):属于同一节点配置模板所创建的多个节点组
- 不可调度 Pod:这边指的不可调度 Pod 特别指的是因资源不足而不可调度且有可以容身的 NodeGroup(比如 nodeSelector)
- 驱逐:驱逐的场景是先从当前节点剔除某个 Pod 而后由所属 Controller 重新创建新的副本
- Cloud Provider:最终节点扩缩容实际操作调用的实现,这部分的实现多为与云厂 API 的调用。例如 GKE Cloud Provider、Azure Cloud Provider 等等
扩容
当出现一个或者若干个不可调度 Pod 时,目前默认算法下 CA 会常去去寻找能够用且成本较低(浪费较少)的节点池。这时候可能会找到多个节点池都可以用,那么就是随机选一个出来扩容。然后这些不可调度 Pod 就会有地方去调度。通过这样的方式来解决固定节点数量的集群在集群不足时需要人工介入扩容的窘境。
流程大体就是:
- 发现不可调度 Pod
- 计算当前需要扩容的资源量
- 从 Cloud Provider 那边找到能够用得上的节点池,如果找不到就放弃扩容
- 最终选择一个节点池告诉 Cloud Provider:「我」要扩 XX 个这个节点池的实例
- Cloud Provider 去调用相关的 API 执行节点创建、初始化等等
- 在节点初始化完成后,加到集群内,这些不可调度 Pod 就可以调度上去正常提供服务了
扩容的本质是某一节点池的节点数量增减。
缩容
缩容内容就多了,也是当前槽点比较多,比较难去做出一个能够符合各家需求的一个地方吧。目前缩容是无视 DaemonSet(这块理论上是需要考虑进去的,提了 MR 给社区不过没太多进展) 和 MirrorPod 的。
节点标注为 Unneeded 只有一个标准:节点上除了 DaemonSet 和 Mirror Pod 之外的所有 Pod CPU 和内存的 Request 占节点可分配(Allocatable resource)均小于设定的缩容阈值(这是一个百分比值)
但,unneeded 并不是最终缩容的依据,期间还得判断一个东西叫 PDB。
判定 unneeded 的节点会再判断相关的 Pod 和他们的 PDB。
- 判定是否这些 Pod 有其他地方可以调度
- 判定他们的 PDB(如果有的话)是否容忍 evict 1 个 Pod(没有上下文保存,也就是说,如果一个节点上有 2 个 Pod 受同一 PDB 管控的也会被判定通过)
- 如果有 kube-system,那么判定 PDB(有 option 可以跳过这块的判定)
- 相关 Pod 没有 local storage
- 相关 Pod 没有 Not SafeToEvict 的 annotation
如果以上几个条件任意一个不满足,则不会缩容这个节点,也不会驱逐这个节点上的 Pod。
在驱逐的过程中,CA 给每个 Pod 10 分钟的 GracefulPeriodSeconds,如果超过就直接强制删除。
FAQ
单实例 Pod 被驱逐导致服务中断
理论上单实例 Pod 本身是认为不提供服务保障的,因为从容器的设计就认为 Pod 本身是具备一定的容错、被驱逐能力。在极端情况下确实需要引入一些额外维护成本的方案包保障单实例 Pod 的服务。
如果服务不支持多实例部署那就只能够通过以下一些方案解决
- 将这类 Pod 调度到一个较为稳定、独立的节点池(例如说少于 2 个实例的服务调度到固定节点池或者少实例专用节点池):新增节点池还要考虑资源冗余
- 直接调度到固定节点(非弹性)上:但比较容易出现因资源不足调度失败的情况
这两种带来的成分主要是节点池的人力维护成本,以及在 Spec 中处理的 nodeSelector 的成本。
因驱逐带来服务 SLA 抖动
在评估 Pod 资源的时候适当预留一些突发负载的量,并且如果在实例过少的时候适当增加实例(如果担心成本增加,则适当减小实例规格)
如果给单实例上 PDB 且设置要求至少 1 个可用
这种情况会导致 CA 直接不去缩容这个节点,最终可能会导致节点长时间无法回收(可能产生一些成本,但也可能很快有新 Pod 调度上来)。
那么这个情况下带来的成本可能会比较可观,根据 k8s 平均调度(默认情况)的思路,那么就可能好几个节点都较空,但因为 PDB 的关系不能进行重调度和缩容。
减少集群发生过多的服务驱逐
这个问题本质是因为短时间内节点变动大(可能这十分钟加五节点,下十分钟下五个节点,然后又上五个;也可能是比较抖的比如持续在下节点)。每个服务都不期望自己是被驱逐的那个,但为了成本没办法啊。
那就「高峰关缩容,低峰开缩容」。在低峰期缩容减少对业务的影响,业务方的容忍度也比较大。
这个操作对于高峰期 delta(node) 较小的集群反而会比较友好,但如果 delta 较大那会带来因扩容后无法缩容而带来比较大的成本浪费。