在云原生环境中,集群提供者常常将不同类型的工作负载部署在同一个集群中,利用不同业务的不同峰值效果,实现资源分时复用,避免资源浪费。然而,不同类型负载之间混合部署常常会导致资源竞争和相互干扰。最为典型的场景便是在线和离线负载的混合部署。当离线较多的占用计算资源时,在线负载的响应时间就会受到影响;当在线长时间较多的占用计算资源时,离线负载的任务完成时间不能得到保证。这种现象属于 Noisy Neighbor 问题。
根据混合部署的程度、资源类型的不同,解决该问题有许多不同的思路。Quota 管理可从整个集群维度限制负载的资源使用量,Koordinator 在这方面提供了多层次弹性 Quota 管理功能
[
1]
。单机维度上看,CPU、内存、磁盘 IO,网络资源都有可能被不同负载共享。Koordinator 在 CPU、内存上已经提供了一些资源隔离和保障的能力,磁盘 IO 和网络资源方面的相关能力正在建设中。
本文主要介绍当不同类型工作负载混合部署在同一个节点上时,Koordinator 如何帮助负载之间(在线和在线、在线和离线)协同地共享 CPU 资源。
CPU 资源 Noisy Neighbor 的本质是不同的负载之间无协同地共享 CPU 资源。
-
Kubernetes 默认的资源模型利用 cgroup(cfs quota) 从 CPU 时间使用量上来限制不同负载对于 CPU 资源的访问。这种情况下,一些负载就可能会被操作系统调度器切换所在的 CPU 核。由于不同 CPU 核对不同物理位置的内存访问时间不同,切换大概率会导致更长的内存访问时间,从而影响负载性能。
-
在 NUMA 架构中,SMT 线程(逻辑核)共享物理核的执行单元和 L2 缓存。当同一个物理核中有多种工作负载时,不同工作负载间就会产生资源争抢,导致负载性能下降。
Kubernetes 在单机侧提供了拓扑管理器和 CPU 管理器来尝试解决上述问题。然而,该功能只有在 Pod 已经调度到机器上之后才会尝试生效。这样就有可能导致 Pod 会被调度到 CPU 资源满足但是 CPU 拓扑不满足负载要求的情况。
针对上述问题和不足,Koordinator 设计了面向应用的 QoS 语义和 CPU 编排协议,如下图所示。
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 压制等策略。本文重点不在此,读者可关注后续文章。
对于 LSE 类型的应用,当机器是超线程架构时,只能保证负载独占逻辑核。这样当同一个物理核中有其它负载时,应用性能仍会受干扰。为此,Koordinator 支持用户在 Pod Annotation 上配置丰富的 CPU 编排策略来提高性能。
CPU 编排策略分为 CPU 绑定策略和 CPU 独占策略。CPU 绑定策略决定应用所被分配逻辑核在物理核间的分布,可采用物理核间打散或者堆叠。堆叠(FullPCPU)的方式指为应用分配完整的物理内核,可以有效地缓解 Noisy Neighbor 问题。打散(SpreadByPCPU)则主要应用于一些具有多种不同峰谷特性的延迟敏感型应用,可以让应用程序在特定时间充分使用 CPU。CPU 独占策略决定应用所被分配逻辑核的独占级别,可尽量避开已经同独占策略申请的物理核或 NUMANode。
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 压制等策略。本文重点不在此,读者可关注后续文章中的实践。
本次实验采用以下指标,评估应用不同部署方式下 Nginx 应用的性能表现:
实验结果如下:
-
对比 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 编排协议
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
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
2. 在线服务本文选用 Nginx 在线服务器,Pod YAML 如下:
---
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 *;
}
}
kind: ConfigMap
metadata:
name: nginx-conf-0
---
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
3. 执行以下命令,部署 Nginx 应用
kubectl apply -f nginx-0.yaml
4. 执行以下命令,查看 Nginx 应用的 Pod 状态