作者:乔普、申信

介绍

在云原生环境中,集群提供者常常将不同类型的工作负载部署在同一个集群中,利用不同业务的不同峰值效果,实现资源分时复用,避免资源浪费。然而,不同类型负载之间混合部署常常会导致资源竞争和相互干扰。最为典型的场景便是在线和离线负载的混合部署。当离线较多的占用计算资源时,在线负载的响应时间就会受到影响;当在线长时间较多的占用计算资源时,离线负载的任务完成时间不能得到保证。这种现象属于 Noisy Neighbor 问题。

根据混合部署的程度、资源类型的不同,解决该问题有许多不同的思路。Quota 管理可从整个集群维度限制负载的资源使用量,Koordinator 在这方面提供了多层次弹性 Quota 管理功能 [ 1] 。单机维度上看,CPU、内存、磁盘 IO,网络资源都有可能被不同负载共享。Koordinator 在 CPU、内存上已经提供了一些资源隔离和保障的能力,磁盘 IO 和网络资源方面的相关能力正在建设中。

本文主要介绍当不同类型工作负载混合部署在同一个节点上时,Koordinator 如何帮助负载之间(在线和在线、在线和离线)协同地共享 CPU 资源。

问题描述

CPU 资源 Noisy Neighbor 的本质是不同的负载之间无协同地共享 CPU 资源。

  1. Kubernetes 默认的资源模型利用 cgroup(cfs quota) 从 CPU 时间使用量上来限制不同负载对于 CPU 资源的访问。这种情况下,一些负载就可能会被操作系统调度器切换所在的 CPU 核。由于不同 CPU 核对不同物理位置的内存访问时间不同,切换大概率会导致更长的内存访问时间,从而影响负载性能。
  2. 在 NUMA 架构中,SMT 线程(逻辑核)共享物理核的执行单元和 L2 缓存。当同一个物理核中有多种工作负载时,不同工作负载间就会产生资源争抢,导致负载性能下降。

Kubernetes 在单机侧提供了拓扑管理器和 CPU 管理器来尝试解决上述问题。然而,该功能只有在 Pod 已经调度到机器上之后才会尝试生效。这样就有可能导致 Pod 会被调度到 CPU 资源满足但是 CPU 拓扑不满足负载要求的情况。

解决方案

面向应用的 CPU 编排 QoS 语义

针对上述问题和不足,Koordinator 设计了面向应用的 QoS 语义和 CPU 编排协议,如下图所示。

 title=

LS(Latency Sensitive)应用于典型的微服务负载,Koordinator 将其与其它的延迟敏感型负载隔离保障其性能。LSR(Latency Sensitive Reserved)类似于 Kubernetes 的 Guaranteed,在 LS 的基础上增加了应用要求预留绑核的语义。LSE(Latency Sensitive Exclusive)则常见于中间件等对 CPU 特别敏感的应用,Koordinator 除了满足其类似于 LSR 要求绑核的语义外,还确保其所被分配的 CPU 不与任何其它负载共享。

另外,为提高资源利用率,BE 负载可与 LSR 和 LS 共享CPU。为了确保与 BE 共享的延迟敏感型应用不受其干扰,Koordinator 提供了如干扰检测、BE 压制等策略。本文重点不在此,读者可关注后续文章。

丰富的 CPU 编排策略

对于 LSE 类型的应用,当机器是超线程架构时,只能保证负载独占逻辑核。这样当同一个物理核中有其它负载时,应用性能仍会受干扰。为此,Koordinator 支持用户在 Pod Annotation 上配置丰富的 CPU 编排策略来提高性能。

CPU 编排策略分为 CPU 绑定策略和 CPU 独占策略。CPU 绑定策略决定应用所被分配逻辑核在物理核间的分布,可采用物理核间打散或者堆叠。堆叠(FullPCPU)的方式指为应用分配完整的物理内核,可以有效地缓解 Noisy Neighbor 问题。打散(SpreadByPCPU)则主要应用于一些具有多种不同峰谷特性的延迟敏感型应用,可以让应用程序在特定时间充分使用 CPU。CPU 独占策略决定应用所被分配逻辑核的独占级别,可尽量避开已经同独占策略申请的物理核或 NUMANode。

增强的 CPU 调度能力

