利用 NUMA 感知内存管理器

特性状态: Kubernetes v1.32 [stable] (默认启用:true)

Kubernetes 内存管理器 实现了在 Guaranteed QoS 类 中为 Pod 保证内存(和巨页)分配的功能。

内存管理器采用提示生成协议来为 Pod 生成最合适的 NUMA 亲和性。内存管理器将这些亲和性提示提供给中央管理器(拓扑管理器)。根据提示和拓扑管理器策略,Pod 被拒绝或被接纳到节点。

此外,内存管理器确保 Pod 请求的内存是从最少数量的 NUMA 节点分配的。

内存管理器仅适用于基于 Linux 的主机。

准备工作

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具已配置为与你的集群通信。建议在至少有两个不是控制平面主机的节点的集群上运行本教程。如果你还没有集群,你可以使用 minikube 创建一个,或者你可以使用这些 Kubernetes 游乐场之一

你的 Kubernetes 服务器必须是 v1.32 或更高版本。

要检查版本,请输入 kubectl version

为了使内存资源与 Pod 规范中请求的其他资源对齐

从 v1.22 开始,内存管理器通过 MemoryManager 特性门控 默认启用。

在 v1.22 之前,必须使用以下标志启动 kubelet

--feature-gates=MemoryManager=true

以启用内存管理器功能。

内存管理器如何运作?

内存管理器目前为 Guaranteed QoS 类中的 Pod 提供有保证的内存(和巨页)分配。要立即启用内存管理器,请遵循 内存管理器配置 部分中的指南,然后,按照 将 Pod 放入 Guaranteed QoS 类 部分的说明准备和部署一个 Guaranteed Pod。

内存管理器是一个提示提供者,它为拓扑管理器提供拓扑提示,然后拓扑管理器根据这些拓扑提示对齐请求的资源。在 Linux 上,它还为 Pod 强制执行 cgroups(即 cpuset.mems)。Pod 准入和部署过程的完整流程图在 内存管理器 KEP:设计概述 和下方展示

Memory Manager in the pod admission and deployment process

在此过程中,内存管理器会更新存储在 节点映射和内存映射 中的内部计数器,以管理有保证的内存分配。

内存管理器在启动和运行时按如下方式更新节点映射。

启动

这发生在节点管理员使用 --reserved-memory保留内存标志 部分)时。在这种情况下,节点映射会更新以反映此保留,如 内存管理器 KEP:启动时的内存映射(带示例) 所示。

配置 Static 策略时,管理员必须提供 --reserved-memory 标志。

运行时

参考 内存管理器 KEP:运行时的内存映射(带示例) 说明了成功的 Pod 部署如何影响节点映射,它还涉及 Kubernetes 或操作系统如何进一步处理潜在的内存不足 (OOM) 情况。

内存管理器操作中一个重要主题是 NUMA 组的管理。每当 Pod 的内存请求超过单个 NUMA 节点容量时,内存管理器会尝试创建一个包含多个 NUMA 节点并具有扩展内存容量的组。这个问题已在 内存管理器 KEP:如何启用跨多个 NUMA 节点的有保证内存分配? 中详细阐述。此外,参考 内存管理器 KEP:模拟 - 内存管理器如何工作?(带示例) 说明了组的管理如何进行。

Windows 支持

功能状态: Kubernetes v1.32 [alpha] (默认禁用)

Windows 支持可以通过 WindowsCPUAndMemoryAffinity 特性门控启用,并且需要容器运行时的支持。Windows 上仅支持 BestEffort 策略

内存管理器配置

其他管理器应首先预配置。接下来,应启用内存管理器功能并使用 Static 策略运行(静态策略 部分)。可选地,可以为系统或 kubelet 进程保留一定量的内存,以提高节点稳定性(保留内存标志 部分)。

策略

内存管理器支持两种策略。你可以通过 kubelet 标志 --memory-manager-policy 选择策略

  • None(默认)
  • Static(仅限 Linux)
  • BestEffort(仅限 Windows)

None 策略

这是默认策略,不以任何方式影响内存分配。它的作用就像内存管理器根本不存在一样。

None 策略返回默认拓扑提示。此特殊提示表示提示提供者(在本例中为内存管理器)对任何资源的 NUMA 亲和性没有偏好。

Static 策略

对于 Guaranteed Pod,Static 内存管理器策略返回与可以保证内存的 NUMA 节点集相关的拓扑提示,并通过更新内部 NodeMap 对象来保留内存。

对于 BestEffortBurstable Pod,Static 内存管理器策略发送回默认拓扑提示,因为没有请求保证内存,并且不在内部 NodeMap 对象中保留内存。

此策略仅在 Linux 上受支持。

BestEffort 策略

功能状态: Kubernetes v1.32 [alpha] (默认禁用)

此策略仅在 Windows 上受支持。

在 Windows 上,NUMA 节点分配的工作方式与 Linux 不同。没有机制可以确保内存访问仅来自特定的 NUMA 节点。相反,Windows 调度程序将根据 CPU 分配选择最优化 NUMA 节点。如果 Windows 调度程序认为最佳,Windows 可能会使用其他 NUMA 节点。

