节点压力驱逐

节点压力驱逐是指 kubelet 主动终止 Pod 以回收节点上的资源的过程。

kubelet 会监控集群节点上的内存、磁盘空间和文件系统 inode 等资源。当一个或多个资源达到特定的消耗水平时,kubelet 可以主动使节点上的一个或多个 Pod 失败,以回收资源并防止资源耗尽。

在节点压力驱逐期间,kubelet 会将所选 Pod 的 阶段 设置为 Failed,并终止 Pod。

节点压力驱逐与 API 发起的驱逐 不同。

kubelet 不遵守您配置的 PodDisruptionBudget 或 Pod 的 terminationGracePeriodSeconds。如果您使用 软驱逐阈值,则 kubelet 会遵守您配置的 eviction-max-pod-grace-period。如果您使用 硬驱逐阈值,则 kubelet 会使用 0s 的宽限期(立即关闭)进行终止。

自愈行为

kubelet 会尝试在终止最终用户 Pod 之前 回收节点级资源。例如,当磁盘资源不足时,它会删除未使用的容器镜像。

如果 Pod 由 工作负载 管理对象(例如 StatefulSetDeployment)管理,该对象会替换失败的 Pod,则控制平面 (kube-controller-manager) 会在被驱逐的 Pod 位置创建新的 Pod。

静态 Pod 的自愈

如果您在资源压力大的节点上运行 静态 Pod,则 kubelet 可能会驱逐该静态 Pod。然后,kubelet 会尝试创建替换 Pod,因为静态 Pod 始终表示要在该节点上运行 Pod 的意图。

kubelet 在创建替换 Pod 时会考虑静态 Pod 的_优先级_。如果静态 Pod 清单指定了低优先级,并且集群控制平面中定义了更高优先级的 Pod,并且节点资源压力很大,则 kubelet 可能无法为该静态 Pod腾出空间。即使节点资源压力很大,kubelet 也会继续尝试运行所有静态 Pod。

驱逐信号和阈值

kubelet 使用各种参数来做出驱逐决策,例如以下参数

  • 驱逐信号
  • 驱逐阈值
  • 监控间隔

驱逐信号

驱逐信号是特定资源在特定时间点的当前状态。Kubelet 通过将信号与驱逐阈值进行比较来使用驱逐信号做出驱逐决策,驱逐阈值是节点上应可用的最小资源量。

在 Linux 上,kubelet 使用以下驱逐信号

驱逐信号描述
memory.availablememory.available := node.status.capacity[memory] - node.stats.memory.workingSet
nodefs.availablenodefs.available := node.stats.fs.available
nodefs.inodesFreenodefs.inodesFree := node.stats.fs.inodesFree
imagefs.availableimagefs.available := node.stats.runtime.imagefs.available
imagefs.inodesFreeimagefs.inodesFree := node.stats.runtime.imagefs.inodesFree
pid.availablepid.available := node.stats.rlimit.maxpid - node.stats.rlimit.curproc

在此表中,“**描述**”列显示了 kubelet 如何获取信号的值。每个信号都支持百分比或字面量值。Kubelet 计算相对于与信号关联的总容量的百分比值。

memory.available 的值是从 cgroupfs 派生的,而不是像 free -m 这样的工具。这一点很重要,因为 free -m 在容器中不起作用,并且如果用户使用 节点可分配 功能,则会在 cgroup 层次结构的最终用户 Pod 部分以及根节点本地做出资源不足的决策。此 脚本cgroupv2 脚本 重复了 kubelet 为计算 memory.available 而执行的相同步骤集。kubelet 在计算中排除了 inactive_file(非活动 LRU 列表中文件支持内存的字节数),因为它假定在压力下可以回收内存。

kubelet 识别两个特定的文件系统标识符

  1. nodefs:节点的主文件系统,用于本地磁盘卷、未由内存支持的 emptyDir 卷、日志存储等。例如,nodefs 包含 /var/lib/kubelet/
  2. imagefs:容器运行时用于存储容器镜像和容器可写层的可选文件系统。

Kubelet 会自动发现这些文件系统,并忽略其他节点本地文件系统。Kubelet 不支持其他配置。

一些 kubelet 垃圾收集功能已被弃用,转而支持驱逐

