理解Kubernetes中的CPU请求和限制

2023年 8月 14日 62.3k 0

image.png

在本文中,我们将探讨「请求」和「限制」的含义,以及它们如何转化为操作系统原语并如何执行,读者如果有Kubernetes和Linux的相关经验,这将会对你有所帮助。

资源管理基础

Kubernetes允许指定单个Pod需要多少CPU/RAM,并且如何限制给定Pod对这些资源的使用。这是通过资源部分下的请求和限制来实现的。

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: app
    image: my.private.registry/my-app
    resources:
      requests:
        memory: "64M"
        cpu: "250m"
      limits:
        memory: "128M"
        cpu: "500m"

在研究如何执行请求和限制之前,让我们先熟悉一下它们所衡量的单位。上面的例子中,容器应用程序的请求被指定为250毫核心和64兆字节,而限制则为500毫核心/120兆字节。

 内存单元

内存以字节为单位进行测量,单位非常直观。Kubernetes允许使用SI后缀,如k、M、G、T,分别表示千字节、兆字节、千兆字节和太字节。同时,还可以使用Ki、Mi、Gi、Ti表示基比字节、米比字节、吉比字节和太比字节,这是2的幂单位。

 CPU 单元

CPU资源的度量单位,你猜对了,就是CPU单位。1个CPU单位等同于1个物理核心(或虚拟核心,取决于集群运行的位置)。要指定CPU的分数,可以使用毫核单位,其中1个CPU = 1000m。

由于CPU是一种可压缩的资源,因此很难直观地确定这个单位对应的是什么,而且更令人困惑的是,请求和限制的含义略有不同。

CPU是一个绝对的单位,意味着无论一个节点有多少个核心,1个CPU始终是相同的。

 请求和安排

Kubernetes使用资源的请求部分来在节点上调度Pod,并确保Pod将获得所请求的资源量。

实际上,Pod中的每个容器都有指定的资源,但为了简单起见,我们假设我们的Pod只有一个容器。当Pod在节点上进行调度时,Kubernetes使用总值来进行调度。

例如,假设我们有以下3个Pods:

  • 应用程序1:250兆CPU和512兆内存
  • 应用程序2:300兆CPU和512兆内存
  • 应用程序3:350兆CPU和768兆内存

当Kubernetes尝试将Pod调度到具有1个vCPU和2G RAM的节点上时,所有的Pod都将适应,因为它们有足够的资源

image.png

但是如果我们改变对于App 3的请求,现在需要1G的内存呢?现在Kubernetes将无法将该Pod调度到节点上,因为资源不足

image.png

这很简单,有了RAM,我们很容易明白内存是如何根据Pod的请求分配的。但是CPU呢?250m到底是什么意思?好吧,让我们一起来探索一下Linux CPU调度器的深渊吧。

CPU请求、CPU份额和CFS

为了将资源分配给Pod的容器,Kubernetes在Linux上使用Cgroups和CFS(完全公平调度器)。简单来说,容器的所有进程/线程都在一个独立的Cgroup中运行,CFS根据指定的资源请求将CPU资源分配给这些Cgroups(稍等,很快就会清楚的……)。

然而,CFS使用的是CPU份额(CPU Shares),而不是Kubernetes的CPU单位(CPU Units)。为了将CPU单位转换为CPU份额,Kubernetes将1个CPU等同于1024个CPU份额。这意味着一个请求500m CPU的Pod将被分配512个CPU份额。而一个具有6个CPU的节点总共有6144个CPU份额。

image.png

好的。但是CPU份额是什么意思呢?一个Pod拥有512份额意味着什么呢?如果没有任何上下文,这些都毫无意义。份额是CFS用来在CPU资源争用时分配的相对单位。

当Pod几乎不工作或工作很少,CPU大部分时间处于空闲状态时,CFS并不关心每个Cgroup拥有多少份额。但是当多个Cgroup有可运行的任务,并且CPU资源不足时,CFS确保每个Cgroup相对于其拥有的份额获得CPU时间。而且,由于Kubernetes从CPU单位计算份额,它保证了Pod获得所请求的CPU资源。

...>_ 0

相对于限制:容器内存工作集字节数与kube_pod_container_resource_limits_memory_bytes的比率

sum(container_memory_working_set_bytes{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}[1s]) by (pod_name) / sum(kube_pod_container_resource_limits_memory_bytes{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}) by (pod_name)) > 0

 中央处理器

