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

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: 背景压测信息:压测工具是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功能。

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

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
存储 边缘计算 数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
394 0
|
3月前
|
移动开发 前端开发 HTML5
Twaver-HTML5基础学习(20)数据容器(3)_数据的批量加载(节省性能方法)
本文介绍了Twaver HTML5中数据的批量加载方法,通过使用`box.startBatch()`可以在大量数据加载时提高性能。文章通过示例代码展示了如何在React组件中使用批量加载功能,以减少界面重绘次数并提升效率。
62 2
Twaver-HTML5基础学习(20)数据容器(3)_数据的批量加载(节省性能方法)
|
4月前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
21天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
54 7
|
4月前
|
存储 缓存 监控
在Linux中,如何优化虚拟机和容器的性能和资源使用?
在Linux中,如何优化虚拟机和容器的性能和资源使用?
|
4月前
|
SQL 关系型数据库 MySQL
MySQL运行在docker容器中会损失多少性能
MySQL运行在docker容器中会损失多少性能
|
6月前
|
Prometheus 监控 Cloud Native
容器化技术的性能调优与监控
【6月更文挑战第29天】本文探讨了容器(如Docker)的性能优化与监控,强调了其在云和微服务中的重要性。调优涉及资源限制设定、代码优化,通过性能测试、瓶颈分析进行迭代优化。监控目标是确保稳定性和可用性,使用工具如Portainer、CAdvisor、Prometheus来跟踪状态、性能指标和日志。监控内容涵盖容器状态、资源使用、日志和限制,策略包括设定阈值和告警机制。调优监控的优化有助于提升应用性能和企业价值。
|
7月前
|
存储 监控 持续交付
Docker容器的优化和性能调优技巧
Docker已经成为了现代应用程序开发和部署的核心工具之一。然而,要确保Docker容器在生产环境中运行稳定、高效,需要一些优化和性能调优的技巧。本文将介绍一些关键的Docker容器优化和性能调优策略,并提供丰富的示例代码,以帮助大家充分利用Docker的潜力。
|
7月前
|
存储 缓存 监控
【Docker 专栏】Docker 容器性能调优实战
【5月更文挑战第8天】本文探讨了Docker容器的性能调优技巧,包括理解容器性能指标(如CPU、内存、网络和磁盘I/O)并进行相应调优。重点讲述了CPU和内存的限制设置,网络配置优化以及磁盘I/O性能提升方法。通过实例展示了如何解决高CPU使用率问题,强调了根据应用需求进行调优的重要性,以实现更高效、稳定的容器运行。
486 0
【Docker 专栏】Docker 容器性能调优实战
|
7月前
|
存储 程序员 C++
C++容器初始化方式详解:优缺点、性能与应用场景
C++容器初始化方式详解:优缺点、性能与应用场景
122 0
下一篇
DataWorks