Openstack云平台性能优化·NUMA拓扑

在Openstack中,Libvirt可以自定义虚拟机vCPU/Mem所处的NUMA节点,但是如果flavor配置的vCPU/Mem超过计算节点NUMA容量,则通过 NUMA Topology 使宿主机更好地利用NUMA并提高虚拟机的性能。

配置NUMA拓扑

0x01 查看物理服务器性能

计算节点必须支持NUMA,通过以下命令查看:

dmesg | grep -i numa

若看到“No NUMA configuration found”代表凉凉,若看到类似以下结果则表示支持NUMA:

NUMA: Node 0 [mem 0x00000000-0x0009ffff] + [mem 0x00100000-0x7ffdffff] -> [mem 0x00000000-0x7ffdffff]

通过numactl命令查看NUMA节点及节点上逻辑CPU和RAM的情况:

# yum install -y numactl
numactl --hardware

通过mpstat命令查看各逻辑CPU的使用情况:

# yum install -y sysstat
mpstat -P ALL

查看物理服务器是否支持超线程:

# 查看物理服务器的Socket数量
cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l
# 查看每个Socket的Core数量
cat /proc/cpuinfo | fgrep "cores" | uniq
# 查看每个Socket的Siblings数量
grep "siblings" /proc/cpuinfo|uniq
# 查看Processor的总数
cat /proc/cpuinfo | grep "processor" | wc -l

当 Processors 是 Sockets * Cores 的整数 n(n>1) 倍时,表示服务器支持并开启了超线程。

0x02 配置控制节点上的nova

配置nova-scheduler服务,启用NUMATopologyFilter,修改配置/etc/nova/nova.conf

[DEFAULT]
...
scheduler_default_filters=...,NUMATopologyFilter

0x03 控制节点创建Flavor

通过以下命令创建flavor,也可以在控制面板创建或修改:

openstack flavor create --ram 4096 --disk 50 --vcpus 2 NUMA_2C4M50D

0x04 配置控制节点nova的NUMA策略

通过Flavor extra-specs设置Guest NUMA topology,尽量将虚拟机的vCPU和内存分配在同一个NUMA上,避免跨节点访问内存。

openstack flavor set FLAVOR-NAME \
--property hw:numa_nodes=FLAVOR-NODES \
--property hw:numa_cpus.N=FLAVOR-CORES \
--property hw:numa_mem.N=FLAVOR-MEMORY
参数值 说明
FLAVOR-NODES Guest NUMA nodes 数量。若不指定,则 Guest vCPUs 在任意可用的 Host NUMA nodes 上浮动。
N Guest NUMA nodes ID。Guest NUMA nodes 的索引ID,并非 Host NUMA node 的 ID。
FLAVOR-CORES 分配给 Guest NUMA node N 的vCPUs列表。如果不指定,则“自动设定 Guest NUMA Topology”。
FLAVOR-MEMORY 分配给 Guest NUMA node N 的 Memory Size,单位MB。如果不指定,则“自动设定 Guest NUMA Topology”。

自动设定 Guest NUMA Topology:

仅指定 Guest NUMA nodes 的个数,Nova会根据Flavor设定的虚拟机规模将vCPU/Mem平均分布到不同的 Host NUMA nodes 上(默认从0开始分配)。

建议同时通过 hw:numa_mempolicy 参数指定NUMA的内存访问策略,参数值为strict(仅访问本地内存)和preferred(宽松可越界)。尤其对一些特定工作负载的NUMA架构问题,比如MySQL的“swap insanity”问题 ,或许preferred会是一个不错的选择。

手动设定 Guest NUMA Topology:

Nova Scheduler 根据参数 hw:numa_nodes 来决定如何映射 Guest NUMA nodes。即使没有设置该参数,Scheduler也会自主决定如何运行虚拟机,无需关心单个NUMA节点能否满足虚拟机flavor中的vCPU和内存的配置。

  • numa_nodes = 1,Scheduler将选出NUMA节点能够满足虚拟机flavor配置的计算节点。
  • numa_nodes > 1,Scheduler将选出NUMA节点数量以及NUMA节点中资源情况能够满足虚拟机flavor配置的计算节点。

注意:当 hw:numa_cpus.N 和 hw:numa_mem.N 的值大于虚拟机本身可用的CPUs和内存时,将触发异常。

举一个正常的例子:

openstack flavor set aze-FLAVOR \
--property hw:numa_nodes=2 \
--property hw:numa_cpus.0=0,1 \
--property hw:numa_mem.0=1024 \
--property hw:numa_cpus.1=2,3 \
--property hw:numa_mem.1=2048 \

另外,也可以通过Image Metadata来设定:

glance image-update --property \
hw_numa_nodes=2 \
hw_numa_cpus.0=0,1 \
hw_numa_mem.0=1024 \
hw_numa_cpus.1=2,3 \
hw_numa_mem.1=2048 \
IMAGE-NAME

注意:当镜像的NUMA约束与Flavor的NUMA约束有冲突时,以Flavor为准。

0x05 修改CPU绑定策略

openstack flavor set NUMA_2C4M50D \
--property hw:cpu_policy=dedicated \
--property hw:cpu_thread_policy=isolate

相关知识请阅读上一篇文章Openstack云平台扩展课程·CPU绑核

附加知识

NUMA

NUMA(Non-Uniform Memory Access),即非一致性存储器访问,是将处理器和存储器划分到不同的节点(NUMA节点),这些节点拥有几乎相等的资源,NUMA节点内部通过自己的存储总线访问内部的本地内存,且所有NUMA节点均可以通过主板上的共享总线来访问其他节点的远程内存。

NUMA Topology

