Cgroup引发非预期行为——容器12C性能不如8C

简介: 背景压测信息:压测工具是jmeter,压测的并发数都是1并发,压测的URL固定。资源信息:ACK集群的ECS是32C, ingress pod 无limit限制, 业务pod采用绑核处理8c或12c链路:客户端 ->  nginx(同VPC下的ECS )-> SLB > ingress pod ->   业务pod 问题客户压测时候, 采用slo-manager对业务p

背景

压测信息:压测工具是jmeter,压测的并发数都是1并发,压测的URL固定。

资源信息:ACK集群的ECS是32C, ingress pod 无limit限制, 业务pod采用绑核处理8c或12c

链路:客户端 ->  nginx(同VPC下的ECS )-> SLB > ingress pod ->   业务pod 

问题

客户压测时候, 采用slo-manager对业务pod采用绑核处理,表明业务pod上的ECS设置的limit核数,业务pod独占,不与其他进程共享。客户遇到一个奇怪现象是采用12c绑核的。 可以看到采用12C时候,,CPU使用率达到100%时候,qps、rt和P99指标相对于8c指标反而并没有提高。

压测结果

14:07~14:17 8核16G: 1并发:QPS 36.7,平均响应时间27ms,P99 49ms,CPU使用率100% 

14:33~14:43 12核16G: 1并发:QPS 28.4,平均响应时间35ms,P99 78ms,CPU使用率100% 

15:14~15:24 8核16G: 1并发:QPS 36.5,平均响应时间27ms,P99 49ms,CPU使用率100% 

18:29~18:39 12核16G: 1并发:QPS 28.5,平均响应时间35ms,P99 78ms,CPU使用率100%

分析过程

  1. pod镜像并无改变。整个链路业务行为也无变化,唯一变化就是绑核由8c改到12c,相关压测指标下降了。 pod配置都开启的cpu-policy: static-burst和 cpuset-scheduler: true 自动绑核和CPU拓扑感知调度
  2. 压测结果 显示12核 性能远逊于 8和 15:14~15:24 8核16G: 1并发:QPS 36.5,平均响应时间27ms,P99 49ms,CPU使用率100%18:29~18:39 12核16G: 1并发:QPS 28.5,平均响应时间35ms,P99 78ms,CPU使用率100%
  3. 8核心pod 部署在 cn-beijing.xxxx节点上, pod绑定的CPU核心是0-7.

4. 12核心pod 部署在 cn-beijing.xxxx节点上, pod绑定的CPU核心是16-31 这16个CPU中

5. 12核 pod查看slo是工作的, cgroup显示动态绑核是成功的

6. 但是通过cpu.stat,可以看到依然存在 CPU throttle

7. promethes监控显示 在同样绑核情况下,8c的pod没有CPU throttled,12c的pod,CPU使用率相比于8C并没有100%趋势下,反而遇到了CPU throttled,明显不符合预期。但是前面的步骤已经验证了绑核是成功的,由此怀疑到应该是系统层面对于cfs调度存在的未知的非预期的行为.

12核

8核

结论

内核版本低于 4.19,如果一个周期内,进程未消耗完CPU时间,则会预留1ms直到一次完整的分配周期结束。1ms虽然不多,但是试想一下,如果是个100c的CPU,那么每个周期就是就有99ms,也就是1C左右的 CPU无法使用,这就可能产生限流。 

容器内跑一些压力时(使用 while :; do :; done 模拟 )

复现情况如下:

1、Cenos7.9,对应 3.10 内核,很容易复现

发生 CPU Throttling 比例 ~1%

2、alinux 2,对于 4.19 内核,

发生 CPU Throttling 比例 ~千分之一

拓展

CPU限制

