首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
kubernetes与velero的第一次尝试
2
在Kubernetes中如何针对Namespace进行资源限制?
3
kubernetes之metrics-server安装与配置
4
kubernetes部署metrics-server
5
Kubernetes1.20.9摘掉一个master节点再重新加入(ETCD需要注意的)
6
Kubernetes 1.17.17升级到1.18.20
7
Kubernetes 1.18.20升级到1.19.12
8
Kubernetes 1.19.12升级到1.20.9(强调一下selfLink)
9
Kubernetes 1.16.15升级到1.17.17
10
使用 kainstall 工具一键部署 kubernetes 高可用集群
11
附034.Kubernetes_v1.21.0高可用部署架构二
12
附016.Kubernetes_v1.17.4高可用部署
13
附022.Kubernetes_v1.18.3高可用部署架构一
14
附024.Kubernetes_v1.18.3高可用部署架构二
15
使用 StatefulSet 部署 etcd 集群
16
Kubernetes 稳定性保障手册 -- 极简版
17
Linux(centos7)离现安装kubernetes1.19.2和docker——组件部分
18
docker register 私有仓库部署 - http模式
19
KubeSphere 开源 KubeEye:Kubernetes 集群自动巡检工具
20
K8S 中的 CPUThrottlingHigh 到底是个什么鬼?
21
全链路分布式跟踪系统 Apache SkyWalking 入门教程
22
pod Evicted的状态究竟是何人所为
23
使用 ezctl 工具部署和管理 Kubernetes 集群
24
Kubernetes部署策略详解
25
kubernetes容器探针检测
26
使用Spring Boot实现动态健康检查HealthChecks
27
真一文搞定 ingress-nginx 的使用
28
K8S备份、恢复、迁移神器 Velero
29
一次关于k8s kubectl top 和 contained ps 不一致的问题探究
30
kubernetes备份恢复之velero
31
使用 Velero 进行集群备份与迁移
32
TKE集群中nginx-ingress使用实践
33
使用velero进行kubernetes灾备
34
Kubernetes 映射外部服务
35
运维体系建设套路
36
k8s解决pod调度不均衡的问题
37
ingress中虚拟路径解决方案
38
容器下的两地三中心建设
39
k8s集群外的主机访问pod的解决方案
40
k8s基础-健康检查机制
41
k8s基础-标签使用
42
ingress-nginx请求改写
43
nginx ingress server alias 多域名多证书问题
44
JAVA | Java 解决跨域问题 花式解决跨域问题
45
如何通过ingress-nginx实现应用灰度发布?
46
在Kubernetes(k8s)中使用GPU
47
使用 Prometheus-Operator 监控 Calico
48
使用Kubespray部署Kubernetes集群
49
云原生下的CI/CD:Argo CD 详解,手把手教你入门
50
Pod的健康检查机制
清单首页k8s文章详情

K8S 中的 CPUThrottlingHigh 到底是个什么鬼?

1. 前言

在使用 Kubernetes 的过程中,我们看到过这样一个告警信息:

[K8S] 告警主题: CPUThrottlingHigh 告警级别: warning 告警类型: CPUThrottlingHigh 故障实例: 告警详情: 27% throttling of CPU in namespace kube-system for container kube-proxy in pod kube-proxy-9pj9j. 触发时间: 2020-05-08 17:34:17

这个告警信息说明 kube-proxy 容器被 throttling 了,然而查看该容器的资源使用历史信息,发现该容器以及容器所在的节点的 CPU 资源使用率都不高:

告警期间容器所在节点 CPU 使用率

告警期间 kube-proxy 的资源使用率

经过我们的分析,发现该告警实际上是和 Kubernetes 对于 CPU 资源的限制和管控机制有关。Kubernetes 依赖于容器的 runtime 进行 CPU 资源的调度,而容器 runtime 以 Docker 为例,是借助于 cgroup 和 CFS 调度机制进行资源管控。本文基于这个告警案例,首先分析了 CFS 的基本原理,然后对于 Kubernetes 借助 CFS 进行 CPU 资源的调度和管控方法进行了介绍,最后使用一个例子来分析 CFS 的一些调度特性来解释这个告警的 root cause 和解决方案。

2. CFS 基本原理

基本原理

