使用prometheus来避免Kubernetes CPU Limits造成的事故

使用,prometheus,避免,kubernetes,cpu,limits,造成,事故 · 浏览次数 : 652

小编点评

**CPU限制** **什么是 CPU限制?** CPU限制是指容器内的线程数量或CPU使用时间限制。当容器超过CPU限制时,它会被抑制。 **如何设置 CPU限制?** 可以使用以下两种方法设置CPU限制: * **资源限制**:在容器的资源配置中设置CPU限制。 * **命令**:使用`sysbench`等工具设置CPU限制。 **示例** **资源限制** ```yaml resources: limits: cpu: 2000m ``` **命令** ```bash sysbench --threads=4 --time=0 run ``` **设置 CPU限制** 可以使用`resources`配置限制。例如: ```yaml resources: limits: cpu: 100m ``` **使用 CPU限制** 可以使用以下方法使用CPU限制: * **设置资源限制**:在容器的资源配置中设置CPU限制。 * **使用命令**:使用`sysbench`等工具设置CPU限制。 **注意事项** * CPU限制的有效性取决于容器的类型和配置。 * 不要设置CPU限制太高,因为这可能会导致应用响应延迟。 * 在使用CPU限制时,应考虑其他因素,例如语言自身runtime的线程数量。

正文

使用prometheus来避免Kubernetes CPU Limits造成的事故

译自:Using Prometheus to Avoid Disasters with Kubernetes CPU Limits

本文将介绍Kubernetes的resource limits是如何工作的、使用哪些metrics来设置正确的limits值、以及使用哪些指标来定位CPU抑制的问题。

将limits中的CPU解释为时间概念,可以方便地理解容器中的多线程是如何使用CPU时间的。

理解Limits

在配置limits时,我们会告诉Linux节点在一个特定的周期内一个容器应用的运行时长。这样做是为了保护节点上的其余负载不受任意一组进程占用过多 CPU 周期的影响。

limits的核并不是主板上的物理核,而是配置了单个容器内的一组进程或线程在容器短暂暂停(避免影响到其他应用)前的运行时长。这句话有点违反直觉,特别是在 Kubernetes 调度器级别上很容易出错,Kubernetes 调度器使用了物理核的概念。

kubernetes 调度器在执行调度的时候用的是节点上物理核的概念,但容器运行的时候,应该将limits配置的CPU 转换为CPU时间的概念。

Limits其实是时间

下面使用一个虚构的例子来解释这个概念。假设有一个单线程应用,该应用需要1秒CPU运行时间来完成一个事务,此时将limits配置为1 core或1000 millicores:

Resources:
  limits:
    cpu: 1000m 

如果该应用需要完整的1秒CPU运行时间来服务一个API调用,中间不能被停止或抑制,即在容器被抑制前需要允许该应用运行1000毫秒(ms)或1 CPU秒。

image

由于1000毫秒等同于1秒CPU运行时间,这就可以让该应用每秒不受限地运行一个完整的CPU秒,实际的工作方式更加微妙。我们将一个CPU秒称为一个周期(period),用来衡量时间块。

Linux Accounting system

Limits是一个记账系统(Accounting system),用于跟踪和限制一个容器在固定时间周期内使用的总vCPU数,该值作为可用运行时的全局池进行跟踪,一个容器可以在该周期内使用该池。上面陈述中有很多内容,下面对此进行分析。

回到周期或记账系统翻页频率的概念。我们需要跨多个 vCPU申请运行时间,这意味着需要将账簿的每页分为多个段,称为切片。Linux内核默认会将一个周期分为20个切片。image

假设我们需要运行半个周期,此时只需要将配额配置为一半数目的切片即可,在一个周期之后,记账系统会重置切片,并重启进程。

image

类似于requests或shares可以转换为表示 CPU 分配百分比的比率,也可以将limits转换为一个百分比。例如,容器的配额设置为半个周期,则配置为:

resources:
 limits:
   cpu: 500m

开始时,使用1000 milliCPU作为一个完整的share。当配置500 milliCPU时,使用了半个周期,或500m/1000m = 50%。如果设置了200m/1000m,则表示使用的CPU比率为20%,以此类推。我们需要这些转换数字来理解一些prometheus的指标输出。

上面提到的记账系统是按容器计算的,下面看下指标container_spec_cpu_period,与我们假设的实验不同,实际与容器相关的周期为100ms。

image

Linux有一个配置,称为cpu.cfs_period_us,设置了账簿翻到下一页前的时间,该值表示下一个周期创建前的微秒时间。这些Linux指标会通过cAdvisor转换为prometheus指标。

撇开一些特殊场景不谈,在账簿翻页之前经过的时间并不像被限制的 CPU时间切片那样重要。

下面看下使用cpu.cfs_quota_us指标设置的容器配额,这里配置为50毫秒,即100ms的一半:

image

多线程容器

容器通常具有多个处理线程,根据语言的不同,可能有数百个线程。

image

