05 March 2022

k8s 中,每个pod使用cpu request 和limit 来申请和管理实际使用的资源。

request 仅作用于分配期,limit才是运行期时真正限制的指标。

例如:一个poc,cpu_request : 2000mi,cpu_limit : 4000mi (1000mi 接近一个cpu时间片,4000mi代表4cpu)

k8s 使用linux的cfs调度器来完成对limit限制的调度,保证pod不会使用超过limit限制的cpu资源。

cfs调度器每100ms调度一次,如果进程在前50ms就用光了在这100ms分配的cpu时间片,后50ms就没有cpu可用,需要等待到下一个调度周期,这个过程就是cpu thorttling , 中文名称是cpu限流/节流。笔者倾向于叫cpu限流。

从这上面看,一切都正常,设定了limit,那么就按照设定的limit来运行。

问题来自于这些地方,并且非常严重:

  1. linux kernel 的cfs有bug,有一些不应该触发限流的场景,会触发限流。该问题在k8s的repo中有讨论,一部分fix合并到了kernel 4.18 和 5.4 版本。但是依然有反馈还是存在会触发不必要的限流。#67577 #97445
  2. 使用k8s时,如果没有注意到cpu thortlling 的监控,就会导致应用产生cpu限流,从而导致rt下降。一部分公司的私有云甚至根本不提供cpu thorttling监控。
  3. 信号堆叠问题,cfs调度100ms一次,但是大多数监控都是分钟级别,少数能做到秒级,很难识别到100ms调度周期中cpu limit usage超过100%的情况。大多数的表现是,cpu使用率只有25%-50%左右,但是应用性能却突然变差。
  4. 应用有突发流量翻倍/java 应用gc等场景,也都有可能导致cpu限流,从而影响性能。

k8s中的limit目前在业界被认为是一个反模式,有k8s开发人员倾向于彻底取消limit。但是没有其他可靠的配额限制手段时,取消limit还是太激进了。

cpu限流的解决方案:

  1. 超额配置limit,以保证突发cpu使用不会触发限流。该选项会导致资源浪费严重(因为高优先级pod的limit=request),为了罕见情况超配资源。
  2. cpu brust , cpu brust 功能可以实现,如果pod平时的使用的cpu 小于limit,差值的部分就可以积累下来,在该pod使用cpu超标时,从积累部分扣除,从而保证整体的cpu使用率平滑。在阿里的测试中,启用该选项可以降低33%的限流(33%–>0%),rt 从111ms 降低到71ms。
  3. 目前社区有在讨论在高优先级(request = limit)的pod中取消cfs,因为它的资源是完全充足的,不需要通过cfs来限制。

以上这些解决方案,都依赖针对cpu thorlling的监控,有了监控才能知道有没有问题,实施了解决方案以后有没有改善。

如果使用私有云,而云部门没有提供相关监控的话,可以在容器中使用以下命令查看cpu 限流情况。

cat /sys/fs/cgroup/cpu/cpu.stat

nr_periods 226 // 容器总运行周期
nr_throttled 26 // 容器限流周期
throttled_time 2117783298

通过 nr_throttled / nr_periods 即可计算出限流率。

我们实际中的一个案例:cpu密集型服务,cpu = 4c , 按分钟统计的cpu使用为25%;流量:每小时会一次qps翻倍的情况。平均耗时为12ms。 调整cpu=8c,平均耗时降低为4ms.