节点压力驱逐

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

功能状态: Kubernetes v1.31 [beta] (默认启用: true)

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

在节点压力驱逐期间,kubelet 将选定的 Pod 的 phase 设置为 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 的意图。

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

驱逐信号和阈值

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

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

驱逐信号

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

kubelet 使用以下驱逐信号

驱逐信号描述仅限 Linux
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
containerfs.availablecontainerfs.available := node.stats.runtime.containerfs.available
containerfs.inodesFreecontainerfs.inodesFree := node.stats.runtime.containerfs.inodesFree
pid.availablepid.available := node.stats.rlimit.maxpid - node.stats.rlimit.curproc

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

内存信号

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

在 Windows 节点上,memory.available 的值源自节点的全局内存提交级别(通过 GetPerformanceInfo() 系统调用查询),方法是从节点的全局 CommitLimit 中减去节点的全局 CommitTotal。 请注意,如果节点的页面文件大小发生变化,CommitLimit 可能会发生变化!

文件系统信号

kubelet 识别可以与驱逐信号一起使用的三个特定文件系统标识符 (<标识符>.inodesFree<标识符>.available)

  1. nodefs:节点的主文件系统,用于本地磁盘卷、不受内存支持的 emptyDir 卷、日志存储、临时存储等。例如,nodefs 包含 /var/lib/kubelet

  2. imagefs:容器运行时可以使用的可选文件系统,用于存储容器镜像(只读层)和容器可写层。

  3. containerfs: 一个可选的文件系统,容器运行时可以使用它来存储可写层。与主文件系统(参见 nodefs)类似,它用于存储本地磁盘卷、非内存支持的 emptyDir 卷、日志存储和临时存储,但不包括容器镜像。当使用 containerfs 时,可以将 imagefs 文件系统拆分,使其仅存储镜像(只读层),而不再存储其他内容。

因此,kubelet 通常允许容器文件系统有三种选择:

  • 所有内容都在单个 nodefs 上,也称为 “rootfs” 或简称为 “root”,并且没有专门的镜像文件系统。

  • 容器存储(参见 nodefs)在专用磁盘上,而 imagefs(可写和只读层)与根文件系统分离。这通常被称为“分割磁盘”(或“单独磁盘”)文件系统。

  • 容器文件系统 containerfs(与 nodefs 相同,加上可写层)位于 root 上,而容器镜像(只读层)存储在单独的 imagefs 上。这通常被称为“分割镜像”文件系统。

kubelet 将尝试直接从底层容器运行时自动发现这些文件系统及其当前配置,并将忽略其他本地节点文件系统。

kubelet 不支持其他容器文件系统或存储配置,并且目前不支持为镜像和容器使用多个文件系统。

已弃用的 kubelet 垃圾回收功能

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

现有标志原因
--maximum-dead-containers一旦旧日志存储在容器上下文之外,则此参数被弃用。
--maximum-dead-containers-per-container一旦旧日志存储在容器上下文之外,则此参数被弃用。
--minimum-container-ttl-duration一旦旧日志存储在容器上下文之外,则此参数被弃用。

驱逐阈值

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

驱逐阈值的格式为 [驱逐信号][运算符][数量],其中

  • 驱逐信号是要使用的驱逐信号
  • 运算符是您想要的关系运算符,例如 <(小于)。
  • 数量是驱逐阈值量,例如 1Gi数量 的值必须与 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 (Linux 节点)
  • memory.available<500Mi (Windows 节点)
  • nodefs.available<10%
  • imagefs.available<15%
  • nodefs.inodesFree<5% (Linux 节点)
  • imagefs.inodesFree<5% (Linux 节点)

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

containerfs.availablecontainerfs.inodesFree (Linux 节点) 的默认驱逐阈值将设置如下:

  • 如果所有内容都使用单个文件系统,则 containerfs 阈值的设置与 nodefs 相同。

  • 如果为镜像和容器配置了单独的文件系统,则 containerfs 阈值的设置与 imagefs 相同。

目前不支持为与 containersfs 相关的阈值设置自定义覆盖,并且如果尝试这样做将发出警告;因此,任何提供的自定义值都将被忽略。

驱逐监控间隔

kubelet 基于其配置的 housekeeping-interval(默认为 10s)评估驱逐阈值。

