ack-koordinator以数据安全的方式将绑核应用远端NUMA上的内存迁移至本地,提高本地访存命中率,为内存密集型的工作负载提供更好的访存性能。本文介绍如何使用内存就近访问加速功能,并验证其对密集型应用性能的提升。
索引
前提条件
已创建ACK Pro集群,且集群版本为1.18及以上版本。具体操作,请参见创建Kubernetes托管版集群。
已通过kubectl连接Kubernetes集群。具体操作,请参见获取集群KubeConfig并通过kubectl工具连接集群。
已安装ack-koordinator组件(原ack-slo-manager),且组件版本为v1.2.0-ack1.2及以上版本。关于安装ack-koordinator组件的具体操作,请参见ack-koordinator(ack-slo-manager)。
说明ack-koordinator适配了原resource-controller组件的所有功能。如果您正在使用resource-controller,请您先卸载resource-controller,再安装ack-koordinator。关于卸载组件的具体操作,请参见卸载resource-controller。
已确保多NUMA机型为神龙裸金属ecs.ebmc、ecs.ebmg、ecs.ebmgn、ecs.ebmr、ecs.ebmhfc、ecs.scc等五、 六、七、八代机型。
说明内存就近访问加速功能尤其对ecs.ebmc8i.48xlarge、ecs.c8i.32xlarge、ecs.g8i.48xlarge的八代机型提供更好的支持。关于ECS实例规格族,请参见ECS实例规格。
已确保应用运行在多NUMA机型上,且已通过CPU拓扑感知调度等功能实现绑核。更多信息,请参见CPU拓扑感知调度。
费用说明
ack-koordinator组件本身的安装和使用是免费的,不过需要注意的是,在以下场景中可能产生额外的费用:
ack-koordinator是非托管组件,安装后将占用Worker节点资源。您可以在安装组件时配置各模块的资源申请量。
ack-koordinator默认会将资源画像、精细化调度等功能的监控指标以Prometheus的格式对外透出。若您配置组件时开启了ACK-Koordinator开启Prometheus监控指标选项并使用了阿里云Prometheus服务,这些指标将被视为自定义指标并产生相应费用。具体费用取决于您的集群规模和应用数量等因素。建议您在启用此功能前,仔细阅读阿里云Prometheus计费说明,了解自定义指标的免费额度和收费策略。您可以通过账单和用量查询,监控和管理您的资源使用情况。
内存就近访问加速功能的优势
多个非一致性内存访问NUMA(Non-uniform memory access)架构下,当内存与CPU不在同一个NUMA时, 进程在跨NUMA读取远端内存时需要经过QPI总线,相对于内存与CPU在相同NUMA的本地内存访问场景,跨NUMA场景访存耗时更多。远端内存的分布会降低应用的本地访存命中率,影响内存密集型容器应用访存性能。
以内存密集型应用Redis为例,Redis对所有CPU亲和时,进程在运行过程中可能发生NUMA的切换。此时,以多NUMA机型ecs.ebmc8i.48xlarge为例,部分内存分布在远端NUMA上,内存分布如下方左图所示。redis-server的内存全本地分布相对于内存随机分布和全远端分布的场景,在压测中,QPS性能分别提升10.52%和26.12%,如下方右图所示。
本文中提供的测试数据仅为理论值(参考值),实际数据以您的操作环境为准。
为提高访存性能,ack-koordinator提供容器内存就近访问加速功能。对基于CPU拓扑感知调度等功能实现绑核的应用,在确保数据安全的前提下尽可能地将远端内存迁移至所在NUMA,且迁移过程中无需中断业务。
使用场景
内存就近访问加速功能的使用场景如下。
工作负载为内存密集型,例如大型内存数据库Redis。
运行在配置英特尔®DSA(Data Streaming Accelerator)数据流加速器硬件的机器上,DSA可以提升内存就近访问加速功能的速度,并降低CPU消耗。
使用内存就近访问加速功能
步骤一:通过Policy开启内存就近访问加速功能
您可以通过以下两种方式开启内存就近访问加速功能。
通过配置
metadata.annotations/koordinator.sh/memoryLocality
,为单个Pod开启内存就近访问加速功能。具体操作,请参见下文为单个Pod开启内存就近访问加速。通过配置
configmap/ack-slo-config
的指定QoS的memoryLocality
,为指定QoS下所有Pod开启内存就近访问加速功能。具体操作,请参见下文为指定QoS下所有Pod开启内存就近访问加速。
为单个Pod开启内存就近访问加速功能
使用以下命令,为已绑核的Redis Pod开启内存就近访问加速功能。
kubectl annotate pod <pod-name> koordinator.sh/memoryLocality='{"policy": "bestEffort"}' --overwrite
policy
参数取值说明:
bestEffort
:立即执行单次内存就近访问加速。none
:关闭内存就近访问加速功能。
为指定QoS下所有Pod开启内存就近访问加速功能
使用以下内容,创建ml-config.yaml文件。
apiVersion: v1 data: resource-qos-config: |- { "clusterStrategy": { "lsClass": { "memoryLocality": { "policy": "bestEffort" } } } } kind: ConfigMap metadata: name: ack-slo-config namespace: kube-system
执行以下命令,检查kube-system命名空间下,是否存在
configmap/ack-slo-config
。为避免影响原有的QoS配置,需要进行
ack-slo-config
的检查操作。kubectl get cm ack-slo-config -n kube-system
若
configmap/ack-slo-config
不存在,则使用以下命令创建。kubectl apply -f ml-config.yaml
若
configmap/ack-slo-config
已存在,则使用以下命令,配置内存就近访问加速。kubectl patch cm -n kube-system ack-slo-config --patch "$(cat ml-config.yaml)"
步骤二:通过Event查看内存就近访问加速结果
使用以下命令,查看内存就近访问加速的结果。
kubectl describe pod <pod-name>
预期输出1:
Normal MemoryLocalityCompleted 6s koordlet Container <container-name-1> completed: migrated memory from remote numa: 1 to local numa: 0 Container <container-name-2> completed: migrated memory from remote numa: 0 to local numa: 1 Total: 2 container(s) completed, 0 failed, 0 skipped; cur local memory ratio 100, rest remote memory pages 0
预期输出1
completed
表明,内存就近访问加速成功。Event记录各个容器内存的迁移方向及本地化后Pod的总内存页分布比例。预期输出2:
Normal MemoryLocalityCompleted 6s koordlet Container <container-name-1> completed: migrated memory from remote numa: 1 to local numa: 0 Container <container-name-2> completed: failed to migrate the following processes: failed pid: 111111, error: No such process,from remote numa: 0 to local numa: 1 Total: 2 container(s) completed, 0 failed, 0 skipped; cur local memory ratio 100, rest remote memory pages 0
预期输出2
completed
表明,部分内存就近访问加速失败,例如迁移过程中,进程终止。Event记录失败进程号。预期输出3:
Normal MemoryLocalitySkipped 1s koordlet no target numa
预期输出3表明,没有远端NUMA时(例如机型为非NUMA架构或进程分布在所有NUMA时),Event告知内存就近访问加速已跳过。
预期输出4:
其他非预期错误,Pod报出MemoryLocalityFailed的Event,并提示Message错误信息。
(可选)步骤三:开启多次内存就近访问加速
若已进行过单次内存就近访问加速,开启多次内存访问加速前,请确认前一次加速已完成。
多次内存就近访问加速时,只有当内存就近访问加速的结果发生改变,或本地化后内存页分布比例发生超出10%的改变时,加速的结果才会记录在Event中。
执行以下命令,开启多次内存就近访问加速。
kubectl annotate pod <pod-name> koordinator.sh/memoryLocality='{"policy": "bestEffort","migrateIntervalMinutes":10}' --overwrite
migrateIntervalMinutes
为两次内存就近访问加速的最小执行间隔,单位为分钟。参数取值说明:
0
:立即执行单次内存就近访问加速。> 0
:立即执行内存就近访问加速。none
:关闭内存就近访问功能。
验证内存就近访问加速功能对应用性能的提升
测试环境
一台多NUMA架构的物理机或虚拟机:用于部署内存密集型应用的测试机,本文测试选用ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge机型。
一台能访问测试机的压测机:用于进行服务压测。
测试的应用:4 Core 32 GB的多线程Redis。
说明若选用的测试机型规格较小,可改用单线程Redis并降低CPU和内存规格。
测试流程测试步骤
使用以下YAML内容,创建并部署Redis应用。
以下代码中
redis-config
字段的内容,请根据测试机型规格修改配置。--- kind: ConfigMap apiVersion: v1 metadata: name: example-redis-config data: redis-config: | maxmemory 32G maxmemory-policy allkeys-lru io-threads-do-reads yes io-threads 4 --- kind: Service apiVersion: v1 metadata: name: redis spec: type: NodePort ports: - port: 6379 targetPort: 6379 nodePort: 32379 selector: app: redis --- apiVersion: v1 kind: Pod metadata: name: redis annotations: cpuset-scheduler: "true" # 通过CPU拓扑感知调度实现绑核。 labels: app: redis spec: containers: - name: redis image: redis:6.0.5 command: ["bash", "-c", "redis-server /redis-master/redis.conf"] ports: - containerPort: 6379 resources: limits: cpu: "4" # 根据测试机型规格修改配置。 volumeMounts: - mountPath: /redis-master-data name: data - mountPath: /redis-master name: config volumes: - name: data emptyDir: {} - name: config configMap: name: example-redis-config items: - key: redis-config path: redis.conf
模拟内存增压。
往Redis中写入业务数据。
参考以下Shell脚本,通过管道方式向redis-server批量写入数据。在以下脚本中,
<max-out-cir>
和<max-out-cir>
用来控制写入的数据量。本文测试用例在4 Core 32 GB的Redis应用中写入数据,本次参数设置为max-our-cir=300
、max-in-cir=1000000
。说明数据量较大时,数据写入时间较长。为提高写入速度,建议在测试机本地进行数据写入。
for((j=1;j<=<max-out-cir>;j++)) do echo "set k$j-0 v$j-0" > data.txt for((i=1;i<=<max-in-cir>;i++)) do echo "set k$j-$i v$j-$i" >> data.txt done echo "$j" unix2dos data.txt # pipe方式需要dos格式文件。 cat data.txt | redis-cli --pipe -h <redis-server-IP> -p <redis-server-port> done
在写入过程中,对Redis所在的NUMA部署高内存占用的混部业务(例如FFmpeg)进行内存增压,模拟Redis在运行过程中,因内存压力突发导致内存漂移到其他NUMA的现象。
在测试机上,可使用
numactl --membind
命令,限制部署业务内存所在的NUMA。使用以下stress命令进行增压。
其中
<workers-num>
用于分配内存的进程数,<malloc-size-per-workers>
为每个进程分配的内存大小。说明由于Redis处于绑核状态,在本地内存未达到100%时,内存会优先写入到本地。请根据节点当前负载情况设置
workers-num
及malloc-size-per-workers
参数。各NUMA内存负载情况可通过numactl -H
命令查询。numactl --membind=<numa-id> stress -m <workers-num> --vm-bytes <malloc-size-per-workers>
在内存就近访问加速前后进行redis-benchmark压测并对比。
内存就近访问加速前压测
数据写入完成后,在压测机上使用redis-benchmark对测试机redis-server进行适中压力及高压力压测。
执行以下适中压力压测命令,并发数为500。
redis-benchmark -t GET -c 500 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
执行以下高压力压测命令,并发数为10000。
redis-benchmark -t GET -c 10000 -d 4096 -n 2000000 -h <redis-server-IP> -p <redis-server-port>
为Redis开启内存加速访问功能
执行以下命令,为Redis进行内存就近访问加速。
kubectl annotate pod redis koordinator.sh/memoryLocality='{"policy": "BestEffort"}' --overwrite
执行以下命令,查看内存就近访问加速结果。
若远端内存量较大,内存就近访问加速需要一定时间。
kubectl describe pod redis
内存就近访问加速完成后,预期输出:
Normal MemoryLocalitySuccess 0s koordlet-resmanager migrated memory from remote numa: 0 to local numa: 1, cur local memory ratio 98, rest remote memory pages 28586
内存就近访问加速后压测
重复执行中压力和高压力压测命令,进行内存就近访问加速后压测。
结果分析
本文测试分别采用ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge机型的测试机,对内存就近访问加速前后进行多次压测取平均值,数据对比如下。
采用不同的工具进行测试,测试结果会存在差异。本文中的测试数据仅为ecs.ebmc8i.48xlarge和ecs.ebmg7a.64xlarge机型的测试机获得的测试结果。
结果说明:在一定内存压力下,应用的数据写入到远端内存,本地内存占比影响了访存性能。在绑核基础上进行内存就近访问加速后,在两种压测场景下的Redis时延和吞吐指标都得到一定的改善。