Kubelet Memory Manager
摘要:Memory Manager 是 kubernetes 1.21 新增的参与拓扑对齐的设备管理器。Memory Manager 模块的对外接口和 CPU Manager 和 Device Manage 基本相同。因此 Memory Manager 在调用和使用上和 CPU Manger、Device Manger 是保持一致的。
什么是 NUMA?
现在计算机内存的使用方式都是 SMP(Shared Memory MulptiProcessors,共享存储型多处理机模型)。SMP 有三种模型:UMA(Uniform Memory Access,统一内存访问)、NUMA(Nonuniform Memory Access,非一致性内存分访问)和 COMA(Cache Only Memory Architectur,只用告诉缓存的存储器结构)。现在我们最常听说的就是 NUMA 的方式了。
那么 NUMA 是怎么来的呢?在 NUMA 之前计算机使用的都是 UMA 的方式。
uma 模型
在 UMA 模型中,所有 CPU 对内存的访问都要通过 BUS,这样的好处是:物理内存可以被所有的 CPU 均匀共享,所有 CPU 对内存的访问时间延迟都是相同的。缺点是:所有访问都要走 BUS,BUS 成为日后系统扩展的瓶颈,CPU 越多,CPU 对于内存的访问性能损耗就会越高。
随着现代计算机的发展,CPU 越来越多。UMA 模型的弊端日益显露。为了解决这些问题,开始有了 NUMA 模型。
numa 模型
在 NUMA 模型中,把 CPU 和临近的内存做成一个 node,在 node 内,CPU 可以通过集成内存控制器(IMC,类似 BUS)访问内存。跨 node 的内存访问则需要通过 QPI(Quick Path Interconnect)。QPI 的延迟要高于 IMC,由此 CPU 对于内存的反问有了远近之别。
我们是如何使用 NUMA?
有用过 NUMA 的同学都知道,绑 numa 的过程就是将进程固定到几个 CPU thread 上运行。为什么,我们固定了进程运行的 CPU thread,就可以显著提升程序的性能。
这个问题的答案就藏在 NUMA 架构下,内存的分配方式上。在 NUMA 架构下,内存分配方式有四种模式:
- –localalloc: 从当前 node 的内存中分配,如果当前 node 分配不出来了,才会从其他 node 上分配。local alloc 也是默认的内存分配策略。
- –preferred=node: 优先从指定的 node 上分配内存,如果指定 node 分配不出来了,也可以从其他 node 上分配。
- –membind=nodes: 只能从指定的一个或多个 node 上分配内存,如果指定的 node 分配不出来,会触发进程 OOM。
- –interleaved=all: 使用轮询算法轮流的从不同的 node 上分配内存。
假设我们现在有一个 NUMA 架构的计算机,其 CPU 架构如下所示:
numa 模型
当进程不指定 NUMA 节点运行时,那么在某个时间片,进程的线程有可能运行在 CPU0-3 上的任意一个 thread 上的。进程在 CPU0/CPU2 上运行时,开辟的内存就会在 node0 上获取。进程在 CPU1/CPU3 上运行时,开辟的内存就会在 node1 上获取。由此进程的内存会算乱的分布在多个 NUMA 节点上。这样带来的一个问题时,进程在运行时,有可能出现大量的跨 NUMA 节点的内存访问。
当进程启动时,使用 numactrl –bindcpu=0 运行,则进程只会运行在 CPU0/CPU2 上。进程分配的内存大部分都会在 node0 上。这样就减少了跨 NUMA 节点的内存访问。
上面我们也提到过,通过 QPI 的内存延迟要远远高于 IMC,公开的数据显示 QPI 的访问延迟是 IMC 的三倍。
Memory Manger 分析
使用方式
-
kubelet 启动时必须为系统预留内存:–reserved-memory={numa-node=0, type=memory, limit=500MB}, {numa-node=1,type=memory, limit=100MB}
-
必须显示开启 Memory Manager 的特性门:–feature-gates=MemoryManager=true
-
通过–memory-manager-policy 参数可以指定 Memory Manger 的策略,目前支持 none 和 static 策略。
- none 策略: memory manager 不提供 topology hints。
- static 策略: memory manger 将会为 Guaranteed Pod 提供 topology hints。
原理介绍
这里将会将社区的 proposal 的原理部分做介绍。大家如果有兴趣也可以研读社区 KEP-1769: Memory Manager
memory-manager
- kubelet 对 pod 做准入检查时,会调用 Topology Manager 的 Admit()方法。
- Topology Manager 之后会调用 MemoryManager 的 GetTopologyHints 方法,获取 MemoryManager 的拓扑建议。
- Topology Manger 综合 MemoryManger、CPUManager、DeviceManager 的 hints,得到 besthint。
- Topology Manger 获取 besthint 后,会调用 MemoryManger、CPUManager、DeviceManager 的 Allocate 方法,使各模块为容器分配资源。
- 当容器启动时,containerManager 会在 PreCreateContainer 时,通过 MemoryManger 的 GetMemoryNUMANodes 方法获取用于分配容器内存的 NUMA 节点。更新到容器的 cgroup config 中。之后容器的内存就只能在指定的 numa 节点上分配。
假设现在我们有 3 个 numa node,每个 node 是 11G 的内存。kubelet 通过 --reserved-memory={numa-node=0, type=memory, limit=1G}, {numa-node=1,type=memory, limit=1G}参数,在每个 node 上预留 1G 的内存。
Step 1: 当 kubelet 启动后,memory manger 的 memory maps 的结果如下所示。
memory maps 记录节点上每种内存(memory/hugepages)的分配情况。
memory manager startup
Step 2: 我们在该节点上启动一个 Guaranteed Pod,Pod A 共申请 15G 内存。此时,由于单 NUMA node 最多只能分配除 10G 内存,为了能够给 Guaranteed Pod 分配出 15G 内存,memory manage 会讲 node0 和 node1 组成一个 NUMA Group。
memory manager 15G Pod
memory manager 要求 numa group 是正交的。如果一个 node 是某个 group 的成员,则该 node 不能是其他的 group 的成员;某个 node 没有和其他 node 组成 group,则该 node 也可认为是一个单节点的 group。
Step 3: 当我们在节点上在启动一个 5G 内存的 Guaranteed Pod B,此时 memory Manager 会建议优先在 node 3 上分配内存。
memory manager 15+5G Pod
Step 4: 当我们在节点上在启动一个 limit 5G 内存的 Besteffort Pod C,该 pod 就会利用 node 1 上剩下的 5G 的内存。其实在 Besteffort Pod 启动的时候 memory manager 其实是不工作的,即他不提供 topology hints。我们只是认为所有的非 Guaranteed Pod 都运行在。memory manager 没有分配出去的内存碎片上。
memory manager 15+5+5G Pod
Step 4: Pod A 从节点上删除后,node 0 和 node 1 组成的 NUMA Group 就会解散。变成各自独立的 NUMA node 参与内存分配。
memory manager 5+5G Pod
参考资料
-
No backlinks found.