该策略通过内部 NodeMap 跟踪可用和请求的内存量。内存管理器将在分配之前尽力确保 NUMA 节点上有足够的内存可用。
这意味着在大多数情况下,内存分配应按预期运行。

保留内存标志

节点可分配 机制通常由节点管理员用于为 kubelet 或操作系统进程保留 K8S 节点系统资源,以增强节点稳定性。为此目的可以使用一组专用标志来设置节点保留内存的总量。此预配置值随后用于计算可供 Pod 使用的节点“可分配”内存的实际量。

Kubernetes 调度程序包含“可分配”以优化 Pod 调度过程。上述标志包括 --kube-reserved--system-reserved--eviction-threshold。它们的总和将构成保留内存的总量。

内存管理器中添加了一个新的 --reserved-memory 标志,以允许此总保留内存由节点管理员拆分并在多个 NUMA 节点上相应保留。

该标志指定了每 NUMA 节点不同内存类型的逗号分隔内存保留列表。跨多个 NUMA 节点的内存保留可以使用分号作为分隔符指定。此参数仅在内存管理器功能的上下文中有效。内存管理器不会将此保留内存用于容器工作负载的分配。

例如,如果你有一个可用内存为 10Gi 的 NUMA 节点“NUMA0”,并且指定了 --reserved-memory 以在“NUMA0”保留 1Gi 内存,则内存管理器假定只有 9Gi 可用于容器。

你可以省略此参数,但是,你应该注意所有 NUMA 节点的保留内存量应等于 节点可分配功能 指定的内存量。如果至少一个节点可分配参数不为零,则你需要为至少一个 NUMA 节点指定 --reserved-memory。实际上,eviction-hard 阈值默认为 100Mi,因此如果使用 Static 策略,则 --reserved-memory 是强制性的。

此外,避免以下配置

  1. 重复项,即相同的 NUMA 节点或内存类型,但值不同;
  2. 将任何内存类型的限制设置为零;
  3. 机器硬件中不存在的 NUMA 节点 ID;
  4. 内存类型名称不同于 memoryhugepages-<size>(特定 <size> 的巨页也应该存在)。

语法

--reserved-memory N:memory-type1=value1,memory-type2=value2,...

  • N(整数)- NUMA 节点索引,例如 0
  • memory-type(字符串)- 表示内存类型
    • memory - 常规内存
    • hugepages-2Mihugepages-1Gi - 巨页
  • value(字符串)- 保留内存的数量,例如 1Gi

示例用法

--reserved-memory 0:memory=1Gi,hugepages-1Gi=2Gi

或者

--reserved-memory 0:memory=1Gi --reserved-memory 1:memory=2Gi

或者

--reserved-memory '0:memory=1Gi;1:memory=2Gi'

当你为 --reserved-memory 标志指定值时,你必须遵守你之前通过节点可分配功能标志提供的设置。也就是说,对于每种内存类型都必须遵守以下规则

sum(reserved-memory(i)) = kube-reserved + system-reserved + eviction-threshold,

其中 i 是 NUMA 节点的索引。

如果你不遵循上述公式,内存管理器将在启动时显示错误。

换句话说,上面的示例说明,对于常规内存 (type=memory),我们总共保留 3Gi,即

sum(reserved-memory(i)) = reserved-memory(0) + reserved-memory(1) = 1Gi + 2Gi = 3Gi

kubelet 命令行参数与节点可分配配置相关的示例

  • --kube-reserved=cpu=500m,memory=50Mi
  • --system-reserved=cpu=123m,memory=333Mi
  • --eviction-hard=memory.available<500Mi

这是一个正确配置的示例

--kube-reserved=cpu=4,memory=4Gi
--system-reserved=cpu=1,memory=1Gi
--memory-manager-policy=Static
--reserved-memory '0:memory=3Gi;1:memory=2148Mi'

在 Kubernetes 1.32 之前,你还需要添加

--feature-gates=MemoryManager=true

让我们验证上面的配置

  1. kube-reserved + system-reserved + eviction-hard(default) = reserved-memory(0) + reserved-memory(1)
  2. 4GiB + 1GiB + 100MiB = 3GiB + 2148MiB
  3. 5120MiB + 100MiB = 3072MiB + 2148MiB
  4. 5220MiB = 5220MiB(正确)

将 Pod 放入 Guaranteed QoS 类

如果选择的策略不是 None,内存管理器会识别属于 Guaranteed QoS 类的 Pod。内存管理器为每个 Guaranteed Pod 提供特定的拓扑提示给拓扑管理器。对于不属于 Guaranteed QoS 类的 Pod,内存管理器向拓扑管理器提供默认拓扑提示。

以下 Pod 清单摘录将 Pod 分配到 Guaranteed QoS 类。

requests 等于 limits 时,具有整数 CPU 的 Pod 在 Guaranteed QoS 类中运行

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"

同样,当 requests 等于 limits 时,共享 CPU 的 Pod 在 Guaranteed QoS 类中运行。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"

请注意,Pod 必须同时指定 CPU 和内存请求才能将其置于 Guaranteed QoS 类中。