现有标志理由
--maximum-dead-containers一旦旧日志存储在容器上下文之外,就会弃用
--maximum-dead-containers-per-container一旦旧日志存储在容器上下文之外,就会弃用
--minimum-container-ttl-duration一旦旧日志存储在容器上下文之外,就会弃用

驱逐阈值

您可以为 kubelet 指定自定义驱逐阈值,以便在做出驱逐决策时使用。您可以配置 驱逐阈值。

驱逐阈值的格式为 [eviction-signal][operator][quantity],其中

  • eviction-signal 是要使用的 驱逐信号
  • operator 是您想要的 关系运算符,例如 <(小于)。
  • quantity 是驱逐阈值量,例如 1Giquantity 的值必须与 Kubernetes 使用的数量表示法相匹配。您可以使用字面量值或百分比 (%)。

例如,如果一个节点有 10GiB 的总内存,并且您希望在可用内存低于 1GiB 时触发驱逐,则可以将驱逐阈值定义为 memory.available<10%memory.available<1Gi(不能同时使用两者)。

软驱逐阈值

软驱逐阈值将驱逐阈值与管理员指定的必需宽限期配对。在超过宽限期之前,kubelet 不会驱逐 Pod。如果您没有指定宽限期,则 kubelet 会在启动时返回错误。

您可以同时指定软驱逐阈值宽限期和 kubelet 在驱逐期间使用的最大允许 Pod 终止宽限期。如果您指定了最大允许宽限期,并且满足了软驱逐阈值,则 kubelet 会使用两者中较小的宽限期。如果您没有指定最大允许宽限期,则 kubelet 会立即终止被驱逐的 Pod,而不会进行优雅终止。

您可以使用以下标志来配置软驱逐阈值

  • eviction-soft:一组驱逐阈值,例如 memory.available<1.5Gi,如果超过指定的宽限期,则可以触发 Pod 驱逐。
  • eviction-soft-grace-period:一组驱逐宽限期,例如 memory.available=1m30s,它们定义了在触发 Pod 驱逐之前软驱逐阈值必须保持多长时间。
  • eviction-max-pod-grace-period:在响应满足软驱逐阈值而终止 Pod 时使用的最大允许宽限期(以秒为单位)。

硬驱逐阈值

硬驱逐阈值没有宽限期。当满足硬驱逐阈值时,kubelet 会立即终止 Pod,而不会进行优雅终止,以回收不足的资源。

您可以使用 eviction-hard 标志来配置一组硬驱逐阈值,例如 memory.available<1Gi

kubelet 具有以下默认硬驱逐阈值

  • memory.available<100Mi
  • nodefs.available<10%
  • imagefs.available<15%
  • nodefs.inodesFree<5%(Linux 节点)
  • imagefs.inodesFree<5%(Linux 节点)

仅当所有参数均未更改时,才会设置这些硬驱逐阈值的默认值。如果更改任何参数的值,则其他参数的值将不会继承为默认值,并将设置为零。为了提供自定义值,您应该分别提供所有阈值。

驱逐监控间隔

kubelet 根据其配置的 housekeeping-interval 评估驱逐阈值,该值默认为 10s

节点状况

kubelet 报告 节点状况 以反映节点处于压力之下,因为满足了硬或软驱逐阈值,而与配置的宽限期无关。

kubelet 按如下方式将驱逐信号映射到节点状况

节点状况驱逐信号描述
MemoryPressurememory.available节点上的可用内存已满足驱逐阈值
DiskPressurenodefs.availablenodefs.inodesFreeimagefs.availableimagefs.inodesFree节点的根文件系统或镜像文件系统上的可用磁盘空间和 inode 已满足驱逐阈值
PIDPressurepid.available(Linux)节点上的可用进程标识符已低于驱逐阈值

控制平面还会将这些节点状况 映射 到污点。

kubelet 根据配置的 --node-status-update-frequency 更新节点状况,该值默认为 10s

节点状况振荡

在某些情况下,节点会在软驱逐阈值上下振荡,而不会保持定义的宽限期。这会导致报告的节点状况在 truefalse 之间不断切换,从而导致错误的驱逐决策。

为了防止振荡,您可以使用 eviction-pressure-transition-period 标志,该标志控制 kubelet 在将节点状况转换到不同状态之前必须等待多长时间。转换期的默认值为 5m

