# Kubernetes：移除 CPU 限制，服务运行更快

原文：[Kubernetes: Make your services faster by removing CPU limits](https://erickhun.com/posts/kubernetes-faster-services-no-cpu-limits/)

作者：[ERIC KHUN](https://erickhun.com/)

我们（Buffer）早在 2016 年就[开始使用 Kubernetes](https://kubernetes.io/case-studies/buffer/) 了。我们使用 [kops](https://kops.sigs.k8s.io/) 对 Kubernetes 集群进行管理，其中包含了大约 60 个运行在 AWS 的节点，运行着 1500 个左右的容器。我们的微服务迁移之路充满坎坷。在和 Kubernetes 相处多年以后，我们还是会时不时遭到它的毒打。本文接下来要讨论的案例就是这样——**CPU Limit** 是一头披着狼皮的羊。

## CPU 限制和流控

[Google 等公司强烈建议](https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits)设置 CPU 限制。如果不进行这一限制，节点上的容器可能会耗尽所有 CPU 资源，这可能会引发多种意料之外的事故——例如导致 Kubernetes 关键进程（比如说 `kubelet`）停止响应。因此理论上为容器设置 CPU 限制能够很好的对节点进行保护。

该特性能限制一个容器在给定周期内（缺省为 100 毫秒）能够消耗的最大 CPU 时间。受限的容器永远无法消耗超过限制的 CPU 资源。Kubernetes 使用 [CFS 配额](https://en.wikipedia.org/wiki/Completely_Fair_Scheduler) 技术对容器进行流控，制止其使用超限的 CPU 资源。也就是说 CPU 的使用受到了限制，会让容器变慢（可能增加响应时间）。

## 不设置 CPU 限制会怎样

我们很不幸地遭遇了这种情况，在每个节点上运行的 `kubelet` 进程停止响应。此时节点进入 `NotReady` 状态，并且其上运行的容器被重新调度到其它节点，又引发其它节点的问题——这可能不是我们希望的情况。

## 发现流控和延迟的问题

容器运行过程中又一个关键指标就是 `throttling`。该指标显示了你的容器受到流控的次数。我们有一个奇怪的发现：不管容器的 CPU 消耗是否逼近了上限，都会遇到流控。下图是我们一个主要 API 的表现：

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745913710007/aa580587-ae20-43c9-ba0e-469a85567116.png align="center")

这里的 CPU 上限设置为 `800m`，而实际运行的最大值仅为 `200m`。如此一来我们似乎应该认为，CPU 资源足够，无需限流。实际情况呢？

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745913724889/b192f950-5673-460a-83ca-e3aecf8c5842.gif align="center")

这里可以看到，虽说 CPU 的消耗远没有达到上限附近，还是发生了限流。

我们找到一些资源（[github issue](https://github.com/kubernetes/kubernetes/issues/67577)、[Zalando 的分享](https://www.youtube.com/watch?v=LpFApeaGv7A&feature=youtu.be&t=1204)、[omio 的帖子](https://medium.com/omio-engineering/cpu-limits-and-aggressive-throttling-in-kubernetes-c5b20bd8a718)），都说到了 CPU 限流引起服务响应变慢的问题。

**为什么在 CPU 消耗不多的时候还是触发了限流呢？**，长话短说的话，这是一个 Linux 内核的 Bug，他会对设置了 CPU 限制的容器进行不必要的流控。如果对其中的细节感兴趣，我们推荐你看看 Dave Chiluk 的[精彩演讲](https://www.youtube.com/watch?v=UE7QX98-kO0)，相关的还有一篇[文字稿](https://engineering.indeedblog.com/blog/2019/12/unthrottled-fixing-cpu-limits-in-the-cloud/)，其中涉及到了更多细节。

## 移除 CPU 限制（有副作用）

经过多次讨论，我们决定删掉所有关键服务上的 CPU 限制。

事关集群稳定，这是一个艰难的决定。我们的在集群的测试中出现过一些不稳定的情况，部分服务占用过多资源，破坏了同一节点内的其它服务。

## 如何在去除限制之后保障集群稳定性

### 隔离不限制 CPU 的服务

因为有的服务会占用太多资源，导致节点进入 `NotReady` 状态。我们决定把一些这些服务放到特定节点上（`taint`），如此一来，不受限的服务就不会干扰到受限服务，让我们可以轻易地分辨节点的故障原因。

![Buffer k8s nodes infrastructure](https://erickhun.com/img/kubernetes-cpu-limits/buffer-k8s-infrastructure-nodes.jpg align="left")

### 为容器分配合适的 CPU 和内存 requests

我们最担心的事情就是服务占用太多资源导致节点不可用。然而我们使用 Datadog 让集群变得易于监控，我们花了几个月的时间，对每个我们希望放开限制的服务的运行情况进行观察，根据监控结果，我们将这些容器的 CPU 请求值设置为观测到的上限的 120%。这样就保障了容器的资源需求。

![Chose CPU request based on max](https://erickhun.com/img/kubernetes-cpu-limits/choose-cpu-request-based-on-max.png align="left")

上图可见，CPU 用量的峰值为 `242m`，我们就根据这个峰值来设置其 CPU 请求值。这个服务是面向用户的，因此其资源使用的波动和流量波动是相匹配的。

同样的方法可以用在内存的设置上。如果觉得还不放心，可以使用 HPA 来增强服务的弹性，并在节点资源不足时告警，或者使用集群的自动扩容能力。

这个操作客观上会降低[容器密度](https://wiki.openvz.org/WP/Containers_density)。

## 结果

在几周的调整之后，我们得到了下表的结果，表格中可以看到，所有服务的延迟都大大降低了。

![faster-kubernetes-containers](https://erickhun.com/img/kubernetes-cpu-limits/speedup-no-cpu-limits2.png align="left")

我们的着陆页面（[buffer.com](https://buffer.com/)），其响应速度提高了 22 倍。

![buffer.com speedup without cpu limits](https://erickhun.com/img/kubernetes-cpu-limits/no-cpu-limit-speedup-buffer-com.jpg align="left")

## 这个内核问题修复了么

4.19 或更高版本的 Linux 发行版已经[纠正了该问题](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=763a9ec06c4)（再次感谢 [Dave Chiluk](https://twitter.com/dchiluk) 发现并解决了这个问题）。

然而直到 2020 年 9 月 2 号，阅读[相关 Issue](https://github.com/kubernetes/kubernetes/issues/67577) 时，我们还是看到很多 Linux 项目在引用这个问题，因此我猜测还有一些 LInux 发行版存在该问题。

如果你使用的 Linux 发行版使用的内核小于 4.19，建议为节点进行内核升级。但是任何情况下，我们都建议读者删掉 CPU 限制，看看监控中的流控数据。下面的列表并不完全：

* Debian：[Buster](https://www.debian.org/releases/buster/) 版本[最近](https://tracker.debian.org/news/1167353/accepted-linux-latest-419-105deb10u5deb9u1-source-amd64-into-oldstable-oldstable/) 进行了修复，可能有些早期版本也做了补丁。
    
* Ubuntu：[Ubuntu Focal Fosa 20.04](https://releases.ubuntu.com/20.04/) 已经修复。
    
* EKS 在 [2019 年](https://github.com/aws/containers-roadmap/issues/175)就修复了这个问题。如果版本落后的话，建议升级一下 AMI。
    
* kops 在 [2020 年 6 月](https://github.com/kubernetes/kops/pull/9283)的 `kops 1.18` 中开始使用 `Ubuntu 20.04` 作为缺省的主机镜像。然而如果使用的是低版本 kops，可能就需要等着补丁发布了（我们也在等）。
    
* GKE：[在 2020 年 1 月](https://cloud.google.com/container-optimized-os/docs/release-notes#cos-stable-77-12371-141-0) 修复了这个问题。但是看起来这个情况还[时有发生](https://news.ycombinator.com/item?id=24356903)
    

### 这样就解决了么

我不确定是否完全解决。我希望拿到修复后的内核之后，尝试根据本文内容进行实现。

## 总结

* 如果你在 Linux 下运行 Docker 容器（不管是Kubernetes/Mesos/Swarm），你可能会因为节流而导致容器表现不佳。
    
* 把你的发行版升级到最新版本，希望这个错误得到修复
    
* 取消 CPU 限制是解决这个问题的一个办法，但这是很危险的，应该格外小心（最好先升级你的内核，并先监控节流）。
    
* 如果你取消了 CPU 限制，请仔细监控节点中 CPU 和内存的使用情况，并确保你的 CPU 请求够大，一个安全的方法是，如果资源使用量大，就使用 HPA 来创建新的 Pod，这样 Kubernetes 就会安排在空间富余的节点中了。
