配置资源不足时的处理方式
本页介绍了如何使用kubelet配置资源不足时的处理方式。
当可用计算资源较少时,kubelet需要保证节点稳定性。这在处理如内存和硬盘之类的不可压缩资源时尤为重要。如果任意一种资源耗尽,节点将会变得不稳定。
- 驱逐策略
- 节点 OOM 行为
- 最佳实践
- 弃用现有特性标签以回收磁盘
驱逐策略
kubelet 能够主动监测和防止计算资源的全面短缺。在那种情况下,kubelet可以主动地结束一个或多个 pod 以回收短缺的资源。当 kubelet 结束一个 pod 时,它将终止 pod 中的所有容器,而 pod 的 PodPhase
将变为 Failed
。
如果被驱逐的 Pod 由 Deployment 管理,这个 Deployment 会创建另一个 Pod 给 Kubernetes 来调度。
驱逐信号
kubelet
支持按照以下表格中描述的信号触发驱逐决定,基于 kubelet
摘要 API。
驱逐信号 | 描述 |
---|---|
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 |
上面的每个信号都支持字面值或百分比的值。基于百分比的值的计算与每个信号对应的总容量相关。
memory.available
的值从 cgroupfs 获取,而不是通过类似 free -m
的工具。这很重要,因为 free -m
不能在容器中工作,并且如果用户使用了可分配节点特性,资源不足的判定将同时在本地 cgroup 层次结构的终端用户 pod 部分和根节点做出。下面的脚本复现了与 kubelet
计算 memory.available
相同的步骤。kubelet
将inactive_file
(意即活动 LRU 列表上基于文件后端的内存字节数)从计算中排除,因为它假设内存在出现压力时将被回收。
#!/bin/bash
#!/usr/bin/env bash
# This script reproduces what the kubelet does
# to calculate memory.available relative to root cgroup.
# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)
memory_total_inactive_file=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}')
memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
memory_working_set=0
else
memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi
memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))
echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_file"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"
kubelet
只支持两种文件系统分区。
nodefs
文件系统,kubelet 将其用于卷和守护程序日志等。imagefs
文件系统,容器运行时用于保存镜像和容器可写层。
imagefs
可选。kubelet
使用 cAdvisor 自动发现这些文件系统。kubelet
不关心其它文件系统。当前不支持配置任何其它类型。例如,在专用文件系统
中存储卷和日志是不可以的。
在将来的发布中,kubelet
将废除当前存在的 垃圾回收 机制,这种机制目前支持将驱逐操作作为对磁盘压力的响应。
驱逐阈值
kubelet
支持指定驱逐阈值,用于触发 kubelet
回收资源。
每个阈值形式如下:
[eviction-signal][operator][quantity]
- 合法的
eviction-signal
标志如上所示。 operator
是所需的关系运算符,例如<
。quantity
是驱逐阈值值标志,例如1Gi
。合法的标志必须匹配 Kubernetes 使用的数量表示。驱逐阈值也可以使用%
标记表示百分比。
举例说明,如果一个节点有 10Gi
内存,希望在可用内存下降到 1Gi
以下时引起驱逐操作,则驱逐阈值可以使用下面任意一种方式指定(但不是两者同时)。
memory.available<10%
memory.available<1Gi
软驱逐阈值
软驱逐阈值使用一对由驱逐阈值和管理员必须指定的宽限期组成的配置对。在超过宽限期前,kubelet
不会采取任何动作回收和驱逐信号关联的资源。如果没有提供宽限期,kubelet
启动时将报错。
此外,如果达到了软驱逐阈值,操作员可以指定从节点驱逐 pod 时,在宽限期内允许结束的 pod 的最大数量。如果指定了 pod.Spec.TerminationGracePeriodSeconds
值,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
将立即采取行动回收关联的短缺资源。如果满足硬驱逐阈值,kubelet
将立即结束 pod 而不是优雅终止。
硬驱逐阈值的配置支持下列标记:
eviction-hard
描述了驱逐阈值的集合(例如memory.available<1Gi
),如果满足条件将触发 pod 驱逐。
kubelet
有如下所示的默认硬驱逐阈值:
memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
驱逐监控时间间隔
kubelet
根据其配置的整理时间间隔计算驱逐阈值。
housekeeping-interval
是容器管理时间间隔。
节点状态
kubelet
会将一个或多个驱逐信号映射到对应的节点状态。
如果满足硬驱逐阈值,或者满足独立于其关联宽限期的软驱逐阈值时,kubelet
将报告节点处于压力下的状态。
下列节点状态根据相应的驱逐信号定义。
节点状态 | 驱逐信号 | 描述 |
---|---|---|
MemoryPressure | memory.available | 节点上可用内存量达到逐出阈值 |
DiskPressure | nodefs.available, nodefs.inodesFree, imagefs.available, 或 imagefs.inodesFree | 节点或者节点的根文件系统或镜像文件系统上可用磁盘空间和i节点个数达到逐出阈值 |
PIDPressure | pid.available | 在(Linux)节点上的可用进程标识符已降至驱逐阈值以下 |
kubelet
将以 --node-status-update-frequency
指定的频率连续报告节点状态更新,其默认值为 10s
。
节点状态振荡
如果节点在软驱逐阈值的上下振荡,但没有超过关联的宽限期时,将引起对应节点的状态持续在 true 和 false 间跳变,并导致不好的调度结果。
为了防止这种振荡,可以定义下面的标志,用于控制 kubelet
从压力状态中退出之前必须等待的时间。
eviction-pressure-transition-period
是kubelet
从压力状态中退出之前必须等待的时长。
kubelet
将确保在设定的时间段内没有发现和指定压力条件相对应的驱逐阈值被满足时,才会将状态变回 false
。
回收节点层级资源
如果满足驱逐阈值并超过了宽限期,kubelet
将启动回收压力资源的过程,直到它发现低于设定阈值的信号为止。
kubelet
将尝试在驱逐终端用户 pod 前回收节点层级资源。发现磁盘压力时,如果节点针对容器运行时配置有独占的 imagefs
,kubelet
回收节点层级资源的方式将会不同。
使用 Imagefs
如果 nodefs
文件系统满足驱逐阈值,kubelet
通过驱逐 pod 及其容器来释放磁盘空间。
如果 imagefs
文件系统满足驱逐阈值,kubelet
通过删除所有未使用的镜像来释放磁盘空间。
未使用 Imagefs
如果 nodefs
满足驱逐阈值,kubelet
将以下面的顺序释放磁盘空间:
- 删除停止运行的 pod/container
- 删除全部没有使用的镜像
驱逐最终用户的 pod
如果 kubelet
在节点上无法回收足够的资源,kubelet
将开始驱逐 pod。
kubelet
首先根据他们对短缺资源的使用是否超过请求来排除 pod 的驱逐行为,然后通过优先级,然后通过相对于 pod 的调度请求消耗急需的计算资源。
kubelet
按以下顺序对要驱逐的 pod 排名:
BestEffort
或Burstable
,其对短缺资源的使用超过了其请求,此类 pod 按优先级排序,然后使用高于请求。Guaranteed
pod 和Burstable
pod,其使用率低于请求,最后被驱逐。Guaranteed
pod 只有为所有的容器指定了要求和限制并且它们相等时才能得到保证。由于另一个 pod 的资源消耗,这些 pod 保证永远不会被驱逐。如果系统守护进程(例如kubelet
、docker
、和journald
)消耗的资源多于通过system-reserved
或kube-reserved
分配保留的资源,并且该节点只有Guaranteed
或Burstable
pod 使用少于剩余的请求,然后节点必须选择驱逐这样的 pod 以保持节点的稳定性并限制意外消耗对其他 pod 的影响。在这种情况下,它将首先驱逐优先级最低的 pod。
必要时,kubelet
会在遇到 DiskPressure
时驱逐一个 pod 来回收磁盘空间。如果 kubelet
响应 inode
短缺,它会首先驱逐服务质量最低的 pod 来回收 inodes
。如果 kubelet
响应缺少可用磁盘,它会将 pod 排在服务质量范围内,该服务会消耗大量的磁盘并首先结束这些磁盘。
使用 imagefs
如果是 nodefs
触发驱逐,kubelet
将按 nodefs
用量 - 本地卷 + pod 的所有容器日志的总和对其排序。
如果是 imagefs
触发驱逐,kubelet
将按 pod 所有可写层的用量对其进行排序。
未使用 imagefs
如果是 nodefs
触发驱逐,kubelet
会根据磁盘的总使用情况对 pod 进行排序 - 本地卷 + 所有容器的日志及其可写层。
最小驱逐回收
在某些场景,驱逐 pod 会导致回收少量资源。这将导致 kubelet
反复碰到驱逐阈值。除此之外,对如 disk
这类资源的驱逐时比较耗时的。
为了减少这类问题,kubelet
可以为每个资源配置一个 minimum-reclaim
。当 kubelet
发现资源压力时,kubelet
将尝试至少回收驱逐阈值之下 minimum-reclaim
数量的资源。
例如使用下面的配置:
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
如果 memory.available
驱逐阈值被触发,kubelet
将保证 memory.available
至少为 500Mi
。对于 nodefs.available
,kubelet
将保证 nodefs.available
至少为 1.5Gi
。对于 imagefs.available
,kubelet
将保证 imagefs.available
至少为 102Gi
,直到不再有相关资源报告压力为止。
所有资源的默认 eviction-minimum-reclaim
值为 0
。
调度器
当资源处于压力之下时,节点将报告状态。调度器将那种状态视为一种信号,阻止更多 pod 调度到这个节点上。
节点状态 | 调度器行为 |
---|---|
MemoryPressure |
新的 BestEffort Pod 不会被调度到该节点 |
DiskPressure |
新的 Pod 不会被调度到该节点 |
节点 OOM 行为
如果节点在 kubelet
回收内存之前经历了系统 OOM(内存不足)事件,它将基于 oom-killer 做出响应。
kubelet
基于 pod 的 service 质量为每个容器设置一个 oom_score_adj
值。
Service 质量 | oom_score_adj |
---|---|
Guaranteed |
-998 |
BestEffort |
1000 |
Burstable |
min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
如果 kubelet
在节点经历系统 OOM 之前无法回收内存,oom_killer
将基于它在节点上使用的内存百分比算出一个 oom_score
,并加上 oom_score_adj
得到容器的有效 oom_score
,然后结束得分最高的容器。
预期的行为应该是拥有最低 service 质量并消耗和调度请求相关内存量最多的容器第一个被结束,以回收内存。
和 pod 驱逐不同,如果一个 pod 的容器是被 OOM 结束的,基于其 RestartPolicy
,它可能会被 kubelet
重新启动。
最佳实践
以下部分描述了资源外处理的最佳实践。
可调度资源和驱逐策略
考虑以下场景:
- 节点内存容量:
10Gi
- 操作员希望为系统守护进程保留 10% 内存容量(内核、
kubelet
等)。 - 操作员希望在内存用量达到 95% 时驱逐 pod,以减少对系统的冲击并防止系统 OOM 的发生。
为了促成这个场景,kubelet
将像下面这样启动:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
这个配置的暗示是理解系统保留应该包含被驱逐阈值覆盖的内存数量。
要达到这个容量,要么某些 pod 使用了超过它们请求的资源,要么系统使用的内存超过 1.5Gi - 500Mi = 1Gi
。
这个配置将保证在 pod 使用量都不超过它们配置的请求值时,如果可能立即引起内存压力并触发驱逐时,调度器不会将 pod 放到这个节点上。
DaemonSet
我们永远都不希望 kubelet
驱逐一个从 DaemonSet
派生的 pod,因为这个 pod 将立即被重建并调度回相同的节点。
目前,kubelet
没有办法区分一个 pod 是由 DaemonSet
还是其他对象创建。如果当这个信息可用时,kubelet
可能会预先将这些 pod 从提供给驱逐策略的候选集合中过滤掉。
总之,强烈推荐 DaemonSet
不要创建 BestEffort
的 pod,防止其被识别为驱逐的候选 pod。相反,理想情况下 DaemonSet
应该启动 Guaranteed
的 pod。
弃用现有特性标签以回收磁盘
kubelet
已经按需求清空了磁盘空间以保证节点稳定性。
当磁盘驱逐成熟时,下面的 kubelet
标志将被标记为废弃的,以简化支持驱逐的配置。
现有标签 | 新标签 |
---|---|
--image-gc-high-threshold |
--eviction-hard or eviction-soft |
--image-gc-low-threshold |
--eviction-minimum-reclaim |
--maximum-dead-containers |
deprecated(弃用) |
--maximum-dead-containers-per-container |
deprecated(弃用) |
--minimum-container-ttl-duration |
deprecated(弃用) |
--low-diskspace-threshold-mb |
--eviction-hard or eviction-soft |
--outofdisk-transition-frequency |
--eviction-pressure-transition-period |
已知问题
以下部分描述了与资源外处理有关的已知问题。
kubelet 可能无法立即发现内存压力
kubelet
当前通过以固定的时间间隔轮询 cAdvisor
来收集内存使用数据。如果内存使用在那个时间窗口内迅速增长,kubelet
可能不能足够快的发现 MemoryPressure
,OOMKiller
将不会被调用。我们准备在将来的发行版本中通过集成 memcg
通知 API 来减小这种延迟。当超过阈值时,内核将立即告诉我们。
如果您想处理可察觉的超量使用而不要求极端精准,可以设置驱逐阈值为大约 75% 容量作为这个问题的变通手段。这将增强这个特性的能力,防止系统 OOM,并提升负载卸载能力,以再次平衡集群状态。
kubelet 可能会驱逐超过需求数量的 pod
由于状态采集的时间差,驱逐操作可能驱逐比所需的更多的 pod。将来可通过添加从根容器获取所需状态的能力 https://github.com/google/cadvisor/issues/1247 来减缓这种状况。
限制了pod的limits,该pod内多个容器没有设置limits,容器资源会被限制在pod设置的limits范围内吗?还是说会被k8s OOM?或者是达到了k8s集群设置的资源阈值,被系统给OOM?
容器OOM的原因是不是pod没做limits限制,容器也没做limits限制,然后容器资源占用满了k8s集群设置的资源,kubelet就把该容器给OOM了?
limit不是针对容器的吗?我需要看到你具体pod yaml文件,发起一个问题吧。