在Buffer公司,我们从2016年就开始使用Kubernetes。我们一直使用kops管理我们在AWS云上Kubernetes集群,它有大约60个节点,运行大约1500个容器。我们在向微服务体系结构的过渡过程中充满了尝试和错误,即便使用Kubernetes的几年之后,我们仍在探索它的秘密,本篇文章将讨论一个我们认为不错的实践经验,即CPU使用限制。

CPU使用限制和节流

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

包括谷歌在内的一些大公司都强烈推荐在应用环境中设置CPU使用限制。因为不设置CPU限制的典型危害就是节点中运行的容器可能耗尽所有可用的CPU资源,从而触发一连串严重的连锁事件反应,例如导致kubelet之类的关键进程无响应等。因此,从理论上讲,设置CPU限制来保护节点的做法是至关重要的。

CPU使用限制是容器在给定时间段内可以使用的最大CPU时间(默认为100ms)。这个人工指定的限制是不允许被超过的。Kubernetes采用一种称为CFS配额的机制来控制容器CPU使用不会超越这个限制值。这也意味着CPU使用将被人为地限制,从而导致容器在高计算情况下发生性能下降(如延迟更大)。

如果不设置CPU限制将会发生什么?

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

我们很不幸地经历了这个问题。运行在每个节点上负责管理节点Pod容器的kubelet进程变得不响应,整个节点变成NotReady状态,节点内的Pod容器被调度到其他节点,并在新节点中再次触发新的问题事件。

发现CPU节流和延迟问题

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

在容器的运行过程中,通常要检查的一个关键指标是CPU节流,这指标表明容器被节流的CPU时间。有趣的是,我们在过程中发现无论CPU使用率是否接近限制,很多容器都执行了节流动作,以下是我们主要API的一个案例:

k8s 如何监控pod cpu使用率 k8s cpu限制_linux_04

如图所示:CPU限制被设置为800微核(0.8核),峰值使用率最多为200微核(0.2核)。数据对比发现,我们认为还有足够的CPU资源可以让服务使用。是否真是推论的这样呢?接下来再看看这个:

k8s 如何监控pod cpu使用率 k8s cpu限制_linux_05

如图所示,我们可以发现,即使CPU使用率低于限制值,甚至最大CPU使用远没有接近CPU限制,也服务也出现了CPU节流情况。

在讨论节流如何导致服务性能下降和延迟的问题,我们找到了部分资源(GitHub问题[1],zanado会话[2],omio推送[3]),通过查阅资料,我们发现与之相关的 Linux内核TLDR进程存在一个bug,它通过非必要的CPU限制功能制约了容器的资源使用。这个进程的详细信息,建议查阅Dave Chiluk的伟大的演讲[4],书面版本[5]。

删除CPU限制(谨慎操作)

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

我们内部经过多次长时间的讨论,最终决定取消用户关键路径上的所有直接或间接服务的CPU限制。这决定并不轻松,因为这个决定关系到我们向来都很重视的集群稳定性。我们曾经经历过集群不稳定的情况,即某些服务使用了太多的资源,并影响了同节点中运行的所有其他服务。但与当时情况不同的是,这次我们更清楚我们服务的资源需求,并制定了一个很好的策略来推行它。

k8s 如何监控pod cpu使用率 k8s cpu限制_docker_07

如何保证删除CPU使用限制后的节点安全

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

隔离“无CPU限制”服务

我们过去看到某些节点变成“notReady”状态,主要是因为节点内的有些服务占用了太多资源。因此,我们决定将这些高消耗的服务放在特定的节点(污染节点)上,从而让这些服务就不会影响到所有“边界清晰”的节点。我们采用更好控制措施,更容易地发现存在问题的节点,从而标识出污染节点,并对超限使用的服务添加缓冲措施来提升服务的可靠性。实现过程请查看文档[6]。

k8s 如何监控pod cpu使用率 k8s cpu限制_python_09

正确分配的CPU和内存