当这些线程/进程运行时,它们会调度不同的(可用)vCPU,Linux的记账系统需要全局跟踪谁在使用这些vCPU,以及需要将哪些内容添加到账簿中。

先不谈周期的概念,下面我们使用container_cpu_usage_seconds_total来跟踪一个应用的线程在1秒内使用的vCPU数。假设线程在4个 vCPU 上均运行了整整一秒钟,则说明其使用了4个vCPU秒。

如果总的vCPU时间小于1个vCPU秒会发生什么呢?此时会在该时间帧内抑制节点上该应用的其他线程的运行。

Global accounting

上面讨论了如何将一个vCPU秒切分为多个片,然后就可以全局地在多个vCPU上申请时间片。让我们回到上述例子(4个线程运行在4个vCPU上),进一步理解它们如何运行的。

当一个CPU需要运行其队列中的一个线程或进程时,它首先会确认容器的全局配额中是否有5ms的时间片,如果全局配额中有足够的时间片,则会启动线程,否则,该线程会被抑制并等待下一个周期。

image

真实场景

下面假设一个实验,假如有4个线程,每个线程需要100ms的CPU时间来完成一个任务,将所有所需的vCPU时间加起来,总计需要400ms或4000m,因此可以以此为进程配置limit来避免被抑制。

image

不幸的是,实际的负载并不是这样的。这些函数的线程可能运行重的或轻的API调用。应用所需的CPU时间是变化的,因此不能将其认为是一个固定的值。再进一步,4个线程可能并不会同时各需要一个vCPU,有可能某些线程需要等待数据库锁或其他条件就绪。

正因为如此,负载往往会突然爆发,因此延迟并不总是能够成为设置limits的候选因素。最新的一个特性--cpu.cfs_burst_us允许将部分未使用的配额由一个周期转至下一个周期。

有趣的是,这并不是让大多数客户陷入麻烦的地方。假设我们只是猜测了应用程序和测试需求,并且1个 CPU 秒听起来差不多是正确的。该容器的应用程序线程将分布到4个 vCPU 上。这样做的结果是将每个线程的全局配额分为100ms/4或25ms 的运行时。

image

而实际的总配额为(100ms 的配额) * (4个线程)或400ms 的配额。在100毫秒的现实时间里,所有线程有300毫秒处于空闲状态。因此,这些线程总共被抑制了300毫秒。

多核场景中,容器申请的CPU时间可能来自多个核。

如果容器内的线程比较多,且对CPU需求都比较大,有可能在极短时间内消耗掉单核或多核的单个CPU周期内的配额,导致线程在该CPU周期的剩余时间内阻塞,直到下一个周期,以此往复。

Latency

下面从应用的角度看下这些影响。单线程应用需要100ms来完成一个任务,当设置的配额为100ms或1000 m/1000 m = 100%,此时设置了一个合理的limits,且没有抑制。

image

在第二个例子中,我们猜测错误,并将limits设置为400m或400 m/1000 m = 40%,此时的配额为100ms周期中的40ms。下图展示该配置了对该应用的延迟:

image

此时处理相同请求的时间翻倍(220ms)。该应用在三个统计周期中的两个周期内受到了抑制。在这两个周期中,应用被抑制了60ms。更重要的是,如果没有其他需要处理的线程,vCPU将会被浪费,这不仅仅会降低应用的处理速度,也会降低CPU的利用率。

与limits相关的最常见的指标container_cpu_cfs_throttled_periods_total展示了被抑制的周期,container_cpu_cfs_periods_total则给出了总的可用周期。上例中,三分之二(66%)的周期被抑制了。

那么,如何知道limits应该增加多少呢?

Throttled seconds

幸运的是,cAdvisor提供了一个指标container_cpu_cfs_throttled_seconds_total,它会累加所有被抑制的5ms时间片,并让我们知道该进程超出配额的数量。指标的单位是秒。

通过如下表达式可以找出CPU使用超过100ms的前三个pods。

topk(3, max by (pod, container)(rate(container_cpu_usage_seconds_total{image!="", instance="$instance"}[$__rate_interval]))) / 10

下面做一个实验:使用sysbench启动一个现实时间100ms中需要400ms CPU时间的的4线程应用。

          command:
            - sysbench
            - cpu
            - --threads=4
            - --time=0
            - run

可以观测到使用了400ms的vCPU:

image

下面对该容器添加limits限制:

          resources:
            limits:
              cpu: 2000m
              memory: 128Mi

可以看到总的 CPU 使用在100ms 的现实时间中减少了一半,这正是我们所期望的。

image

PromQL 给出了每秒的抑制情况,每秒有10个周期(每个周期默认100ms)。为了得到每个周期的抑制情况,需要除以10。假设如下表达式的结果为1.5s,即1500ms,说明每秒钟,容器中的线程总计被抑制了1500ms,因此需要每秒它需要1500ms的CPU时间才能防止不被抑制,因此将limit增加1500m即可:

topk(3, max by (pod, container)(rate(container_cpu_cfs_throttled_seconds_total{image!="", instance="$instance"}[$__rate_interval]))) / 10

告警设置