容器的limit声明式限制最终反映在宿主机的cgroup中,根据pod的qos,可以在下面的三个地址中找到limit设置。

  • /sys/fs/cgroup/cpu/kubepods.slice/kubepods-pod{pod ID}.slice/{docker/cri}-{container ID}.scope
  • /sys/fs/cgroup/cpu/kubepods-besteffort.slice/kubepods-pod{pod ID}.slice/{docker/cri}-{container ID}.scope
  • /sys/fs/cgroup/cpu/kubepods-burstable.slice/kubepods-pod{pod ID}.slice/{docker/cri}-{container ID}.scope 

  • cpu.cfs_period_us:设定周期时间,必须与cfs_quota_us配合使用。
  • cpu.cfs_quota_us :设定周期内最多可使用的时间。这里的配置指 task 对单个 cpu 的使用上限,若cfs_quota_us是cfs_period_us的两倍,就表示在两个核上完全使用。数值范围为 1000 - 1000,000(微秒)。
  • cpu.stat:统计信息,包含nr_periods(表示经历了几个cfs_period_us周期)、nr_throttled(表示 task 被限制的次数)及throttled_time(表示 task 被限制的总时长)。
  • cpuset.cpus:在这个文件中填写 cgroup 可使用的 CPU 编号,如0-2,16代表 0、1、2 和 16 这 4 个 CPU。

CFS运行逻辑和CPU限流原理

某个单线程容器应用运行时。假设此应用需要200ms的CPU时间才能处理完毕一个请求,如果没有其他约束,此时它的cfs分配应该入下图所示。

现在我们给这个应用程序设置0.4 CPU的CPU限制(CPU quota是0.4)。这意味着应用程序每100ms CPU周期,该程序将要获得40ms的运行时间——即使这些时间CPU是空余的。还是同样的一个200ms的请求,由于0.4c的限制,现在需要440ms才能完成

这个时候查看该容器路径下的cpu group的cpu.stat,其中 throttled_time会被限制了 240ms(对于每 100 毫秒的周期,应用程序只能运行 40ms,并被限制 60ms。4 个周期,因此 4 * 60 = 240ms。)

低CPU使用率情况下依然遇到限流

容器环境中,一个关键指标是throttling,这表明容器被限制的次数。我们发现很多容器无论 CPU 使用率是否接近极限都会受到限制。如下一个案例:

在动画中可以看到 CPU 限制设置为800m(0.8 个核心,80% 的核心),峰值使用率最高为200m(20% 的核心)。看到之后,我们可能会认为我们有足够的 CPU 让服务在它节流之前运行,但是依然会遇到cpu限流,这就意味着延迟增高和性能下降。

限流原因

如下图,第一个图显示了 cgroup 在一段时间内的全局配额。这从 20ms 的配额开始,这与 0.2 CPU 相关。中间的图表显示分配给每个 CPU 队列的配额,底部的图表显示实际工作线程在其 CPU 上运行的时间。

在 10 毫秒:

  • Worker 1 收到了一个请求,需要5毫秒处理请求
  • 一部分配额从全局配额转移到 CPU 1 的每个 CPU 队列。
  • Worker 1 需要 5ms 来处理和响应请求。

在 17 毫秒:

  • Worker 2 收到了一个请求。
  • 一部分配额从全局配额转移到 CPU 2 的每个 CPU 队列。

Worker 1 需要精确到需要5 毫秒来响应请求,并完全用完这5毫秒是不现实的。如果很快就完成请求会发生什么呢?

在 30 毫秒:

  • Worker 1 收到了一个请求。
  • Worker 1 只需要 1 毫秒来处理请求,而 CPU 1 的每个 CPU 存储桶上还剩下 4 毫秒。
  • 由于每个 CPU 运行队列上还有剩余时间,但 CPU 1 上没有更多可运行线程,因此设置了一个计时器以将 slack 配额返回给全局存储桶。

在 36 毫秒:

  • CPU 1 上设置的 slack 计时器触发并将除 1 ms 之外的所有配额返回到全局配额池(返回的是5-1(消耗)-1(预留)=3ms),此时全局quota还剩下5+3=8ms
  • 这会在 CPU 1 上留下 1 毫秒的配额。