回收节点级资源

kubelet 会尝试在驱逐最终用户 Pod 之前回收节点级资源。

当报告 DiskPressure 节点状况时,kubelet 会根据节点上的文件系统回收节点级资源。

使用 imagefs

如果节点具有专用的 imagefs 文件系统供容器运行时使用,则 kubelet 会执行以下操作

  • 如果 nodefs 文件系统达到驱逐阈值,则 kubelet 会垃圾回收已死的 Pod 和容器。
  • 如果 imagefs 文件系统达到驱逐阈值,则 kubelet 会删除所有未使用的镜像。

不使用 imagefs

如果节点只有一个达到驱逐阈值的 nodefs 文件系统,则 kubelet 会按以下顺序释放磁盘空间

  1. 垃圾回收已死的 Pod 和容器
  2. 删除未使用的镜像

kubelet 驱逐的 Pod 选择

如果 kubelet 尝试回收节点级资源但未能将驱逐信号降至阈值以下,则 kubelet 会开始驱逐最终用户 Pod。

kubelet 使用以下参数来确定 Pod 驱逐顺序

  1. Pod 的资源使用量是否超过请求量
  2. Pod 优先级
  3. Pod 的资源使用量相对于请求量的比例

因此,kubelet 按以下顺序对 Pod 进行排名和驱逐

  1. 使用量超过请求量的 BestEffortBurstable Pod。这些 Pod 将根据其优先级进行驱逐,然后根据其使用量超过请求量的程度进行驱逐。
  2. Guaranteed Pod 和使用量小于请求量的 Burstable Pod 最后被驱逐,驱逐顺序基于其优先级。

仅当为所有容器指定了请求和限制并且它们相等时,才会保证 Guaranteed Pod。这些 Pod 永远不会因为其他 Pod 的资源消耗而被驱逐。如果系统守护程序(例如 kubeletjournald)消耗的资源超过了通过 system-reservedkube-reserved 分配预留的资源,并且节点上只剩下使用资源少于请求的 GuaranteedBurstable Pod,则 kubelet 必须选择驱逐其中一个 Pod,以保持节点稳定性并限制资源匮乏对其他 Pod 的影响。在这种情况下,它将选择首先驱逐优先级最低的 Pod。

如果您正在运行 静态 Pod 并希望避免它在资源压力下被驱逐,请直接为该 Pod 设置 priority 字段。静态 Pod 不支持 priorityClassName 字段。

当 kubelet 因 inode 或进程 ID 不足而驱逐 Pod 时,它会使用 Pod 的相对优先级来确定驱逐顺序,因为 inode 和 PID 没有请求。

kubelet 根据节点是否具有专用的 imagefs 文件系统来对 Pod 进行不同的排序

使用 imagefs

如果 nodefs 触发了驱逐,则 kubelet 会根据 nodefs 使用量(本地卷 + 所有容器的日志)对 Pod 进行排序。

如果 imagefs 触发了驱逐,则 kubelet 会根据所有容器的可写层使用量对 Pod 进行排序。

不使用 imagefs

如果 nodefs 触发了驱逐,则 kubelet 会根据其总磁盘使用量(本地卷 + 所有容器的日志和可写层)对 Pod 进行排序

最小驱逐回收量

在某些情况下,驱逐 Pod 只会回收少量不足的资源。这可能导致 kubelet 反复达到配置的驱逐阈值并触发多次驱逐。

您可以使用 --eviction-minimum-reclaim 标志或 kubelet 配置文件 为每种资源配置最小回收量。当 kubelet 注意到资源不足时,它会继续回收该资源,直到回收您指定的数量。

例如,以下配置设置了最小回收量

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
  memory.available: "500Mi"
  nodefs.available: "1Gi"
  imagefs.available: "100Gi"
evictionMinimumReclaim:
  memory.available: "0Mi"
  nodefs.available: "500Mi"
  imagefs.available: "2Gi"

在此示例中,如果 nodefs.available 信号达到驱逐阈值,则 kubelet 会回收资源,直到该信号达到 1GiB 的阈值,然后继续回收最小量 500MiB,直到可用的 nodefs 存储值达到 1.5GiB。

