这篇文章已过时一年以上。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.27:内存资源的服务质量(alpha)
Kubernetes v1.27 于 2023 年 4 月发布,引入了内存 QoS(alpha)的更改,以改进 Linux 节点中的内存管理功能。
对内存 QoS 的支持最初在 Kubernetes v1.22 中添加,后来发现关于计算 memory.high
的公式存在一些限制。这些限制在 Kubernetes v1.27 中得到了解决。
背景
Kubernetes 允许您在 Pod 规范中选择性地指定容器需要的每种资源量。最常见的指定资源是 CPU 和内存。
例如,一个定义容器资源要求的 Pod 清单可能如下所示
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "64Mi"
cpu: "500m"
spec.containers[].resources.requests
当您在 Pod 中指定容器的资源请求时,Kubernetes 调度器使用此信息来决定将 Pod 放置在哪个节点上。调度器确保对于每种资源类型,调度的容器的资源请求总和小于节点上的总可分配资源。
spec.containers[].resources.limits
当您在 Pod 中指定容器的资源限制时,kubelet 会强制执行这些限制,以便正在运行的容器不允许使用超过您设置的限制的资源。
当 kubelet 启动作为 Pod 一部分的容器时,kubelet 会将容器的 CPU 和内存的请求和限制传递给容器运行时。容器运行时将 CPU 请求和 CPU 限制都分配给容器。如果系统有空闲 CPU 时间,则保证为容器分配尽可能多的 CPU 作为其请求。容器不能使用超过配置限制的 CPU,即如果容器在给定的时间片内使用超过指定限制的 CPU,则容器的 CPU 使用率将被限制。
在内存 QoS 功能之前,容器运行时仅使用内存限制并丢弃内存 request
(请求过去并且仍然用于影响调度)。如果容器使用的内存超过配置的限制,则会调用 Linux 内存不足 (OOM) 杀手。
让我们比较一下 Linux 上的容器运行时通常如何在 cgroups 中配置内存请求和限制,无论是否有内存 QoS 功能
内存请求
内存请求主要由 kube-scheduler 在(Kubernetes)Pod 调度期间使用。在 cgroups v1 中,没有控制项可以指定 cgroups 必须始终保留的最小内存量。因此,容器运行时不使用 Pod 规范中设置的请求内存的值。
cgroups v2 引入了
memory.min
设置,用于指定给定 cgroup 中进程应始终可用的最小内存量。如果 cgroup 的内存使用量在其有效的最小边界内,则在任何情况下都不会回收 cgroup 的内存。如果内核无法为 cgroup 中的进程维护至少memory.min
字节的内存,则内核会调用其 OOM 杀手。换句话说,内核保证至少有这么多内存可用,或者终止进程(可能在 cgroup 之外)以使更多内存可用。内存 QoS 将memory.min
映射到spec.containers[].resources.requests.memory
,以确保 Kubernetes Pod 中容器的内存可用性。内存限制
memory.limit
指定内存限制,如果容器尝试分配更多内存,超过该限制,Linux 内核将使用 OOM(内存不足)kill 终止进程。如果终止的进程是容器内部的主要(或唯一)进程,则容器可能会退出。在 cgroups v1 中,
memory.limit_in_bytes
接口用于设置内存使用限制。但是,与 CPU 不同,不可能应用内存限制:一旦容器超出内存限制,它将被 OOM 杀死。在 cgroups v2 中,
memory.max
类似于 cgroupv1 中的memory.limit_in_bytes
。内存 QoS 将memory.max
映射到spec.containers[].resources.limits.memory
,以指定内存使用量的硬限制。如果内存消耗超过此水平,内核会调用其 OOM 杀手。cgroups v2 还添加了
memory.high
配置。内存 QoS 使用memory.high
设置内存使用限制。如果违反了memory.high
限制,则会限制违规的 cgroups,并且内核会尝试回收内存,这可以避免 OOM 杀死。
它是如何工作的
Cgroups v2 内存控制器接口 & Kubernetes 容器资源映射
内存 QoS 使用 cgroups v2 的内存控制器来保证 Kubernetes 中的内存资源。此功能使用的 cgroupv2 接口是
memory.max
memory.min
memory.high
.
内存 QoS 级别
memory.max
映射到 Pod 规范中指定的 limits.memory
。kubelet 和容器运行时在各自的 cgroup 中配置限制。内核强制执行该限制,以防止容器使用超过配置的资源限制。如果容器中的进程尝试消耗超过指定限制的资源,则内核将使用内存不足 (OOM) 错误终止一个或多个进程。
memory.max 映射到 limits.memory
memory.min
映射到 requests.memory
,这会导致预留内核永远不应回收的内存资源。这就是内存 QoS 如何确保 Kubernetes pod 的内存可用性。如果没有未受保护的可回收内存可用,则会调用 OOM 杀手以使更多内存可用。
memory.min 映射到 requests.memory
为了进行内存保护,除了限制内存使用的原始方法之外,内存 QoS 还会限制接近其内存限制的工作负载,从而确保系统不会被内存使用量的偶发增加所淹没。当您启用 MemoryQoS 功能时,KubeletConfiguration 中会提供一个新字段 memoryThrottlingFactor
。默认情况下设置为 0.9。memory.high
映射到使用 memoryThrottlingFactor
、requests.memory
和 limits.memory
计算的限制值,如以下公式所示,并将该值向下舍入到最接近的页面大小
memory.high 公式
注意
如果未指定容器的内存限制,则将节点可分配内存替换为limits.memory
。总结
文件 | 描述 |
---|---|
memory.max | memory.max 指定容器允许使用的最大内存限制。如果容器中的进程尝试消耗超过配置限制的内存,则内核将使用内存不足 (OOM) 错误终止该进程。它映射到 Pod 清单中指定的容器的内存限制。 |
memory.min | memory.min 指定 cgroups 必须始终保留的最小内存量,即系统永远不应回收的内存。如果没有未受保护的可回收内存可用,则会调用 OOM 杀死。它映射到 Pod 清单中指定的容器的内存请求。 |
memory.high | memory.high 指定内存使用限制。这是控制 cgroup 内存使用的主要机制。如果 cgroup 内存使用量超过此处指定的高边界,则 cgroup 进程将被限制并承受重回收压力。Kubernetes 使用公式来计算 memory.high ,具体取决于容器的内存请求、内存限制或节点可分配内存(如果容器的内存限制为空)以及限制因子。有关该公式的更多详细信息,请参阅 KEP。 |
注意
memory.high
仅在容器级 cgroup 上设置,而 memory.min
在容器、pod 和节点级 cgroup 上设置。cgroups 层级的 memory.min
计算
当发出容器内存请求时,kubelet 在容器创建期间通过 CRI 中的 Unified
字段将 memory.min
传递到后端 CRI 运行时(例如 containerd 或 CRI-O)。对于 pod 中的每个第 i 个容器,容器级 cgroup 中的 memory.min
将设置为
memory.min = pod.spec.containers[i].resources.requests[memory]
由于 memory.min
接口要求设置所有祖先 cgroup 目录,因此 pod 和节点 cgroup 目录需要正确设置。
对于 pod 中的每个第 i 个容器,pod 级 cgroup 中的 memory.min
memory.min = \sum_{i=0}^{no. of pods}pod.spec.containers[i].resources.requests[memory]
对于节点上每个第 i 个 pod 中的每个第 j 个容器,节点级 cgroup 中的 memory.min
memory.min = \sum_{i}^{no. of nodes}\sum_{j}^{no. of pods}pod[i].spec.containers[j].resources.requests[memory]
Kubelet 将使用 libcontainer 库(来自 runc 项目)直接管理 pod 级别和节点级别 cgroup 的 cgroup 层级,而容器 cgroup 限制由容器运行时管理。
支持 Pod QoS 类
根据 Kubernetes v1.22 中 Alpha 功能的用户反馈,一些用户希望在每个 pod 的基础上选择退出 MemoryQoS,以确保没有早期内存限制。因此,在 Kubernetes v1.27 中,内存 QOS 还支持根据 Pod 类的服务质量 (QoS) 设置 memory.high。以下是根据 QOS 类划分的 memory.high 的不同情况
Guaranteed (保证型) Pod 根据其 QoS 定义,要求内存请求 (memory requests) = 内存限制 (memory limits),并且不会超额分配。因此,通过不设置 memory.high,在这些 Pod 上禁用了 MemoryQoS 功能。这确保了 Guaranteed Pod 可以完全使用其内存请求,直至达到设定的限制,并且不会受到任何节流。
Burstable (突发型) Pod 根据其 QoS 定义,要求 Pod 中至少有一个容器设置了 CPU 或内存的请求或限制。
当设置了 requests.memory 和 limits.memory 时,将按原样使用该公式
当设置了 requests 和 limits 时,memory.high 的计算方式
当设置了 requests.memory 但未设置 limits.memory 时,将使用节点可分配内存来替代公式中的 limits.memory
当未设置 requests 和 limits 时,memory.high 的计算方式
BestEffort (尽力而为型) Pod 根据其 QoS 定义,不需要任何内存或 CPU 限制或请求。对于这种情况,Kubernetes 会设置 requests.memory = 0,并在公式中使用节点可分配内存来替代 limits.memory
BestEffort Pod 的 memory.high 计算方式
总结:只有 Burstable 和 BestEffort QoS 类中的 Pod 才会设置 memory.high
。Guaranteed QoS Pod 不会设置 memory.high
,因为它们的内存是有保证的。
如何使用它?
在 Linux 节点上启用内存 QoS 功能的先决条件是:
- 验证是否满足与 cgroups v2 的 Kubernetes 支持相关的要求。
- 确保 CRI 运行时支持内存 QoS。在撰写本文时,只有 containerd 和 CRI-O 提供了与内存 QoS(alpha 版)兼容的支持。这是在以下 PR 中实现的:
内存 QoS 对于 Kubernetes v1.27 仍然是一个 alpha 功能。您可以通过在 kubelet 配置文件中设置 MemoryQoS=true
来启用该功能。
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
MemoryQoS: true
如何参与?
非常感谢所有为该功能的设计、实现和审查做出贡献的贡献者:
- Dixita Narang (ndixita)
- Tim Xu (xiaoxubeii)
- Paco Xu (pacoxu)
- David Porter(bobbypage)
- Mrunal Patel(mrunalp)
对于那些有兴趣参与未来关于内存 QoS 功能讨论的人,您可以通过以下几种方式联系 SIG Node:
- Slack: #sig-node
- 邮件列表
- 开放的社区问题/PR