节点压力驱逐
节点压力驱逐是指 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 由 工作负载 管理对象(例如 StatefulSet 或 Deployment)管理,则控制平面(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.available | memory.available := node.status.capacity[memory] - node.stats.memory.workingSet | |
nodefs.available | nodefs.available := node.stats.fs.available | |
nodefs.inodesFree | nodefs.inodesFree := node.stats.fs.inodesFree | • |
imagefs.available | imagefs.available := node.stats.runtime.imagefs.available | |
imagefs.inodesFree | imagefs.inodesFree := node.stats.runtime.imagefs.inodesFree | • |
containerfs.available | containerfs.available := node.stats.runtime.containerfs.available | |
containerfs.inodesFree | containerfs.inodesFree := node.stats.runtime.containerfs.inodesFree | • |
pid.available | pid.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(inactive LRU 列表上的文件支持内存的字节数),因为它假设在压力下可以回收内存。
在 Windows 节点上,memory.available 的值是从节点的全局内存提交级别(通过 GetPerformanceInfo() 系统调用查询)派生的,方法是从节点的 CommitLimit 中减去节点的 CommitTotal。请注意,如果节点的页面文件大小发生变化,CommitLimit 可能会发生变化!
文件系统信号
kubelet 识别可以使用驱逐信号(<identifier>.inodesFree 或 <identifier>.available)的三个特定文件系统标识符
nodefs:节点的根文件系统,用于本地磁盘卷、不由内存支持的 emptyDir 卷、日志存储、临时存储等。例如,nodefs包含/var/lib/kubelet。imagefs:容器运行时可以用来存储容器镜像(只读层)和容器可写层的可选文件系统。containerfs:容器运行时可以用来存储可写层的可选文件系统。与主文件系统(参见nodefs)类似,它用于存储本地磁盘卷、不由内存支持的 emptyDir 卷、日志存储和临时存储,但不包括容器镜像。当使用containerfs时,imagefs文件系统可以拆分为仅存储镜像(只读层)而不存储其他任何内容。
说明
Kubernetes v1.31 [beta](默认启用)启用 containerfs 文件系统支持的拆分镜像文件系统特性,添加了几个新的驱逐信号、阈值和指标。要使用 containerfs,Kubernetes v1.35 版本需要启用 特性门控 KubeletSeparateDiskGC。目前,只有 CRI-O(v1.29 或更高版本)提供 containerfs 文件系统支持。
因此,kubelet 通常允许三种容器文件系统选项
所有内容都在单个
nodefs上,也称为“rootfs”或简单地“root”,并且没有专用的镜像文件系统。容器存储(参见
nodefs)位于专用磁盘上,而imagefs(可写和只读层)与根文件系统分开。这通常被称为“拆分磁盘”(或“独立磁盘”)文件系统。容器文件系统
containerfs(与nodefs相同加上可写层)位于根目录上,而容器镜像(只读层)存储在单独的imagefs上。这通常被称为“拆分镜像”文件系统。
kubelet 将尝试从底层容器运行时直接自动发现这些文件系统及其当前配置,并忽略其他本地节点文件系统。
kubelet 不支持其他容器文件系统或存储配置,并且目前不支持镜像和容器的多个文件系统。
弃用的 kubelet 清理功能
一些 kubelet 清理功能已被弃用,转而使用驱逐
| 现有标志 | 理由 |
|---|---|
--maximum-dead-containers | 弃用,因为旧日志存储在容器上下文之外 |
--maximum-dead-containers-per-container | 弃用,因为旧日志存储在容器上下文之外 |
--minimum-container-ttl-duration | 弃用,因为旧日志存储在容器上下文之外 |
驱逐阈值
您可以指定 kubelet 在做出驱逐决策时使用的自定义驱逐阈值。您可以配置 软 和 硬 驱逐阈值。
驱逐阈值的形式为 [eviction-signal][operator][quantity],其中
eviction-signal是 驱逐信号。operator是你想要的 关系运算符,例如<(小于)。quantity是驱逐阈值数量,例如1Gi。quantity的值必须与 Kubernetes 中使用的数量表示形式匹配。你可以使用字面值或百分比 (%)。
例如,如果一个节点有 10GiB 的总内存,并且你想在可用内存降至 1GiB 以下时触发驱逐,你可以将驱逐阈值定义为 memory.available<10% 或 memory.available<1Gi(你不能同时使用两者)。
软驱逐阈值
软驱逐阈值将驱逐阈值与管理员指定的必需宽限期配对。在超过宽限期之前,kubelet 不会驱逐 Pod。如果你没有指定宽限期,kubelet 会在启动时返回错误。
你可以指定一个软驱逐阈值宽限期和一个 kubelet 在驱逐期间使用的最大允许 Pod 终止宽限期。如果你指定了最大允许宽限期,并且满足了软驱逐阈值,kubelet 将使用这两个宽限期中较小的一个。如果你没有指定最大允许宽限期,kubelet 将立即终止被驱逐的 Pod,而不会进行优雅终止。
你可以使用以下标志来配置软驱逐阈值
eviction-soft:一组可以触发 Pod 驱逐的驱逐阈值,例如memory.available<1.5Gi,如果超过指定的宽限期。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 节点)
这些硬驱逐阈值的默认值只有在没有更改任何参数的情况下才会设置。如果你更改了任何参数的值,则其他参数的值将不会继承默认值,并将设置为零。为了提供自定义值,你应该分别提供所有阈值。你也可以在 kubelet 配置文件中将 kubelet config MergeDefaultEvictionSettings 设置为 true。如果设置为 true 并且更改了任何参数,则其他参数将继承其默认值,而不是 0。
对于 Linux 节点,containerfs.available 和 containerfs.inodesFree 的默认驱逐阈值将设置为如下
如果单个文件系统用于所有内容,则
containerfs阈值与nodefs阈值相同。如果为镜像和容器配置了单独的文件系统,则
containerfs阈值与imagefs阈值相同。
目前不支持为与 containersfs 相关的阈值设置自定义覆盖,如果尝试这样做,将发出警告;因此,提供的任何自定义值都将被忽略。
驱逐监控间隔
kubelet 基于其配置的 housekeeping-interval 评估驱逐阈值,默认值为 10s。
节点条件
kubelet 会报告 节点条件,以反映节点由于满足了硬或软驱逐阈值而处于压力之下,与配置的宽限期无关。
kubelet 将驱逐信号映射到节点条件,如下所示
| 节点条件 | 驱逐信号 | 描述 |
|---|---|---|
MemoryPressure | memory.available | 节点上的可用内存满足了驱逐阈值 |
DiskPressure | nodefs.available、nodefs.inodesFree、imagefs.available、imagefs.inodesFree、containerfs.available 或 containerfs.inodesFree | 节点根文件系统、镜像文件系统或容器文件系统上的可用磁盘空间和 inode 满足了驱逐阈值 |
PIDPressure | pid.available | (Linux)节点上可用的进程标识符已降至驱逐阈值以下 |
控制平面还会 映射 这些节点条件到污点。
kubelet 基于配置的 --node-status-update-frequency 更新节点条件,默认值为 10s。
节点条件震荡
在某些情况下,节点在软驱逐阈值之上和之下震荡,而没有保持定义的宽限期。这导致报告的节点条件不断在 true 和 false 之间切换,从而导致错误的驱逐决策。
为了防止震荡,你可以使用 eviction-pressure-transition-period 标志,该标志控制 kubelet 在将节点条件转换为不同状态之前必须等待多长时间。过渡期默认为 5m。
回收节点级别资源
在驱逐最终用户 Pod 之前,kubelet 会尝试回收节点级别资源。
当报告 DiskPressure 节点条件时,kubelet 会根据节点上的文件系统回收节点级别资源。
没有 imagefs 或 containerfs
如果节点只有满足驱逐阈值的 nodefs 文件系统,kubelet 将按以下顺序释放磁盘空间
- 垃圾回收已死的 Pod 和容器。
- 删除未使用的镜像。
有 imagefs
如果节点为容器运行时配置了专用的 imagefs 文件系统,kubelet 将执行以下操作
如果
nodefs文件系统满足驱逐阈值,kubelet 将垃圾回收已死的 Pod 和容器。如果
imagefs文件系统满足驱逐阈值,kubelet 将删除所有未使用的镜像。
有 imagefs 和 containerfs
如果节点配置了专用的 containerfs 以及容器运行时使用的 imagefs 文件系统,那么 kubelet 将尝试按如下方式回收资源
如果
containerfs文件系统满足驱逐阈值,kubelet 将垃圾回收已死的 Pod 和容器。如果
imagefs文件系统满足驱逐阈值,kubelet 将删除所有未使用的镜像。
Pod 选择用于 kubelet 驱逐
如果 kubelet 尝试回收节点级别资源未能将驱逐信号降至阈值以下,kubelet 将开始驱逐最终用户 Pod。
kubelet 使用以下参数来确定 Pod 驱逐顺序
- Pod 的资源使用量是否超过请求
- Pod 优先级
- Pod 的资源使用量相对于请求
因此,kubelet 会按以下顺序对 Pod 进行排序和驱逐
BestEffort或BurstablePod,其中使用量超过请求。这些 Pod 基于其优先级以及使用量超过请求的程度进行驱逐。GuaranteedPod 和BurstablePod,其中使用量小于请求,最后被驱逐,基于其优先级。
说明
kubelet 不使用 Pod 的 QoS 类 来确定驱逐顺序。你可以使用 QoS 类来估计在回收内存等资源时最有可能的 Pod 驱逐顺序。QoS 分类不适用于 EphemeralStorage 请求,因此如果节点例如处于DiskPressure 状态,则上述场景将不适用。只有当所有容器的请求和限制都已指定并且相等时,Guaranteed Pod 才能保证。这些 Pod 不会因为另一个 Pod 的资源消耗而被驱逐。如果系统守护程序(例如 kubelet 和 journald)消耗的资源超过通过 system-reserved 或 kube-reserved 分配保留的资源,并且节点上只剩下使用资源少于请求的 Guaranteed 或 Burstable Pod,那么 kubelet 必须选择驱逐其中一个 Pod 以保持节点稳定性并限制资源匮乏对其他 Pod 的影响。在这种情况下,它将首先选择驱逐优先级最低的 Pod。
如果你正在运行一个 静态 Pod 并且希望避免在资源压力下将其驱逐,请直接为该 Pod 设置 priority 字段。静态 Pod 不支持 priorityClassName 字段。
当 kubelet 响应 inode 或进程 ID 匮乏时驱逐 Pod 时,它使用 Pod 的相对优先级来确定驱逐顺序,因为 inode 和 PID 没有请求。
kubelet 根据节点是否具有专用的 imagefs 或 containerfs 文件系统,对 Pod 进行不同的排序
没有 imagefs 或 containerfs(nodefs 和 imagefs 使用相同的文件系统)
- 如果
nodefs触发驱逐,kubelet 会根据 Pod 的总磁盘使用量(local volumes + logs 和所有容器的可写层)对 Pod 进行排序。
有 imagefs(nodefs 和 imagefs 文件系统是分开的)
如果
nodefs触发驱逐,kubelet 会根据nodefs使用量(local volumes + 所有容器的日志)对 Pod 进行排序。如果
imagefs触发驱逐,kubelet 会根据所有容器的可写层使用量对 Pod 进行排序。
有 imagesfs 和 containerfs(imagefs 和 containerfs 已拆分)
如果
containerfs触发驱逐,kubelet 会根据containerfs使用量(local volumes + logs 和所有容器的可写层)对 Pod 进行排序。如果
imagefs触发驱逐,kubelet 会根据给定镜像的磁盘使用量对 Pod 进行排序,该磁盘使用量表示storage of images排名。
最小驱逐回收
说明
从 Kubernetes v1.35 开始,你无法为containerfs.available 指标设置自定义值。此特定指标的配置将自动设置为反映为 nodefs 或 imagefs 设置的值,具体取决于配置。在某些情况下,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 |
BestEffort | 1000 |
Burstable | 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 活动的容器设置相同的内存限制和内存请求来解决此行为。您需要估计或测量该容器的最佳内存限制值。
接下来
- 了解 API 发起的驱逐
- 了解 Pod 优先级和抢占
- 了解 PodDisruptionBudgets
- 了解 服务质量 (QoS)
- 查看 驱逐 API