Linux 在 2.6.23 之后开始引入 CFS 逐步替代 O1 调度器作为新的进程调度器,正如它名字所描述的,CFS(Completely Fair Scheduler) 调度器[1]追求的是对所有进程的全面公平,实际上它的做法就是在一个特定的调度周期内,保证所有待调度的进程都能被执行一遍,主要和当前已经占用的 CPU 时间经权重除权之后的值 (vruntime,见下面公式) 来决定本轮调度周期内所能占用的 CPU 时间,vruntime 越少,本轮能占用的 CPU 时间越多;总体而言,CFS 就是通过保证各个进程 vruntime 的大小尽量一致来达到公平调度的效果:

进程的运行时间计算公式为: 进程运行时间 = 调度周期 * 进程权重 / 所有进程权重之和

vruntime = 进程运行时间 * NICE_0_LOAD / 进程权重 = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重 = 调度周期 * NICE_0_LOAD / 所有进程总权重

通过上面两个公式,可以看到 vruntime 不是进程实际占用 CPU 的时间,而是剔除权重影响之后的 CPU 时间,这样所有进程在被调度决策的时候的依据是一致的,而实际占用 CPU 时间是经进程优先级权重放大的。这种方式使得系统的调度粒度更小来,更加适合高负载和多交互的场景。

Kernel 配置

在 kernel 文件系统中,可以通过调整如下几个参数来改变 CFS 的一些行为:

  • /proc/sys/kernel/sched_min_granularity_ns,表示进程最少运行时间,防止频繁的切换,对于交互系统
  • /proc/sys/kernel/sched_nr_migrate,在多 CPU 情况下进行负载均衡时,一次最多移动多少个进程到另一个 CPU 上
  • /proc/sys/kernel/sched_wakeup_granularity_ns,表示进程被唤醒后至少应该运行的时间,这个数值越小,那么发生抢占的概率也就越高
  • /proc/sys/kernel/sched_latency_ns,表示一个运行队列所有进程运行一次的时间长度 (正常情况下的队列调度周期,P)
  • sched_nr_latency,这个参数是内核内部参数,无法直接设置,是通过 sched_latency_ns/sched_min_granularity_ns 这个公式计算出来的;在实际运行中,如果队列排队进程数 nr_running > sched_nr_latency,则调度周期就不是 sched_latency_ns,而是 P = sched_min_granularity_ns * nr_running,如果 nr_running <= sched_nr_latency,则 P = sched_latency_ns

在阿里云的 Kubernetes 节点上,这些参数配置如下:

代码语言:javascript
复制
$ cat /proc/sys/kernel/sched_min_granularity_ns
10000000
$ cat /proc/sys/kernel/sched_nr_migrate
32
$ cat /proc/sys/kernel/sched_wakeup_granularity_ns
15000000
$ cat /proc/sys/kernel/sched_latency_ns
24000000

可以算出来 sched_nr_latency = sched_latency_ns / sched_min_granularity_ns = 24000000 / 10000000 = 2.4

在阿里云普通的虚拟机上的参数如下:

代码语言:javascript
复制
$ cat /proc/sys/kernel/sched_min_granularity_ns
3000000
$ cat /proc/sys/kernel/sched_latency_ns
15000000

可以算出来 sched_nr_latency = sched_latency_ns / sched_min_granularity_ns = 15000000 / 3000000 = 5

而在普通的 CentOS Linux release 7.5.1804 (Core) 上的参数如下:

代码语言:javascript
复制
$ cat /proc/sys/kernel/sched_min_granularity_ns
3000000
$ cat /proc/sys/kernel/sched_nr_migrate
32
$ cat /proc/sys/kernel/sched_wakeup_granularity_ns
4000000
$ cat /proc/sys/kernel/sched_latency_ns
24000000

可以算出来 sched_nr_latency = sched_latency_ns / sched_min_granularity_ns = 24000000 / 3000000 = 8

可以看到,阿里云的 Kubernetes 节点设置了更长的最小执行时间,在进程队列稍有等待 (2.4) 的时候就开始保证每个进程的最小运行时间不少于 10 毫秒。

运行和观察

部署这样一个 yaml POD:

代码语言:javascript
复制
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  containers:
  - image: busybox
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
    command:
      - "/bin/sh"
      - "-c"
      - "while true; do sleep 10; done"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always

可以看到该容器内部的进程对应的 CPU 调度信息变化如下:

代码语言:javascript
复制
[root@k8s-node-04 ~]# cat /proc/121133/sched
sh (121133, #threads: 1)
-------------------------------------------------------------------
se.exec_start                                :   20229360324.308323
se.vruntime                                  :             0.179610
se.sum_exec_runtime                          :            31.190620
se.nr_migrations                             :                   12
nr_switches                                  :                   79
nr_voluntary_switches                        :                   78
nr_involuntary_switches                      :                    1
se.load.weight                               :                 1024
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                   26
mm->numa_scan_seq                            :                    0
numa_migrations, 0
numa_faults_memory, 0, 0, 0, 0, -1
numa_faults_memory, 1, 0, 0, 0, -1
numa_faults_memory, 0, 1, 1, 0, -1
numa_faults_memory, 1, 1, 0, 0, -1


[root@k8s-node-04 ~]# cat /proc/121133/sched
sh (121133, #threads: 1)
-------------------------------------------------------------------
se.exec_start                                :   20229480327.896307
se.vruntime                                  :             0.149504
se.sum_exec_runtime                          :            33.325310
se.nr_migrations                             :                   17
nr_switches                                  :                   91
nr_voluntary_switches                        :                   90
nr_involuntary_switches                      :                    1
se.load.weight                               :                 1024
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                   31
mm->numa_scan_seq                            :                    0
numa_migrations, 0
numa_faults_memory, 0, 0, 1, 0, -1
numa_faults_memory, 1, 0, 0, 0, -1
numa_faults_memory, 0, 1, 0, 0, -1
numa_faults_memory, 1, 1, 0, 0, -1


[root@k8s-node-04 ~]# cat /proc/121133/sched
sh (121133, #threads: 1)
-------------------------------------------------------------------
se.exec_start                                :   20229520328.862396
se.vruntime                                  :             1.531536
se.sum_exec_runtime                          :            34.053116
se.nr_migrations                             :                   18
nr_switches                                  :                   95
nr_voluntary_switches                        :                   94
nr_involuntary_switches                      :                    1
se.load.weight                               :                 1024
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                   34
mm->numa_scan_seq                            :                    0
numa_migrations, 0
numa_faults_memory, 0, 0, 0, 0, -1
numa_faults_memory, 1, 0, 0, 0, -1
numa_faults_memory, 0, 1, 1, 0, -1
numa_faults_memory, 1, 1, 0, 0, -1

其中 sum_exec_runtime 表示实际运行的物理时间。

3. Kubernetes 借助 CFS 进行 CPU 管理

CFS 进行 CPU 资源限流 (throtting) 的原理

根据文章《Kubernetes 生产实践系列之三十:Kubernetes 基础技术之集群计算资源管理》[2]的描述,Kubernetes 的资源定义:

代码语言:javascript
复制
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

比如里面的 CPU 需求,会被翻译成容器 runtime 的运行时参数,并最终变成 cgroups 和 CFS 的参数配置:

代码语言:javascript
复制
$ cat cpu.shares
256
$ cat cpu.cfs_quota_us
50000
$ cat cpu.cfs_period_us
100000

这里有一个默认的参数:

代码语言:javascript
复制
$ cat /proc/sys/kernel/sched_latency_ns
24000000

所以在这个节点上,正常压力下,系统的 CFS 调度周期是 24ms,CFS 重分配周期是 100ms,而该 POD 在一个重分配周期最多占用 50ms 的时间,在有压力的情况下,POD 可以占据的 CPU share 比例是 256。

下面一个例子可以说明不同资源需求的 POD 容器是如何在 CFS 的调度下占用 CPU 资源的:

CPU 资源配置和 CFS 调度

在这个例子中,有如下系统配置情况:

  • CFS 调度周期为 10ms,正常负载情况下,进程 ready 队列里面的进程在每 10ms 的间隔内都会保证被执行一次
  • CFS 重分配周期为 100ms,用于保证一个进程的 limits 设置会被反映在每 100ms 的重分配周期内可以占用的 CPU 时间数,在多核系统中,limit 最大值可以是 CFS 重分配周期 * CPU 核数
  • 该执行进程队列只有进程 A 和进程 B 两个进程
  • 进程 A 和 B 定义的 CPU share 占用都一样,所以在系统资源紧张的时候可以保证 A 和 B 进程都可以占用可用 CPU 资源的一半
  • 定义的 CFS 重分配周期都是 100ms
  • 进程 A 在 100ms 内最多占用 50ms,进程 B 在 100ms 内最多占用 20ms

所以在一个 CFS 重分配周期 (相当于 10 个 CFS 调度周期) 内,进程队列的执行情况如下:

  • 在前面的 4 个 CFS 调度周期内,进程 A 和 B 由于 share 值是一样的,所以每个 CFS 调度内 (10ms),进程 A 和 B 都会占用 5ms
  • 在第 4 个 CFS 调度周期结束的时候,在本 CFS 重分配周期内,进程 B 已经占用了 20ms,在剩下的 8 个 CFS 调度周期即 80ms 内,进程 B 都会被限流,一直到下一个 CFS 重分配周期内,进程 B 才可以继续占用 CPU
  • 在第 5-7 这 3 个 CFS 调度周期内,由于进程 B 被限流,所以进程 A 可以完全拥有这 3 个 CFS 调度的 CPU 资源,占用 30ms 的执行时间,这样在本 CFS 重分配周期内,进程 A 已经占用了 50ms 的 CPU 时间,在后面剩下的 3 个 CFS 调度周期即后面的 30ms 内,进程 A 也会被限流,一直到下一个 CFS 重分配周期内,进程 A 才可以继续占用 CPU

如果进程被限流了,可以在如下的路径看到:

cat /sys/fs/cgroup/cpu/kubepods/pod5326d6f4-789d-11ea-b093-fa163e23cb69/69336c973f9f414c3f9fdfbd90200b7083b35f4d54ce302a4f5fc330f2889846/cpu.stat nr_periods 14001693 nr_throttled 2160435 throttled_time 570069950532853

本文开头问题的原因分析

根据 3.1 描述的原理,很容易理解本文开通的告警信息的出现,是由于在某些特定的 CFS 重分配周期内,kube-proxy 的 CPU 占用率超过了给它分配的 limits,而参看 kube-proxy daemonset 的配置,确实它的 limits 配置只有 200ms,这就意味着在默认的 100ms 的 CFS 重调度周期内,它只能占用 20ms,所以在特定繁忙场景会有问题:

代码语言:javascript
复制
$ cat cpu.shares
204
$ cat cpu.cfs_period_us
100000
$ cat cpu.cfs_quota_us
20000

注:这里 cpu.shares 的计算方法如下:200x1024/1000~=204

而这个问题的解决方案就是将 CPU limits 提高。

Zalando 公司有一个分享《Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latency / Henning Jacobs》[3]很好的讲述了 CPU 资源管理的问题,可以参考,这个演讲的 PPT 在这里[4]可以找到。

更具体问题分析和讨论还可以参考如下文章:

  • CPUThrottlingHigh false positives #108[5]
  • CFS quotas can lead to unnecessary throttling #67577[6]
  • CFS Bandwidth Control[7]
  • Overly aggressive CFS[8]

其中《Overly aggressive CFS[9]》里面还有几个小实验可以帮助大家更好的认识到 CFS 进行 CPU 资源管控的特点:

参考资料

[1]

CFS(Completely Fair Scheduler) 调度器: https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt

[2]

《Kubernetes 生产实践系列之三十:Kubernetes 基础技术之集群计算资源管理》: https://blog.csdn.net/cloudvtech/article/details/107634724

[3]

《Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latency / Henning Jacobs》: https://www.youtube.com/watch?v=eBChCFD9hfs

[4]

PPT 在这里: https://www.slideshare.net/try_except_/optimizing-kubernetes-resource-requestslimits-for-costefficiency-and-latency-highload?from_action=save

[5]

CPUThrottlingHigh false positives #108: https://github.com/kubernetes-monitoring/kubernetes-mixin/issues/108

[6]

CFS quotas can lead to unnecessary throttling #67577: https://github.com/kubernetes/kubernetes/issues/67577

[7]

CFS Bandwidth Control: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt

[8]

Overly aggressive CFS: https://gist.github.com/bobrik/2030ff040fad360327a5fab7a09c4ff1

[9]

Overly aggressive CFS: https://gist.github.com/bobrik/2030ff040fad360327a5fab7a09c4ff1

原文链接:https://blog.csdn.net/cloudvtech/article/details/107634785

下一篇
举报
领券