解决 Kubernetes CPU 使用与 Docker 容器指标冲突的问题

52次阅读
没有评论

问题描述

最近将他们的生产环境切换到了 Kubernetes。他想要在容器上强制执行 CPU 限制。然而,他发现得到的 CPU 指标存在冲突,无法吻合。以下是他的设置:

  • 使用 Daemonset 运行的 DataDog 代理
  • 正在运行没有 CPU 限制的现有应用程序
  • 相关容器是多线程 Ruby 应用程序
  • 两个指标:kubernetes.cpu.usage.{avg,max} 和 docker.cpu.usage
  • c4.xlarge 集群节点(在 Kubernetes 术语中为 4 vCPUs 或 4000m)

kubernetes.cpu.usage.max 报告相关容器的大约 600m。docker.cpu.usage 报告相关容器的约 60% 使用率。由此可知,在正常运行情况下,1000m 的 CPU 限制将足够容纳。

然而,当将限制设置为 1000m 后,docker.container.throttles 明显增加,而 kubernetes.cpu.usage.max 和 docker.cpu.usage 保持不变。系统在此期间变得不稳定。这对我来说没有任何意义。

用户研究了 Docker 统计信息。似乎 docker stats 命令(以及底层 API)根据 CPU 核心来进行负载规范化。在用户的情况下,docker.cpu.usage 的 60% 相当于 Kubernetes 术语中的 2400m(4000m * 0.60)。然而,这与任何 Kubernetes 数字都不符合。用户进行了另一个实验,以测试 Kubernetes 的数字是否错误。他将限制设置为 2600m(为了获得额外的冗余)。这并没有导致任何限制,但 Kubernetes 观察到的 CPU 使用情况并未改变。这让用户感到困惑。

因此,用户的问题是:

  1. 这是否感觉像是 Kubernetes 中的一个 bug(或者是堆栈中的某个问题)?
  2. 我的理解是否正确?

用户的后续问题涉及如何正确确定 Ruby 应用程序的 CPU 使用情况。一个容器使用 Puma,这是一个具有可配置线程数量的多线程 Web 服务器。HTTP 请求由其中一个线程处理。第二个应用程序是使用线程服务器的 Thrift 服务器。每个传入的 TCP 连接由其自己的线程处理。当连接关闭时,线程退出。Ruby 有全局解释器锁(GIL),因此一次只能执行一个线程的 Ruby 代码。这允许多个线程执行 IO 等操作。

用户认为最好的方法是限制每个应用程序中运行的线程数,并基于线程数量来近似 Kubernetes 的 CPU 限制。这些进程不进行分叉,因此很难预测总的 CPU 使用情况。

问题是:如何正确预测这些应用程序的 CPU 使用情况和限制?

解决方案

以下解决方案可能涉及版本差异和需要谨慎操作,请在执行前做好备份。

解决方案1:核心问题分析与解决

首先,您的疑惑是完全合理的。问题可能不在于 Kubernetes 或 Docker 本身,而是在于如何正确地解释和计算 CPU 使用率。

步骤1:了解 AWS EC2 虚拟化架构

您提到的 CPU 使用率冲突可能涉及到 AWS EC2 的虚拟化架构。AWS EC2 实例的虚拟 CPU 并不等同于实际的物理 CPU 核心。在 EC2 中,虚拟 CPU 是基于实际物理核心的超线程技术产生的。这可能导致您观察到的 CPU 使用率在不同指标中有所不同。

步骤2:CPU 使用率的多样性

您提到的不同 CPU 使用率指标可能是由于不同层级的计算所导致的。Kubernetes 和 Docker 可能在不同的层级上计算 CPU 使用率,从而导致差异。比如,Kubernetes 可能根据实际的 vCPU 使用来计算,而 Docker 则可能基于容器内核的 CPU 使用来计算。

步骤3:考虑限制和线程数

针对您的 Ruby 应用程序,您提到可以考虑限制每个应用程序中运行的线程数,并基于线程数量来近似 Kubernetes 的 CPU 限制。这是一个很好的思路,但需要进行实验和测试,以找到适合您应用程序的最佳配置。

解决方案总结

总之,解决这个问题需要更深入的分析和实验。您可以考虑以下步骤:

  • 在不同配置下进行实验,例如在 1CPU 节点和 2CPU 节点上测试,以观察 CPU 数值的相关性。
  • 使用 htop 等工具观察容器和主机的实际 CPU 使用情况,将其与 CloudWatch 等指标进行比较。
  • 考虑 CPU 超线程技术和 EC2 实例的虚拟化架构对 CPU 使用率的影响。

解决方案2:容器 CPU Pinning

另一种方法是尝试将容器的 CPU 核心进行 Pinning,即将容器限制在特定的 CPU 核心上运行。这可以通过 Docker 的 --cpuset 参数来实现。在 AWS EC2 实例上,您可以将容器钉在虚拟 CPU 上,以观察其行为。

解决方案3:容器运行时优

正文完