我们的主要担忧是服务消耗了太多的资源,从而导致节点没有响应。通过使用Datadog工具,使我们对集群中运行的所有服务具有非常好的观察力,所以我对希望去除限制的每个服务,开展了几个月的资源使用分析。我按照服务所请求CPU资源的1.2倍,设置了服务的最大CPU使用。这将确保在Kubernetes不尝试调度同一节点中的任何其他服务的情况下,为节点上的服务分配了足够的资源。

k8s 如何监控pod cpu使用率 k8s cpu限制_linux_10

从图中可以看到CPU峰值使用率为242m CPU核心(0.242 CPU核心)。我们只需将最大CPU使用的设置数字提高一点即可。同时还可以注意到,由于服务是面向用户的,因此CPU峰值使用率与峰值流量时间是相互匹配的。

对内存的请求和使用情况也执行同样的操作策略。为了提升服务的安全性,在容器内资源使用率过高时,可以使用Pod的自动水平伸缩功能来创建新的Pod,Kubernetes将自动将新建的Pod调度到资源充足的节点中。除此之外,我们还建议为集群设计资源使用告警,或者使用节点的自动伸缩功能为资源不够的集群自动添加计算节点。

上述策略所带来的缺点是降低了节点内的“容器密度”,即在单个节点中运行的容器数量会少一些。在低流量期间,可能会出现大量的“闲置块”。即便如此,你还可能遇到一些CPU使用率过高的情况,但是节点自动伸缩功能可以解决这种类型的问题。

结果

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

经过几周的试验,我很高兴发布非常棒的结果,我们已经看到了我们所调整的全部服务在延迟方面的显著改进,数据如下图所示:

k8s 如何监控pod cpu使用率 k8s cpu限制_linux_12

其中,我们的主登陆页面(buffer.com)服务提升最大,服务响应速度提高了22倍。

k8s 如何监控pod cpu使用率 k8s cpu限制_java_13

Linux内核bug的修复情况

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

确认这个bug已经被修复,并被合并到运行4.19或更高版本的Linux内核中。然而,截止到2020年9月2日,我们阅读Kubernetes的问题资料时,仍然可以看到多个Linux项目还在引用这个问题,所以猜测某些Linux发行版仍然存在这个bug,并且正在修复集成中。

如果你的节点所用的内核版本低于4.19,那建议升级到最新的Linux发行版。并且在任何情况下,都应该尝试删除CPU限制来验证是否存在任何CPU或内存限制。以下为各种托管的Kubernetes服务或Linux发行版的修复情况:

  • Debian:最新版本已经修复,发布日期为2020年8月,部分更早版本可能已经被修补;
  • Ubuntu:最新版本Ubuntu Focal Fosa 20.04已经修复;
  • EKS自2019年12月修复;
  • kops:从2020年6月修复,kops 1.18+将使用Ubuntu 20.04作为默认系统映像;
  • GKE(Google云):不确定,猜测可能已经修复。

附:具体日期不完全肯定,欢迎实践信息反馈。同时,Bug修复是否解决了节流问题?我不确定这个问题是否完全解决了。我将尝试对修复过的内核版本进行验证,并根据实践情况相应更新这篇文章。如果有人做了,也很期待结果分享。

要点总结

k8s 如何监控pod cpu使用率 k8s cpu限制_linux

  • 如果你在Linux下运行Docker容器(无论是Kubernetes/Mesos/Swarm),你的容器都可能会因为CPU节流限制而导致性能表现不佳。
  • 升级软件发行版的最新版本,以期望bug被修复。
  • 消除CPU限制是解决这个问题的一个解决方案,但存在高风险,应该格外小心,建议先升级内核和调节性能监视器。
  • 如果删除了CPU限制,需仔细监控节点的CPU和内存使用情况,并确保CPU资源请求可以得到满足。
  • 推荐采用Pod的水平伸缩功能,如果资源使用率很高,可以利用Kubernetes自动创Pod,并调度到资源充足的节点上。