故障排除

可以使用以下方法来排查 Pod 未能部署或在节点上被拒绝的原因

  • Pod 状态 - 指示拓扑亲和性错误
  • 系统日志 - 包含有价值的调试信息,例如有关生成的提示
  • 状态文件 - 内存管理器内部状态的转储(包括 节点映射和内存映射
  • 从 v1.22 开始,设备插件资源 API 可用于检索有关为容器保留的内存的信息

Pod 状态 (TopologyAffinityError)

此错误通常发生在以下情况

  • 节点没有足够的可用资源来满足 Pod 的请求
  • 由于特定的拓扑管理器策略限制,Pod 的请求被拒绝

错误出现在 Pod 的状态中

kubectl get pods
NAME         READY   STATUS                  RESTARTS   AGE
guaranteed   0/1     TopologyAffinityError   0          113s

使用 kubectl describe pod <id>kubectl get events 获取详细错误消息

Warning  TopologyAffinityError  10m   kubelet, dell8  Resources cannot be allocated with Topology locality

系统日志

搜索与特定 Pod 相关的系统日志。

内存管理器为 Pod 生成的提示集可以在日志中找到。此外,CPU 管理器生成的提示集也应该出现在日志中。

拓扑管理器合并这些提示以计算出最佳提示。最佳提示也应该出现在日志中。

最佳提示指示在哪里分配所有资源。拓扑管理器根据其当前策略测试此提示,并根据结果决定是将 Pod 接纳到节点还是拒绝它。

此外,搜索日志中与内存管理器相关的事件,例如,查找有关 cgroupscpuset.mems 更新的信息。

检查节点上的内存管理器状态

让我们首先部署一个示例 Guaranteed Pod,其规范如下

apiVersion: v1
kind: Pod
metadata:
  name: guaranteed
spec:
  containers:
  - name: guaranteed
    image: consumer
    imagePullPolicy: Never
    resources:
      limits:
        cpu: "2"
        memory: 150Gi
      requests:
        cpu: "2"
        memory: 150Gi
    command: ["sleep","infinity"]

接下来,让我们登录到部署它的节点并检查 /var/lib/kubelet/memory_manager_state 中的状态文件

{
   "policyName":"Static",
   "machineState":{
      "0":{
         "numberOfAssignments":1,
         "memoryMap":{
            "hugepages-1Gi":{
               "total":0,
               "systemReserved":0,
               "allocatable":0,
               "reserved":0,
               "free":0
            },
            "memory":{
               "total":134987354112,
               "systemReserved":3221225472,
               "allocatable":131766128640,
               "reserved":131766128640,
               "free":0
            }
         },
         "nodes":[
            0,
            1
         ]
      },
      "1":{
         "numberOfAssignments":1,
         "memoryMap":{
            "hugepages-1Gi":{
               "total":0,
               "systemReserved":0,
               "allocatable":0,
               "reserved":0,
               "free":0
            },
            "memory":{
               "total":135286722560,
               "systemReserved":2252341248,
               "allocatable":133034381312,
               "reserved":29295144960,
               "free":103739236352
            }
         },
         "nodes":[
            0,
            1
         ]
      }
   },
   "entries":{
      "fa9bdd38-6df9-4cf9-aa67-8c4814da37a8":{
         "guaranteed":[
            {
               "numaAffinity":[
                  0,
                  1
               ],
               "type":"memory",
               "size":161061273600
            }
         ]
      }
   },
   "checksum":4142013182
}

从状态文件中可以推断,该 Pod 已固定到两个 NUMA 节点,即

"numaAffinity":[
   0,
   1
],

“固定”一词表示 Pod 的内存消耗受限于(通过 cgroups 配置)这些 NUMA 节点。

这自动意味着内存管理器实例化了一个新组,该组包含这两个 NUMA 节点,即索引为 01 的 NUMA 节点。

请注意,组的管理以相对复杂的方式处理,更多详细说明请参见内存管理器 KEP 的 部分。

为了分析组中可用的内存资源,必须将属于该组的 NUMA 节点中的相应条目相加。

例如,组中可用“常规”内存的总量可以通过将组中每个 NUMA 节点(即 NUMA 节点 0"memory" 部分("free":0)和 NUMA 节点 1"memory" 部分("free":103739236352))的可用空闲内存相加来计算。因此,该组中可用“常规”内存的总量等于 0 + 103739236352 字节。

"systemReserved":3221225472 表示此节点的管理员已使用 --reserved-memory 标志保留了 3221225472 字节(即 3Gi)以在 NUMA 节点 0 上为 kubelet 和系统进程提供服务。

设备插件资源 API

kubelet 提供了一个 PodResourceLister gRPC 服务,以实现资源和关联元数据的发现。通过使用其 List gRPC 端点,可以检索每个容器保留内存的信息,该信息包含在 protobuf ContainerMemory 消息中。此信息只能针对 Guaranteed QoS 类中的 Pod 检索。

下一步

上次修改时间:2024 年 11 月 21 日 下午 7:38 PST:更新 content/en/docs/tasks/administer-cluster/memory-manager.md (0867522df3)