可以基于CPU抑制时间或抑制比率来编写告警表达式,其表达的也是CPU的饱和度信息:

# CPU抑制时间超过1s时产生告警
rate(container_cpu_cfs_throttled_seconds_total{namespace=~"wordpress-.*"}[1m]) > 1
# CPU抑制周期占可用周期的一半时产生告警(推荐)
sum(increase(container_cpu_cfs_throttled_periods_total{container!=""}[5m])) by (container, pod, namespace)/sum(increase(container_cpu_cfs_periods_total{}[5m])) by (container, pod, namespace) *100 > 50

推荐使用抑制周期占比作为告警表达式,container_cpu_cfs_throttled_seconds_total表达的是容器内所有线程被抑制的时间之和,因而它的值可能会大于每秒的周期时间(默认为1s),如果容器内的线程过多,该值可能会非常大。

上述cadvisor中的抑制指标均来自节点的/sys/fs/cgroup/cpu/kubepods/podxxxxx/cpu.stat文件,对应如下内容:

  • nr_periods – cgroup可使用的周期数
  • nr_throttled – cgroup被抑制的周期数
  • throttled_time – cgroup中的线程被抑制的时间之和

总结

本文介绍了limits是如何工作的,以及可以使用哪些指标来设置正确的值,使用哪些指标来进行抑制类型的问题定位。本文的实验提出了一个观点,即过多地配置limits的vCPU数也可能会导致vCPU处于idle状态而造成应用响应延迟,但在现实的服务中,一般会包含语言自身runtime的线程(如go和java)以及开发者自己启动的线程,一般设置较多的vCPU不会对应用的响应造成影响,但会造成资源浪费。

参考

与使用prometheus来避免Kubernetes CPU Limits造成的事故相似的内容:

使用prometheus来避免Kubernetes CPU Limits造成的事故

使用prometheus来避免Kubernetes CPU Limits造成的事故 译自:Using Prometheus to Avoid Disasters with Kubernetes CPU Limits 本文将介绍Kubernetes的resource limits是如何工作的、使用哪些

[转帖]使用prometheus来避免Kubernetes CPU Limits造成的事故

https://www.cnblogs.com/charlieroro/p/17074808.html 译自:Using Prometheus to Avoid Disasters with Kubernetes CPU Limits 本文将介绍Kubernetes的resource limits是

[转帖]使用Prometheus监控bind9的DNS服务

https://www.cnblogs.com/charlieroro/p/11013428.html 首先编译bind_exporter,编译方式参见bind_exporter 创建一个systemd配置文件来运行bind_exporter vi /etc/systemd/system/bind_

iftop的学习与使用

iftop的学习与使用 背景 前段时间一直进行netperf 等网络性能验证工具的学习与使用. 监控很多时候采用了 node-exporter + prometheus + grafana来进行观察 但是到了一些特殊项目现场. 感觉grafana的大屏展示模式,其实存在很多不太优雅的地方. 还是需要

[转帖]Promethues + Grafana + AlertManager使用总结

Prometheus是一个开源监控报警系统和时序列数据库,通常会使用Grafana来美化数据展示。 1|01. 监控系统基础架 1|11.1核心组件 Prometheus Server, 主要用于抓取数据和存储时序数据,另外还提供查询和 Alert Rule 配置管理。 exporters ,数据采

Grafana 系列-统一展示-3-Prometheus 仪表板

系列文章 Grafana 系列文章 知识储备 Prometheus Template Variables 你可以使用变量来代替硬编码的细节,如 server、app 和 pod_name 在 metric 查询中。Grafana 在仪表盘顶部的下拉选择框中列出这些变量,帮助你改变仪表盘中显示的数据。

使用 Grafana 统一监控展示-对接 Zabbix

概述 在某些情况下,Metrics 监控的 2 大顶流: Zabbix: 用于非容器的虚拟机环境 Prometheus: 用于容器的云原生环境 是共存的。但是在这种情况下,统一监控展示就不太方便,本文介绍利用 Grafana 对接 Zabbix, 来作为统一监控展示端。Let's go! 在这里,主

[转帖]使用Prometheus和Grafana监控RabbitMQ集群 (使用RabbitMQ自带插件)

https://www.cnblogs.com/hahaha111122222/p/15683696.html 配置RabbitMQ集群 官方文档:https://www.rabbitmq.com/prometheus.html#quick-start 官方github地址:https://gith

[转帖]使用Prometheus监控snmp

获取snmp信息 首先获取需要监控的snmp的基本信息,假设基本信息如下: snmp服务IP: 1.1.1.1 snmp community: public snmp exportor部署地址: 2.2.2.2 配置snmp exporter 从官方下载snmp exporter的可执行文件。 此外

使用Prometheus监控docker compose方式部署的ES

需求 收集 ES 的指标, 并进行展示和告警; 现状 ES 通过 docker compose 安装 所在环境的 K8S 集群有 Prometheus 和 AlertManager 及 Grafana 方案 复用现有的监控体系, 通过: Prometheus 监控 ES. 具体实现为: 采集端 el