类似地,kubelet 会尝试回收 imagefs 资源,直到 imagefs.available 值达到 102Gi,表示 102 GiB 的可用容器镜像存储空间。如果 kubelet 可以回收的存储空间量小于 2GiB,则 kubelet 不会回收任何内容。

所有资源的默认 eviction-minimum-reclaim 值为 0

节点内存不足行为

如果节点在 kubelet 能够回收内存之前遇到_内存不足_ (OOM) 事件,则节点依赖于 oom_killer 来响应。

kubelet 会根据 Pod 的 QoS 为每个容器设置一个 oom_score_adj 值。

服务质量oom_score_adj
Guaranteed-997
-9981000
BestEffort1000

min(max(2, 1000 - (1000 × memoryRequestBytes) / machineMemoryCapacityBytes), 999)

对于具有 system-node-critical 优先级 的 Pod 中的任何容器,kubelet 还会设置 oom_score_adj 值为 -997

如果 kubelet 在节点遇到 OOM 之前无法回收内存,则 oom_killer 会根据其在节点上使用的内存百分比计算 oom_score,然后添加 oom_score_adj 以获得每个容器的有效 oom_score。然后,它会杀死得分最高的容器。

这意味着,在 QoS 较低的 Pod 中,相对于其调度请求消耗大量内存的容器将首先被杀死。

与 Pod 驱逐不同,如果容器因 OOM 被杀死,kubelet 可以根据其 restartPolicy 重新启动它。

良好实践

以下部分介绍了驱逐配置的良好实践。

可调度资源和驱逐策略

  • 使用驱逐策略配置 kubelet 时,应确保调度程序不会调度那些会立即导致内存压力并触发驱逐的 Pod。
  • 考虑以下场景
  • 节点内存容量:10GiB

操作员希望为系统守护程序(内核、kubelet 等)保留 10% 的内存容量。

--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi

操作员希望在内存利用率达到 95% 时驱逐 Pod,以减少系统 OOM 的发生率。

为此,kubelet 的启动方式如下

在此配置中,--system-reserved 标志为系统保留了 1.5GiB 的内存,即 总内存的 10% + 驱逐阈值量

如果 Pod 使用的内存超过其请求量,或者系统使用的内存超过 1GiB,则节点可能会达到驱逐阈值,这会导致 memory.available 信号降至 500MiB 以下并触发阈值。

DaemonSet 和节点压力驱逐

Pod 优先级是做出驱逐决策的主要因素。如果您不希望 kubelet 驱逐属于 DaemonSet 的 Pod,请通过在 Pod 规范中指定合适的 priorityClassName 为这些 Pod 指定足够高的优先级。您也可以使用较低的优先级或默认优先级,以便仅在资源充足时才允许来自该 DaemonSet 的 Pod 运行。

已知问题

以下部分介绍了与资源不足处理相关的已知问题。

kubelet 可能无法立即观察到内存压力

默认情况下,kubelet 会定期轮询 cAdvisor 以收集内存使用情况统计信息。如果内存使用量在该窗口内快速增加,则 kubelet 可能无法足够快地观察到 MemoryPressure,并且 OOM 杀手仍将被调用。

您可以使用 --kernel-memcg-notification 标志在 kubelet 上启用 memcg 通知 API,以便在超过阈值时立即收到通知。

如果您不是要实现极高的利用率,而是要实现合理的超额承诺,则解决此问题的可行方法是使用 --kube-reserved--system-reserved 标志为系统分配内存。

active_file 内存不被视为可用内存

在 Linux 上,内核将活动最近最少使用 (LRU) 列表上的文件支持内存的字节数跟踪为 active_file 统计信息。kubelet 将 active_file 内存区域视为不可回收。对于大量使用块支持的本地存储(包括临时本地存储)的工作负载,文件和块数据的内核级缓存意味着许多最近访问的缓存页很可能被计为 active_file。如果活动 LRU 列表上有足够多的此类内核块缓冲区,则 kubelet 很可能会将其视为高资源使用率,并将节点标记为 experiencing memory pressure - triggering pod eviction.

有关更多详细信息,请参阅 https://github.com/kubernetes/kubernetes/issues/43916

上次修改时间:2024 年 2 月 19 日 下午 1:21 PST:修复调度部分中的拼写错误 (e839bf7aee)