现在的服务器基本都支持NUMA拓扑。

NUMA具有的高存储访问带宽、有效的Cache效率以及灵活的 PCIe I/O 设备的布局设计。但由于NUMA跨节点远程内存访问不仅延时高、带宽低、消耗大,还可能需要处理数据一致性的问题,因此,虚拟机的vCPU和内存在NUMA节点上的错误布局,将会导宿主机资源的严重浪费,这将抹掉任何内存与CPU决策所带来的好处。

标准策略是尽量将一个虚拟机完全限制在单个NUMA节点内。

Guest NUMA Topology

将虚拟机的vCPU/Mem完全限制在单个NUMA节点内是最佳的方案,但假如分配给虚拟机的vCPU数量以及内存大小超过了一个NUMA节点所拥有的资源呢?此时必须针对大资源需求的虚拟机设计出合适的策略,Guest NUMA Topology 的概念也是为此而提出。

这些策略或许禁止创建超出单一NUMA节点拓扑的虚拟机,或许允许虚拟机跨多NUMA节点运行,并且在虚拟机迁移时,允许更改这些策略。也就是说,在维护计算节点进行时,接收临时降低性能而选择次优的NUMA拓扑布局。当然了,NUMA拓扑布局的问题还需要考虑到虚拟机的具体使用场景,例如,NFV 虚拟机的部署就会强制的要求严格的NUMA拓扑布局。

如果虚拟机具有多个 Guest NUMA Node,为了让操作系统能最大化利用其分配到的资源,宿主机的NUMA拓扑就必须暴露给虚拟机,让虚拟机的 Guest NUMA Node 与宿主机的 Host NUMA Node 进行关联映射,这样可以映射大块的虚拟机内存到宿主机内存,和设置vCPU与pCPU的映射。

Guest NUMA Topology 实际上是将一个大资源需求的虚拟机划分为多个小资源需求的虚拟机,将多个 Guest NUMA Node 分别绑定到不同的 Host NUMA Node。这样做是因为虚拟机内部运行的工作负载同样会遵守 NUMA 节点原则,最终的效果实际上就是虚拟机的工作负载依旧有效的被限制在了一个 Host NUMA Node 内。

也就是说,如果虚拟机有 4 vCPU 需要跨两个 Host NUMA Node,vCPU 0/1 绑定到 Host NUMA Node 1,而 vCPU 2/3 绑定到 Host NUMA Node 2 上,然后虚拟机内的 DB 应用分配到 vCPU 0/1,Web 应用分配到 vCPU 2/3,这样实际就是 DB 应用和 Web 应用的线程始终被限制在了同一个 Host NUMA Node 上。

但是,Guest NUMA Topology 并不强制将vCPU与对应的 Host NUMA Node 中特定的pCPU进行绑定,这可以由操作系统调度器来隐式完成,只是如果宿主机开启了超线程,则要求将超线程特性暴露给虚拟机,并在 NUMA Node 内绑定vCPU与pCPU的关系,否则,vCPU会被分配给 Siblings Thread,由于超线程竞争,性能远不如将vCPU分配到Socket或Core的好。

获取物理机NUMA拓扑的脚本

#!/bin/bash

function get_nr_processor()
{
  grep '^processor' /proc/cpuinfo | wc -l
}

function get_nr_socket()
{
  grep 'physical id' /proc/cpuinfo | awk -F: '{
  print $2 | "sort -un"}' | wc -l
}

function get_nr_siblings()
{
  grep 'siblings' /proc/cpuinfo | awk -F: '{
  print $2 | "sort -un"}'
}

function get_nr_cores_of_socket()
{
  grep 'cpu cores' /proc/cpuinfo | awk -F: '{
  print $2 | "sort -un"}'
}

echo '===== CPU Topology Table ====='
echo

echo '+--------------+---------+-----------+'
echo '| Processor ID | Core ID | Socket ID |'
echo '+--------------+---------+-----------+'

while read line; do
if [ -z "$line" ]; then
  printf '| %-12s | %-7s | %-9s |\n' $p_id $c_id $s_id
  echo '+--------------+---------+-----------+'
  continue
fi

if echo "$line" | grep -q "^processor"; then
  p_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi

if echo "$line" | grep -q "^core id"; then
  c_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi

if echo "$line" | grep -q "^physical id"; then
  s_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi
done < /proc/cpuinfo

echo

awk -F: '{
if ($1 ~ /processor/) {
  gsub(/ /,"",$2);
  p_id=$2;
} else if ($1 ~ /physical id/)
  {
    gsub(/ /,"",$2);
    s_id=$2;
    arr[s_id]=arr[s_id] " " p_id
  }
}

END{
for (i in arr)
  printf "Socket %s:%s\n", i, arr[i];
}' /proc/cpuinfo

echo
echo '===== CPU Info Summary ====='
echo

nr_processor=`get_nr_processor`
echo "Logical processors: $nr_processor"

nr_socket=`get_nr_socket`
echo "Physical socket: $nr_socket"

nr_siblings=`get_nr_siblings`
echo "Siblings in one socket: $nr_siblings"

nr_cores=`get_nr_cores_of_socket`
echo "Cores in one socket: $nr_cores"

let nr_cores*=nr_socket
echo "Cores in total: $nr_cores"

if [ "$nr_cores" = "$nr_processor" ]; then
  echo "Hyper-Threading: off"
else
  echo "Hyper-Threading: on"
fi

echo
echo '===== END ====='

原创文章禁止转载:技术学堂 » Openstack云平台性能优化·NUMA拓扑

精彩评论

3+8=

感谢您的支持与鼓励

支付宝扫一扫打赏

微信扫一扫打赏