关于 Pod 驱逐的二三事

2024/09/04 19:47 pm posted in  Kubernetes

应知应会

什么是驱逐

很长一段时间,我都一遍又一遍的向用户解释,他的 Pod 被驱逐,是 K8s 的正常行为,而非某一个平台出现的 Bug。

驱逐(Eviction) 是 K8s 一个特色功能,是在某些场景下,为了寻求全局最优解的手段。当节点 NotReady、节点资源不足,把 pod 驱逐至其它节点,而不是谋求将节点压榨致宕,更能保护业务的连续性和可用性。

驱逐原因定位

一般来说,从发起模块的角度,pod eviction 可以分为两类:

  • kube-controller-manager 周期性检查所有节点状态,当节点处于 NotReady 状态超过一段时间后,驱逐该节点上所有 pod。
  • kubelet 周期性检查本节点资源,当资源不足时,按照优先级驱逐部分 pod。

如果不考虑排水引入的驱逐的话,如果发生了 Pod 驱逐,可以从以下角度进行定位:

  • Pod Status:发生驱逐后,Pod 会将驱逐原因记录在 .status.message 中;
  • 节点异常:是否出现了 NotReady 或者异常的 Condition(Ready、NetworkReady 等);
  • 驱逐污点:节点是否被打上了驱逐污点;
  • 资源压力:节点是否存在某种资源压力(DiskPressure、MemoryPressure 等);
  • 抢占或重调度:是否存在 Pod 优先级或者触发过重调度;
  • PodDisruptionConditions:1.26+ 的集群可以在 Pod 干扰状态中获取一部分信息。

Pod 驱逐进阶

如何干预驱逐行为

如果在不能自定义集群配置,下面的因素可以调整 Pod 的驱逐行为:

  • 设置资源请求和限制:有助让 Kubernetes 感知负载需求,更好的指定驱逐优先级;
  • 使用优先级:为关键的 Pod 分配优先级类,确保它们优先获得资源;
  • Pod 和节点资源监控:在 Pod 被驱逐前,提供发现资源压力。

如果可以自定义集群的配置,则可以从 kcm 和 kubelet 两个维度干预驱逐行为。

干预 kube-controller-manager 触发的驱逐:

kube-controller-manager 会周期性检查节点状态。当节点状态为 NotReady 并且超过 podEvictionTimeout 时间后,kube-controller-manager 会将该节点上的 Pod 全部驱逐到其它节点。具体驱逐速度还会受到驱逐速度参数和集群大小等因素的影响。以下是控制驱逐行为的启动参数:

参数名 描述 默认值
pod-eviction-timeout 【1.26废弃】节点宕机后,经过此时间间隔后开始驱逐机制,驱赶宕机节点上的 Pod。 5 分钟
node-eviction-rate 节点驱逐速率,由令牌桶流控算法实现。注意,这里指的是驱逐节点的速率,而不是驱逐 Pod 的速率。 0.1
secondary-node-eviction-rate 二级驱逐速率,当集群中宕机节点过多时,驱逐速率会降低。 0.01
unhealthy-zone-threshold 不健康区域的阈值,影响何时开启二级驱逐速率。当某个区域中节点宕机数目超过 55% 时,该区域被认为是不健康的。 0.55
large-cluster-size-threshold 大集群阈值,当某个区域的节点数超过该阈值时,该区域被认为是一个大集群。当大集群中节点宕机数目超过 55% 时,驱逐速率降为 0.01;如果是小集群,则驱逐速率直接降为 0(关闭驱逐)。 50

1.29 之后 kcm 可以单独控制 taint-eviction-controller,可以通过设置 --controllers=-taint-eviction-controller ,使得关闭因资源压力污点导致的驱逐(https://kubernetes.io/zh-cn/blog/2023/12/19/kubernetes-1-29-taint-eviction-controller/)。

Kubelet 的配置存在软驱逐阈值和硬驱逐阈值两种。

软驱逐阈值由若干对配置参数组成,包括驱逐阈值和必须指定的宽限期。在宽限期内,kubelet 不会采取任何动作来回收或驱逐与该阈值相关的资源。

硬驱逐阈值则没有宽限期。一旦触发,kubelet 将立即采取行动回收相关的资源。如果满足硬驱逐阈值条件,kubelet 会立即终止 Pod,而不是进行优雅退出。

参数名 描述 默认值
eviction-soft 描述驱逐阈值的集合(例如 memory.available<1.5Gi),如果在宽限期之外满足条件将触发 Pod 驱逐。 -
eviction-soft-grace-period 描述驱逐宽限期的集合(例如 memory.available=1m30s),对应于在驱逐 Pod 前软驱逐阈值应该被控制的时长。 -
eviction-max-pod-grace-period 描述当满足软驱逐阈值并终止 Pod 时允许的最大宽限期值(秒数)。 -
eviction-hard 描述驱逐阈值的集合(例如 memory.available<1Gi),如果满足条件将触发 Pod 驱逐。 imagefs.available<15% memory.available<100Mi nodefs.available<10%
eviction-minimum-reclaim 当某资源压力过大时,kubelet 将执行 Pod 驱逐操作。 此参数设置软性驱逐操作需要回收的资源的最小数量。 -
eviction-pressure-transition-period kubelet 在驱逐压力状况解除之前的最长等待时间。

如果有人对外宣称在未改 K8s 代码的情况下,实现了「关闭驱逐」,实则只是覆盖了 eviction-hard 配置,至于为什么不建议关闭驱逐,可参考后文。

有状态应用的特例

无状态应用因为无法区分是宕机还是网络分区,以及无法正常优雅退出 Pod,所以不会创建新的 Pod,需要手动干预(https://github.com/kubernetes/kubernetes/issues/54368)。

我可以关闭驱逐吗

无论是服务提供商还是用户,国内普遍喜欢大家长式的服务,无限兜底的背后其实就是谁背锅的问题,而 Pod 的被驱逐,就是一个完美的背锅侠。

我经受过很多要求关闭驱逐的工单,也确实通过 Hack 关掉客户想要的某种驱逐,但正常情况下,驱逐是一个手段而不是功能,Kubernetes 并没有提供任何一种叫关闭驱逐的开关,以及相应的能力,但可以用过上述配置影响或者关闭特定场景的执行。

1.29 之后,SeparateTaintEvictionController 的特性,使得可以关闭默认的污点驱逐行为,但也失去了 Node 异常后 Pod 的驱逐能力。但另一个角度,也可以自行维护基于 Node 和业务属性的 taint-eviction-controller,用于实现更贴近业务场景的驱逐判定。

Kubernetes 作为一个分布式编排工具,其核心工作就是确保工作负载符合预期,当 Node 出现问题,驱逐就是为了换个健康的 Node 将工作负载重新拉起来。

但是现实情况是由于运维/PaaS团队话语权弱,需要无条件服从开发团队的「业务 Pod 誓与 Node 共存亡」的要求。如果你也有我的相同困扰,我建议你把这篇文章发到开发群里。

Pod 被驱逐肯定是由于某种异常原因,比如是不是业务 Pod 写了大量的日志文件,或者内存泄露超限,从根本上解决问题是更好的选择,通过 Hack 手动干预驱逐,只不过是今天💣和明天💥的区别。