节点压力驱逐
节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。
Kubernetes v1.31 [beta]
注意
分割镜像文件系统功能(支持containerfs
文件系统)添加了一些新的驱逐信号、阈值和指标。要使用 containerfs
,Kubernetes 版本 v1.31 要求启用 KubeletSeparateDiskGC
功能开关。目前,只有 CRI-O(v1.29 或更高版本)提供 containerfs
文件系统支持。kubelet 监控集群节点上的资源,如内存、磁盘空间和文件系统 inode。当其中一个或多个资源达到特定使用量时,kubelet 可以主动在节点上失败一个或多个 Pod 以回收资源并防止资源耗尽。
在节点压力驱逐期间,kubelet 将所选 Pod 的 阶段 设置为 Failed
,并终止 Pod。
节点压力驱逐与 API 驱逐 不同。
kubelet 不尊重您配置的 PodDisruptionBudget 或 Pod 的 terminationGracePeriodSeconds
。如果您使用 软驱逐阈值,kubelet 将尊重您配置的 eviction-max-pod-grace-period
。如果您使用 硬驱逐阈值,kubelet 将使用 0s
优雅期(立即关闭)进行终止。
自愈行为
在终止最终用户 Pod 之前,kubelet 会尝试 回收节点级资源。例如,它会在磁盘资源耗尽时删除未使用的容器镜像。
如果 Pod 由 工作负载 管理对象(例如 StatefulSet 或 Deployment)管理,该对象会替换失败的 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.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(非活动 LRU 列表上的文件支持内存的字节数),因为它假设内存在压力下是可回收的。
在 Windows 节点上,memory.available
的值来自节点的全局内存提交级别(通过 GetPerformanceInfo()
系统调用查询),通过从节点的全局 CommitTotal
中减去节点的 CommitLimit
来得出。请注意,如果节点的页面文件大小发生变化,CommitLimit
也会发生变化!
文件系统信号
kubelet 识别三个特定文件系统标识符,这些标识符可与驱逐信号一起使用(<identifier>.inodesFree
或 <identifier>.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]
,其中
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 节点)
这些硬驱逐阈值的默认值只有在未更改任何参数时才会设置。如果您更改了任何参数的值,则其他参数的值将不会继承为默认值,并将设置为零。为了提供自定义值,您应该分别提供所有阈值。
containerfs.available
和 containerfs.inodesFree
(Linux 节点)默认驱逐阈值将按以下方式设置
如果单个文件系统用于所有内容,则
containerfs
阈值与nodefs
相同。如果为镜像和容器配置了单独的文件系统,则
containerfs
阈值与imagefs
相同。
目前不支持为与 containersfs
相关的阈值设置自定义覆盖,如果尝试这样做,将发出警告;因此,任何提供的自定义值都将被忽略。
驱逐监控间隔
kubelet 基于其配置的 housekeeping-interval
(默认为 10s
)评估驱逐阈值。
节点状态
kubelet 会报告 节点状态 以反映节点处于压力之下,因为满足了硬驱逐或软驱逐阈值,与配置的宽限期无关。
kubelet 将驱逐信号映射到节点状态,如下所示
节点状态 | 驱逐信号 | 描述 |
---|---|---|
内存压力 | memory.available | 节点上的可用内存已满足驱逐阈值 |
磁盘压力 | nodefs.available 、nodefs.inodesFree 、imagefs.available 、imagefs.inodesFree 、containerfs.available 或 containerfs.inodesFree | 节点的根文件系统、镜像文件系统或容器文件系统上的可用磁盘空间和 inode 已满足驱逐阈值 |
PID 压力 | 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
或Burstable
Pod,其使用量超过请求。这些 Pod 根据其优先级进行驱逐,然后根据其使用量超过请求的程度进行驱逐。Guaranteed
Pod 和Burstable
Pod,其使用量小于请求,最后根据其优先级进行驱逐。
注意
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.31 开始,您无法为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
值。
服务质量 |
|
---|---|
保证 | -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,因为这些 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 杀手仍然会被调用。
您可以使用 --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