Koordinator 支持配置 NUMA 的分配策略,决定在调度时如何选择满意的 NUMA 节点。MostAllocated 表示从可用资源最少的 NUMA 节点分配,可以尽可能减少碎片,为后续的负载留下更大的分配空间。但是,这种方式可能会导致依赖 Barrier 的并行代码性能收到影响。DistributeEvenly 表示在 NUMA 节点上平均分配 CPU,可以提高上述并行代码的性能。LeastAllocated 表示从可用资源最多的 NUMA 节点分配。

另外,Koordinator 对 CPU 的分配逻辑是在中心调度器完成的。这样就会有一个全局的视角,避免了 Kubernetes 单机方案可能导致的 CPU 资源量满足但是拓扑不满足的窘境。

最佳实践

由上文可知,Koordinator 精细化 CPU 编排能力能够显著提高多应用混合部署场景下 CPU 敏感型工作负载的性能。为了让读者能够更清楚地使用和直观感受 Koordinator 的精细化 CPU 编排能力,本文将在线应用采用不同方式部署到集群中,观察压测中服务的延迟,来判断 CPU 编排能力的效果。

本文会在同一个机器上部署多个在线应用,压测 10 分钟,以充分模拟生产实践中可能出现的 CPU 核切换场景。对于在线应用和离线应用混合部署的情况,Koordinator 提供了如干扰检测、BE 压制等策略。本文重点不在此,读者可关注后续文章中的实践。

 title=

本次实验采用以下指标,评估应用不同部署方式下 Nginx 应用的性能表现:

  • 响应时间 RT(Response Time)分位值RT 是在线应用通常关注的性能指标,RT 越低代表在线服务性能越好。RT 指标通过收集 wrk 压测结束后打印的信息获得,在实验中反映了 Nginx 应用响应 wrk 请求所花费的时间。例如 RT-p50 表示 Nginx 响应前 50% wrk 请求最大所花费的时间(中位数),RT-p90 表示 Nginx 响应前 90% wrk 请求最大所花费的时间。
  • 每秒请求数 RPS(Request Per Second)RPS 是在线应用每秒服务的请求数量,服务承受的 RPS 越多代表在线服务的性能越好。

实验结果如下:

 title=

  • 对比 B 和 A,可以发现采用 LSE QoS 绑核之后,服务响应时间 P99 明显减小,很好地减轻了长尾现象
  • 对比 C 和 B,可以发现采用 LSR QoS 绑核且允许逻辑核占用更多物理核资源之后,在服务响应时间更好的情况下可以承受更多的请求

综上,在线服务部署在同一机器的场景下,采用 koordinator 精细化 CPU 编排能够有效抑制 Noisy Neighbor 问题,减少 CPU 核切换带来的性能下降。

环境

首先,要先准备一个 Kubernetes 集群并安装 Koordinator [ 2] 。本文选择一个 Kubernetes 集群的两个节点来做实验,其中一个节点作为测试机,将运行 Nginx 在线服务器;另一节点作为压测机,将运行客户端的 wrk,向 Nginx 请求 Web 服务,制造压测请求。

在线应用

  1. 使用 ColocationProfile [ 3] 为应用注入精细化 CPU 编排协议

B 组精细化 CPU 编排协议:

apiVersion: config.koordinator.sh/v1alpha1
kind: ClusterColocationProfile
metadata:
  name: colocation-profile-example
spec:
  selector:
    matchLabels:
      app: nginx
  # 采用 LSE QoS
  qosClass: LSE
  annotations:
  # 采用物理核间堆叠
    scheduling.koordinator.sh/resource-spec: '{"preferredCPUBindPolicy":"FullPCPUs"}'
  priorityClassName: koord-prod

C 组 CPU 精细化编排协议:

apiVersion: config.koordinator.sh/v1alpha1
kind: ClusterColocationProfile
metadata:
  name: colocation-profile-example
spec:
  selector:
    matchLabels:
      app: nginx
  # 采用 LSR QoS
  qosClass: LSR
  annotations:
  # 采用物理核间打散且独占物理核
    scheduling.koordinator.sh/resource-spec: '{"preferredCPUBindPolicy":"SpreadByPCPUs", "preferredCPUExclusivePolicy":"PCPULevel"}'
  priorityClassName: koord-prod
  1. 在线服务本文选用 Nginx 在线服务器,Pod YAML 如下:
---
# nginx应用配置
apiVersion: v1
data:
  config: |-
    user  nginx;
    worker_processes  4; # Nginx的Worker个数,影响Nginx Server的并发。

    events {
        worker_connections  1024;  # 默认值为1024。
    }

    http {
        server {
            listen  8000;

            gzip off;
            gzip_min_length 32;
            gzip_http_version 1.0;
            gzip_comp_level 3;
            gzip_types *;
        }
    }

    #daemon off;
kind: ConfigMap
metadata:
  name: nginx-conf-0
---
# Nginx实例,作为在线类型服务应用。
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: nginx-0
  namespace: default
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - "${node_name}"    
  schedulerName: koord-scheduler
  priorityClassName: koord-prod
  containers:
    - image: 'koordinatorsh/nginx:v1.18-koord-exmaple'
      imagePullPolicy: IfNotPresent
      name: nginx
      ports:
        - containerPort: 8000
          hostPort: 8000 # 压测请求访问的端口。
          protocol: TCP
      resources:
        limits:
          cpu: '4'
          memory: 8Gi
        requests:
          cpu: '4'
          memory: 8Gi
      volumeMounts:
        - mountPath: /apps/nginx/conf
          name: config
  hostNetwork: true
  restartPolicy: Never
  volumes:
    - configMap:
        items:
          - key: config
            path: nginx.conf
        name: nginx-conf-0
      name: config
  1. 执行以下命令,部署 Nginx 应用
kubectl apply -f nginx-0.yaml
  1. 执行以下命令,查看 Nginx 应用的 Pod 状态
kubectl get pod -l app=nginx -o wide

可以看到输出如下,表示 Nginx 应用已经在测试机上正常运行

NAME      READY   STATUS    RESTARTS   AGE     IP           NODE                    NOMINATED NODE   READINESS GATES
nginx-0   1/1     Running   0          2m46s   10.0.0.246   cn-beijing.10.0.0.246   <none>           <none>
  1. 在压测机上,执行以下命令,部署压测工具 wrk
wget -O wrk-4.2.0.tar.gz https://github.com/wg/wrk/archive/refs/tags/4.2.0.tar.gz && tar -xvf wrk-4.2.0.tar.gz
cd wrk-4.2.0 && make && chmod +x ./wrk

压测

  1. 使用压测工具 wrk,向 Nginx 应用发起压测请求。
# node_ip填写测试机的IP地址,用于wrk向测试机发起压测;8000是Nginx暴露到测试机的端口。
taskset -c 32-45 ./wrk -t120 -c400 -d600s --latency http://${node_ip}:8000/
  1. 等待 wrk 运行结束后,获取 wrk 的压测结果,wrk 输出格式如下所示。重复多次测试,以获得相对稳定的结果。
Running 10m test @ http://192.168.0.186:8000/
  120 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.29ms    2.49ms 352.52ms   91.07%
    Req/Sec     0.96k   321.04     3.28k    62.00%
  Latency Distribution
     50%    2.60ms
     75%    3.94ms
     90%    5.55ms
     99%   12.40ms
  68800242 requests in 10.00m, 54.46GB read
Requests/sec: 114648.19
Transfer/sec:     92.93MB

总结

在 Kubernetes 集群中,不同业务负载之间可能存在 CPU、内存等资源的争抢,影响业务的性能和稳定性。面对 Noisy Neighbor 现象,用户可以使用 Koordinator 为应用配置更精细的 CPU 编排策略,使得不同应用可以协同的共享 CPU 资源。我们通过实验说明,Koordinator 的精细化 CPU 编排能力能有效抑制 CPU 资源的争抢,改善应用性能。

非常欢迎你通过 Github/Slack/钉钉/微信 等方式加入我们来参与 Koordinator 开源社区。你是否已经有一些希望与我们社区交流的内容呢?可以通过以下渠道参与讨论:

  • 加入社区 Slack channel (English)
  • 加入社区钉钉群:搜索群号 33383887 (Chinese)

相关链接:

[1] 多层次弹性 Quota 管理功能

https://koordinator.sh/docs/user-manuals/multi-hierarchy-elas...

[2] 安装 Koordinator

https://koordinator.sh/docs/installation/

[3] ColocationProfile

https://koordinator.sh/docs/user-manuals/colocation-profile/

点击此处,立即了解 Koordinator 项目!


阿里云云原生
1k 声望302 粉丝