在 41 毫秒:

  • Worker 2 收到一个长请求。
  • 所有剩余时间都从全局存储桶转移到 CPU 2 的 per-CPU 存储桶,Worker 2 使用所有时间。

在 49 毫秒:

  • CPU 2 上的 Worker 2 现在在未完成请求的情况下受到限制,此处worker 我产生throttled
  • 尽管 CPU 1 仍有 1ms 的配额,但仍会发生这种情况。

虽然 1 毫秒可能对双核机器没有太大影响,但这些毫秒在高核数机器上加起来。如果我们在 88 核 (n) 机器上遇到此行为,我们可能会在每个周期内耗费 87 (n-1) 毫秒,因为肯定有1个限制的CPU。那可能无法使用的 87 毫秒或 0.87 CPU。也就意味着假设一个容器分配的CPU越多,那么在一个周期内,其预留的时间累积就越多,而往往一个压测时间并不是100ms,所以在CPU越多,其受到throttled概率也就越多。

linux 内核是如何解决这个问题

当且仅当每个 CPU 的过期时间与全局过期时间匹配时,预补丁代码才会在运行时过期cfs_rq->runtime_expires != cfs_b->runtime_expires。因此,那 1 毫秒永不过期。该补丁将此逻辑从基于时钟时间更改为周期序列计数,解决了内核中长期存在的错误。代码如下:

