节点压力驱逐
节点压力驱逐是 kubelet 主动终止 Pods 以回收节点上资源的过程。
Kubernetes v1.31 [beta]
(默认开启: true)说明
split image filesystem 特性允许支持containerfs
文件系统,并增加了一些新的驱逐信号、阈值和指标。要使用 containerfs
,Kubernetes v1.33 版本需要启用 KubeletSeparateDiskGC
feature gate。当前,只有 CRI-O (v1.29 或更高版本) 提供了 containerfs
文件系统支持。kubelet 监控集群节点上的内存、磁盘空间和文件系统 inode 等资源。当这些资源中的一个或多个达到特定使用水平时,kubelet 可以主动终止节点上的一个或多个 Pod,以回收资源并防止资源枯竭。
在节点压力驱逐期间,kubelet 将选定 Pod 的阶段(phase)设置为 Failed
并终止该 Pod。
节点压力驱逐与API 发起的驱逐不同。
kubelet 不会遵从你配置的 PodDisruptionBudget 或 Pod 的 terminationGracePeriodSeconds
。如果你使用软驱逐阈值,kubelet 会遵从你配置的 eviction-max-pod-grace-period
。如果你使用硬驱逐阈值,kubelet 会使用 0s
优雅终止时长(立即关停)来终止 Pod。
自愈行为
kubelet 在终止最终用户 Pod 之前,会尝试回收节点级别资源。例如,当磁盘资源不足时,它会移除未使用的容器镜像。
如果 Pod 由能够替换失败 Pod 的工作负载管理对象(例如 StatefulSet 或 Deployment)管理,则控制平面(kube-controller-manager
)会创建新的 Pod 来替代被驱逐的 Pod。
静态 Pod 的自愈
如果你在资源压力下的节点上运行静态 Pod,kubelet 可能会驱逐该静态 Pod。然后 kubelet 会尝试创建一个替代的 Pod,因为静态 Pod 总是代表在该节点上运行 Pod 的意图。
kubelet 在创建替代 Pod 时会考虑静态 Pod 的优先级。如果静态 Pod 清单文件指定的优先级较低,并且集群控制平面中定义了较高优先级的 Pods,同时节点处于资源压力下,则 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
的值是通过 GetPerformanceInfo()
系统调用查询节点的全局内存提交级别,然后用节点的 CommitLimit
减去 CommitTotal
得出。请注意,如果节点页文件大小更改,CommitLimit
可能会改变!
文件系统信号
kubelet 识别三个特定的文件系统标识符,可与驱逐信号(<标识符>.inodesFree
或 <标识符>.available
)一起使用
nodefs
:节点的主文件系统,用于本地磁盘卷、非内存支持的 emptyDir 卷、日志存储、临时存储等。例如,nodefs
包含/var/lib/kubelet
。imagefs
:一个可选的文件系统,容器运行时可以使用它来存储容器镜像(即只读层)和容器可写层。containerfs
:容器运行时可以用来存储可写层的一个可选文件系统。与主文件系统(参阅nodefs
)类似,它用于存储本地磁盘卷、非内存支持的 emptyDir 卷、日志存储和临时存储,但不存储容器镜像。使用containerfs
时,imagefs
文件系统可以分割出来只存储镜像(只读层)而不存储其他内容。
因此,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 不会驱逐 Pods,直到超出优雅终止时长。如果你未指定优雅终止时长,kubelet 在启动时会返回错误。
你可以同时指定软驱逐阈值优雅终止时长和 kubelet 在驱逐期间使用的 Pod 终止最大允许优雅终止时长。如果你指定了最大允许优雅终止时长并且达到了软驱逐阈值,则 kubelet 会使用两个优雅终止时长中较小的一个。如果你未指定最大允许优雅终止时长,则 kubelet 会立即终止被驱逐的 Pods,不进行优雅终止。
你可以使用以下标志配置软驱逐阈值
eviction-soft
:一组驱逐阈值,例如memory.available<1.5Gi
,如果持续超出指定的优雅终止时长,可能会触发 Pod 驱逐。eviction-soft-grace-period
:一组驱逐优雅终止时长,例如memory.available=1m30s
,定义软驱逐阈值必须持续多久才会触发 Pod 驱逐。eviction-max-pod-grace-period
:当达到软驱逐阈值时,用于终止 Pods 的最大允许优雅终止时长(以秒为单位)。
硬驱逐阈值
硬驱逐阈值没有优雅终止时长。当达到硬驱逐阈值时,kubelet 会立即终止 Pods,不进行优雅终止,以回收匮乏的资源。
你可以使用 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。
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 按以下顺序对 Pods 进行排序和驱逐
使用量超出请求的
BestEffort
或Burstable
Pods。这些 Pods 会根据其优先级进行驱逐,然后根据其使用水平超出请求的程度进行驱逐。使用量低于请求的
Guaranteed
Pods 和Burstable
Pods 最后被驱逐,根据其优先级进行驱逐。
说明
kubelet 不使用 Pod 的QoS 类来确定驱逐顺序。你可以使用 QoS 类来估计回收内存等资源时最可能的 Pod 驱逐顺序。QoS 分类不适用于 EphemeralStorage 请求,因此如果节点例如处于DiskPressure
状态,则上述情况不适用。只有当为所有容器都指定了相等 requests 和 limits 时,Guaranteed
Pods 才能得到保证。这些 Pods 永远不会因为其他 Pod 的资源消耗而被驱逐。如果系统守护进程(例如 kubelet
和 journald
)消耗的资源超出了通过 system-reserved
或 kube-reserved
分配预留的资源,并且节点上只剩下使用资源少于请求的 Guaranteed
或 Burstable
Pod,则 kubelet 必须选择驱逐其中一个 Pod,以维护节点稳定性并限制资源不足对其他 Pod 的影响。在这种情况下,它将首先选择驱逐优先级最低的 Pod。
如果你正在运行静态 Pod 并希望避免其在资源压力下被驱逐,请直接设置该 Pod 的 priority
字段。静态 Pod 不支持 priorityClassName
字段。
当 kubelet 响应 inode 或进程 ID 匮乏而驱逐 Pods 时,它使用 Pods 的相对优先级来确定驱逐顺序,因为 inode 和 PID 没有请求。
kubelet 根据节点是否具有专用的 imagefs
或 containerfs
文件系统以不同方式排序 Pods
没有 imagefs
或 containerfs
(nodefs
和 imagefs
使用相同的文件系统)
- 如果
nodefs
触发驱逐,kubelet 会根据 Pod 的总磁盘使用量(本地卷 + 日志和所有容器的可写层
)对 Pods 进行排序。
有 imagefs
(nodefs
和 imagefs
文件系统是分开的)
如果
nodefs
触发驱逐,kubelet 会根据nodefs
使用量(本地卷 + 所有容器的日志
)对 Pods 进行排序。如果
imagefs
触发驱逐,kubelet 会根据所有容器可写层的使用量对 Pods 进行排序。
有 imagefs
和 containerfs
(imagefs
和 containerfs
已分割)
如果
containerfs
触发驱逐,kubelet 会根据containerfs
使用量(本地卷 + 日志和所有容器的可写层
)对 Pods 进行排序。如果
imagefs
触发驱逐,kubelet 会根据storage of images
排名对 Pods 进行排序,该排名代表给定镜像的磁盘使用量。
最小驱逐回收量
说明
从 Kubernetes v1.33 开始,你不能为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
值。
服务质量 (QoS) | 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 配置驱逐策略时,应确保调度器不会调度那些会因为立即引起内存压力而触发驱逐的 Pods。
考虑以下场景
- 节点内存容量: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,以便在阈值被跨越时立即收到通知。
如果你并非要追求极致的利用率,而是寻求合理的超额分配(overcommit),解决此问题的一个可行方法是使用 --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 活动的容器设置相同的内存 limit 和 memory request 来解决此行为。你需要估计或测量该容器的最佳内存 limit 值。