节点压力驱逐
节点压力驱逐是 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)管理,并且这些对象会替换失败的 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 通过将信号与驱逐阈值(即节点上应可用的最小资源量)进行比较来做出驱逐决策。
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 在容器中不起作用,如果用户使用节点可分配功能,则资源不足的决策是针对 cgroup 层次结构的最终用户 Pod 部分以及根节点本地做出的。此脚本或cgroupv2 脚本重现了 kubelet 用于计算 memory.available 的相同步骤。kubelet 在计算中排除了 inactive_file(不活跃 LRU 列表中文件支持内存的字节数),因为它假定在压力下内存是可回收的。
在 Windows 节点上,memory.available 的值是通过将节点的全局 CommitTotal 从节点的 CommitLimit 中减去,从节点的全局内存提交级别(通过 GetPerformanceInfo() 系统调用查询)中派生出来的。请注意,如果节点的页面文件大小发生变化,CommitLimit 可能会发生变化!
文件系统信号
kubelet 识别三个特定的文件系统标识符,可与驱逐信号一起使用(<identifier>.inodesFree 或 <identifier>.available):
nodefs:节点的主文件系统,用于本地磁盘卷、非内存支持的 emptyDir 卷、日志存储、临时存储等。例如,nodefs包含/var/lib/kubelet。imagefs:容器运行时可用于存储容器镜像(只读层)和容器可写层的可选文件系统。containerfs:容器运行时可用于存储可写层的可选文件系统。与主文件系统(见nodefs)类似,它用于存储本地磁盘卷、非内存支持的 emptyDir 卷、日志存储和临时存储,但容器镜像除外。当使用containerfs时,imagefs文件系统可以拆分以仅存储镜像(只读层)而不再存储其他内容。
注意
Kubernetes v1.31 [beta] (默认启用:true)“拆分镜像文件系统”功能支持 containerfs 文件系统,它增加了几个新的驱逐信号、阈值和指标。要使用 containerfs,Kubernetes v1.34 版本需要启用 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是驱逐阈值数量,例如1Gi。quantity的值必须与 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 节点)
这些硬驱逐阈值的默认值只有在所有参数都未更改的情况下才会设置。如果你更改任何参数的值,则其他参数的值将不会作为默认值继承,而是设置为零。为了提供自定义值,你应该分别提供所有阈值。你还可以在 kubelet 配置文件中将 kubelet 配置 MergeDefaultEvictionSettings 设置为 true。如果设置为 true 并且任何参数被更改,则其他参数将继承其默认值而不是 0。
containerfs.available 和 containerfs.inodesFree(Linux 节点)默认驱逐阈值将按如下方式设置:
如果所有内容都使用单个文件系统,则
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。
回收节点级资源
kubelet 会在驱逐终端用户 Pod 之前尝试回收节点级资源。
当报告 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 会删除所有未使用的镜像。
kubelet 驱逐的 Pod 选择
如果 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 的总磁盘使用量(本地卷 + 日志和所有容器的可写层)对 Pod 进行排序。
有 imagefs(nodefs 和 imagefs 文件系统是分开的)
如果
nodefs触发驱逐,kubelet 会根据nodefs使用量(本地卷 + 所有容器的日志)对 Pod 进行排序。如果
imagefs触发驱逐,kubelet 会根据所有容器的可写层使用量对 Pod 进行排序。
有 imagesfs 和 containerfs(imagefs 和 containerfs 已拆分)
如果
containerfs触发驱逐,kubelet 会根据containerfs使用量(本地卷 + 日志和所有容器的可写层)对 Pod 进行排序。如果
imagefs触发驱逐,kubelet 会根据镜像存储排名对 Pod 进行排序,这表示给定镜像的磁盘使用量。
最小驱逐回收量
注意
自 Kubernetes v1.34 起,你无法为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