如前所述,CPU的请求/限制应以某个时间段的百分比形式呈现。鉴于此,为了监控CPU使用情况,我们可以使用容器中container_cpu_usage_seconds_total在1秒内的增加量作为实际使用的CPU时间的良好表示。

CPU使用量单位:irate(container_cpu_usage_seconds_total)

sum(irate(container_cpu_usage_seconds_total{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}[1s])) by (node, pod_name)

在节点上使用百分比:irate(container_cpu_usage_seconds_total)/ kube_node_status_allocatable_cpu_cores - 将显示节点上CPU使用的熟悉百分比。

sum(irate(container_cpu_usage_seconds_total{cluster_name="$cluster", node=~"$node", container_name!="POD", pod_name=~"^$app-.*"}[1s])) by (node) / on(node) group_left() kube_node_status_allocatable_cpu_cores{cluster_name="$cluster", node=~"$node"} OR on() vector(0)

相对于请求:(container_cpu_usage_seconds_total)/kube_pod_container_resource_requests_cpu_cores

sum(irate(container_cpu_usage_seconds_total{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}[1s])) by (pod_name) / sum(kube_pod_container_resource_requests_cpu_cores{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}) by (pod_name)) > 0

相对于限制:irate(container_cpu_usage_seconds_total) / kube_pod_container_resource_limits_cpu_cores

sum(irate(container_cpu_usage_seconds_total{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}[1s])) by (pod_name) / sum(kube_pod_container_resource_limits_cpu_cores{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*", container_name!="POD"}) by (pod_name)) > 0

限流:container_cpu_cfs_throttled_periods_total / container_cpu_cfs_periods_total

sum(rate(container_cpu_cfs_throttled_periods_total{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*"} / container_cpu_cfs_periods_total{cluster_name="$cluster", namespace="$namespace", pod_name=~"^$app-.*"})) by (pod_name)

从cgroup/cpu.stat中收集到的限流值。

  • nr_periods = container_cpu_cfs_periods_total 是指在 Cgroup 任务可运行时,经过的带宽控制周期数 (cpu.cfs_period_us)。
  • nr_throttled = container_cpu_cfs_throttled_periods_total — Cgroup中任务被限制的周期数
  • throttled_time = container_cpu_cfs_throttled_seconds_total 是指在 Cgroup 内部,各个线程被限制的总时间量

这些指标是应用程序可观察性的良好基础。建议监控这些数值,并根据需要调整请求/限制。

 总结起来

希望阅读完这篇文章后,你对Kubernetes中的CPU资源有了更好的理解,包括请求的计算和平衡以及限制的执行方式。

总结一下,以下是一些建议,帮助您在Kubernetes上顺利进行:

了解您的应用程序的线程模型,并根据容器化环境进行调整。

对于Node.JS应用程序:不要使用集群模块,因为它会创建不必要的线程。可以尝试调整libuv和v8线程的数量进行实验。

针对JVM应用程序:调整线程池和垃圾回收设置。

对于Golang应用程序:调整GOMAXPROCS或者直接使用Uber的automaxprocs包。

2. 测量CPU使用率,设置足够的CPU请求以处理高峰时段的生产流量。

进行性能测试,测量CPU使用率,并根据峰值时段的流量设置CPU请求。一个好的起点是将峰值时段的CPU使用率设置在CPU请求的70-80%左右。不要过度提供资源以应对所有的突发情况,这就是CPU限制的作用。

根据请求为应用程序设置CPU限制。

对于延迟敏感的应用程序,比如处理客户请求,将限制设置为请求量的2倍至4倍。请注意,Grafana仪表板的粒度不够细,可能会错过一些CPU使用率的峰值,因此建议观察CPU限制值并将其最小化。

对于后台应用程序,比如cron作业或异步事件处理,将请求数的限制设置为合理的x1.5-x2倍。请求数/限制的组合应该能够轻松处理高峰时段的流量,并考虑到一些额外的负载。

为应用程序添加更多的指标,尽可能多地进行测量,并在监控仪表板上展示出来。

通过node-exporter等工具收集的指标不够精细,可能会错过负载/延迟的峰值。

正确地收集应用内度量数据,利用滑动窗口、插值和其他技术使突发情况可见。不要依赖平均值,要使尾延迟可见——至少收集p50、p75、p99分位数。如果传入请求的大小/处理时间严重变化——确保将它们收集到单独的桶或计数器中,以免掩盖异常值和突发情况。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论