节点条件

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

kubelet 将驱逐信号映射到节点条件,如下所示:

节点条件驱逐信号描述
MemoryPressurememory.available节点上的可用内存已满足驱逐阈值。
DiskPressurenodefs.available, nodefs.inodesFree, imagefs.available, imagefs.inodesFree, containerfs.availablecontainerfs.inodesFree节点根文件系统、镜像文件系统或容器文件系统上的可用磁盘空间和 inodes 已满足驱逐阈值。
PIDPressurepid.available(Linux) 节点上的可用进程标识符已降至驱逐阈值以下。

控制平面还将这些节点条件映射到污点。

kubelet 根据配置的 --node-status-update-frequency(默认为 10s)更新节点条件。

节点条件震荡

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

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

回收节点级资源

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

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

没有 imagefscontainerfs

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

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

使用 imagefs

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

  • 如果 nodefs 文件系统满足驱逐阈值,则 kubelet 会垃圾回收已死亡的 Pod 和容器。

  • 如果 imagefs 文件系统满足驱逐阈值,则 kubelet 会删除所有未使用的镜像。

使用 imagefscontainerfs

如果节点除了为容器运行时配置的 imagefs 文件系统之外,还具有专用的 containerfs,则 kubelet 将尝试按以下方式回收资源:

  • 如果 containerfs 文件系统满足驱逐阈值,则 kubelet 会垃圾回收已死亡的 Pod 和容器。

  • 如果 imagefs 文件系统满足驱逐阈值,则 kubelet 会删除所有未使用的镜像。

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 会根据节点是否具有专用的 imagefscontainerfs 文件系统来不同地对 Pod 进行排序:

没有 imagefscontainerfsnodefsimagefs 使用相同的文件系统)

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

使用 imagefsnodefsimagefs 文件系统是分开的)

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

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

使用 imagesfscontainerfs (imagefscontainerfs 已拆分)

  • 如果 containerfs 触发驱逐,kubelet 会根据 containerfs 的使用情况(本地卷 + 日志和所有容器的可写层)对 Pod 进行排序。

  • 如果 imagefs 触发驱逐,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-reclaim0

节点内存不足行为

如果在 kubelet 能够回收内存之前节点发生*内存不足* (OOM) 事件,则节点将依赖 oom_killer 来响应。

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

服务质量oom_score_adj
保证的-997
尽力而为1000
可突发的min(max(2, 1000 - (1000 × memoryRequestBytes) / machineMemoryCapacityBytes), 999)

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

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

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

良好实践

以下各节描述了驱逐配置的良好实践。

可调度资源和驱逐策略

当你使用驱逐策略配置 kubelet 时,你应该确保调度程序不会调度那些会因为立即引起内存压力而触发驱逐的 Pod。

考虑以下场景

  • 节点内存容量:10GiB
  • 操作员希望为系统守护进程(内核、kubelet 等)保留 10% 的内存容量
  • 操作员希望在内存利用率达到 95% 时驱逐 Pod,以减少系统 OOM 的发生率。

为了使其工作,kubelet 的启动方式如下

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

在此配置中,--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 killer。

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

如果你不是尝试实现极高的利用率,而是合理地度量过度分配,则解决此问题的可行方法是使用 --kube-reserved--system-reserved 标志为系统分配内存。

active_file 内存不被视为可用内存

在 Linux 上,内核会将活动最近最少使用 (LRU) 列表上以文件为后盾的内存字节数跟踪为 active_file 统计信息。 kubelet 将 active_file 内存区域视为不可回收。对于大量使用基于块的本地存储(包括临时本地存储)的工作负载,文件和块数据的内核级缓存意味着许多最近访问的缓存页面很可能被计为 active_file。如果足够的此类内核块缓冲区在活动 LRU 列表上,则 kubelet 很可能会将其观察为高资源使用率并将节点标记为遇到内存压力 - 触发 Pod 驱逐。

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

你可以通过为可能执行密集 I/O 活动的容器设置相同的内存限制和内存请求来解决此问题。你需要估计或测量该容器的最佳内存限制值。

下一步

上次修改时间:太平洋标准时间 2024 年 8 月 1 日上午 8:55: 更新 content/en/docs/concepts/scheduling-eviction/node-pressure-eviction.md (6fbfd77f3b)