- if (cfs_rq->runtime_expires != cfs_b->runtime_expires) { 
+ if (cfs_rq->expires_seq == cfs_b->expires_seq) { 
               /* 延长本地期限,漂移以 2 个滴答为界 */ 
                cfs_rq->runtime_expires + = TICK_NSEC; 
       } else { 
                /* 全局截止日期提前,过期已过 */ 
                cfs_rq->runtime_remaining = 0; 
        }

修改问题 5.4+ 主线内核的一部分。它们已被反向移植到许多可用的内核中:

  • Linux-stable: 4.14.154+, 4.19.84+, 5.3.9+
  • Ubuntu: 4.15.0–67+, 5.3.0–24+
  • Redhat Enterprise Linux:
  1. RHEL 7: 3.10.0–1062.8.1.el7+
  2. RHEL 8: 4.18.0–147.2.1.el8_1+
  • CoreOS: v4.19.84+

该错误https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=763a9ec06c4已被修复并合并到运行 4.19 或更高版本的 Linux 发行版的内核中。

同时k8s社区也有关于此问题的issue:https://github.com/kubernetes/kubernetes/issues/67577

如果使用的 Linux 发行版的内核版本低于 4.19,建议使用aliyun2 最新版或升级到最新版本的内核

ACK另辟蹊径

通过前文,我们已经知道了这是由于内核cpu cgroup在调度cpu时候预留行为会对绑核的容器性能产生影响,解决办法是通过升级内核到4.19以上或者使用aliyunOS。但是更换OS往往对客户来说是一个很大的工程,那么ACK是否有其他途径呢?这时候可以使用koordinator的CPU burst,此feature可以根据对CPU Throttled的动态感知,自动调节CPU burst参数,允许容器在空闲时积累一些CPU时间片,用于满足突发时的资源需求,进而可以提升容器性能、降低延迟指标,临时允许突破limit限制,从而保障业务平稳运行。

  • sched_cfs_bw_burst_enabled 为1 表明CPU Burst功能的全局开关已打开
  • cpu.cfs_burst_us 表明可以突发额外使用最多几个CPU资源。可以看到在aliyun2 系统下,可以额外突发十倍limit的burst资源。

而客户在我们建议下,开启Pod的Annotation alibabacloud.com/cpuBurst: '{"policy": "auto"}' 后,12C下,相关的CPU限流已经被改善。

一句话总结

低于4.19内核版本的,在cgroup cpu调度过程中,由于基于时钟时间的cfs,所以在一个cpu周期内,如果worker未完全消耗完cpu时间,则每次slack回收时候,都会预留1ms/per-cpu,当CPU数量越大,则预留的1ms 'cpu'也就越多,进而产生CPU throttled概率也就越大,从而业务感知上也越加明显。建议:采用4.19以上的内核OS或者使用ack-koordinator的cpu burst功能。

感谢:@佑祎@牧原同学的大力支持

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
7月前
|
存储 边缘计算 数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
163 0
|
2月前
|
存储 程序员 C++
C++容器初始化方式详解:优缺点、性能与应用场景
C++容器初始化方式详解:优缺点、性能与应用场景
19 0
|
12月前
|
Kubernetes 固态存储 测试技术
【容器技术】在裸机与虚拟机上运行容器:性能和优点
【容器技术】在裸机与虚拟机上运行容器:性能和优点
|
存储 Linux Docker
语言流氓子跟你聊3分钟容器核心概念cgroup
还是容器,之前咱们简单聊了一下namespace,可以把它想象成用来存储进程固定参数的盒子。如果两个进程希望彼此更进一步了解对方,那就把他们的参数放在一个盒子里,仅此而已。
|
监控 Linux 测试技术
性能测试 基于Python结合InfluxDB及Grafana图表实时采集Linux多主机或Docker容器性能数据
性能测试 基于Python结合InfluxDB及Grafana图表实时采集Linux多主机或Docker容器性能数据
178 0
|
存储 缓存 监控
容器 I/O 性能诊断:到底哪个应用是带宽杀手?
容器和 Kubernetes 的发展成熟为应用的云原生化提供最基础的支撑,从而使企业最大化利用云上的资源。存储作为应用运行的基石,也在服务云原生化的过程中不断演进。
194 0
容器 I/O 性能诊断:到底哪个应用是带宽杀手?
|
Kubernetes Cloud Native 应用服务中间件
如何合理使用 CPU 管理策略,提升容器性能?
CPU Burst、拓扑感知调度是阿里云容器服务 ACK 提升应用性能的两大利器,它们解决了不同场景下的 CPU 资源管理,可以共同使用。点击下文,查看详情!
如何合理使用 CPU 管理策略,提升容器性能?
|
缓存 运维 监控
长文解析:作为容器底层技术的半壁江山, cgroup如何突破并发创建瓶颈?
io_uring 作为一种新型高性能异步编程框架,代表着 Linux 内核未来的方向,当前仍处于快速发展中。阿里云联合 InfoQ 发起《io_uring 介绍及应用实践》的技术公开课,围绕 OpenAnolis 龙蜥社区 Anolis OS 8 全方位解析高性能存储场景。
长文解析:作为容器底层技术的半壁江山, cgroup如何突破并发创建瓶颈?
|
Kubernetes 安全 Linux
容器新体验 - Rootless Container + cgroup V2
![image.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/145b98103c9bcd945b81c9463fab1867.png) 在2020年12月最新的 Docker 20.10 版本中,其中两个关键的特性发布揭示了容器运行时技术发展一些新方向。 首先是 Cgroup V2 已经被正式支持,虽然这个功能对最终用户很
3798 0
容器新体验 - Rootless Container + cgroup V2
|
存储 机器学习/深度学习 缓存
阿里云容器服务团队实践——Alluxio优化数倍提升云上Kubernetes深度学习训练性能
近些年,以深度学习为代表的人工智能技术取得了飞速的发展,正落地应用于各行各业。越来越多的用户在云上构建人工智能训练平台,利用云平台的弹性计算能力满足高速增长的AI业务模型训练方面的需求,然而这种“本地存储+云上训练”的训练模式加剧了计算存储分离架构带来的远程数据访问的性能影响。
4356 0
阿里云容器服务团队实践——Alluxio优化数倍提升云上Kubernetes深度学习训练性能