本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 性能测量和路线图

无论您的容器编排系统多么灵活可靠,最终您总有一些工作要做,并且希望它能快速完成。对于大问题,一个常见的答案是投入更多的机器来解决。毕竟,更多的计算力 = 更快,对吗?

有趣的是,添加更多节点有点像火箭方程的暴政——在某些系统中,添加更多机器实际上会使您的处理速度变慢。然而,与火箭方程不同,我们可以做得更好。Kubernetes 在 v1.0 版本中支持多达 100 个节点的集群。然而,我们的目标是到 2015 年底将支持的节点数量增加 10 倍。这篇博文将介绍我们目前的情况以及我们打算如何实现更高水平的性能。

我们测量什么?

我们需要回答的第一个问题是:“Kubernetes 能够管理一个 N 节点集群意味着什么?”用户期望它能“合理快速地”处理所有操作,但我们需要一个精确的定义。我们决定根据以下两个指标来定义性能和可扩展性目标:

  1. 1.“API 响应时间”:我们 99% 的 API 调用在 1 秒内返回。

  2. 2.“Pod 启动时间”:99% 的 Pod(预拉取镜像)在 5 秒内启动。

请注意,对于“Pod 启动时间”,我们明确假设运行 Pod 所需的所有镜像都已预先拉取到将要运行的机器上。在我们的实验中,不同镜像之间存在高度可变性(网络吞吐量、镜像大小等),这些变异与 Kubernetes 的整体性能关系不大。

选择这些指标的决定是基于我们在 Google 每周启动 20 亿个容器的经验。我们明确希望测量面向用户流程的延迟,因为这才是客户真正关心的。

我们如何测量?

为了监控性能改进和检测回归,我们建立了持续测试基础设施。每隔 2-3 小时,我们就会从 HEAD 创建一个 100 节点集群,并在其上运行我们的可扩展性测试。我们使用一台 GCE n1-standard-4(4 核,15GB 内存)机器作为主节点,使用 n1-standard-1(1 核,3.75GB 内存)机器作为节点。

在可扩展性测试中,我们明确只关注满集群情况(满 N 节点集群是指运行着 30 * N 个 Pod 的集群),这是从性能角度来看最苛刻的场景。为了重现客户可能实际操作的情况,我们执行以下步骤:

  • 填充 Pod 和副本控制器以填满集群

  • 生成一些负载(创建/删除额外的 Pod 和/或副本控制器,扩展现有 Pod 和/或副本控制器等),并记录性能指标

  • 停止所有正在运行的 Pod 和副本控制器

  • 抓取指标并检查它们是否符合我们的预期

值得强调的是,测试的主要部分是在满集群(每节点 30 个 Pod,共 100 个节点)上进行的——在一个空集群中启动 Pod,即使它有 100 个节点,也会快得多。

为了测量 Pod 启动延迟,我们使用了非常简单的 Pod,只有一个容器运行“gcr.io/google_containers/pause:go”镜像,该镜像启动后会一直休眠。该容器保证已预拉取到节点上(我们将其用作所谓的 Pod 基础设施容器)。

性能数据

下表包含 100 节点集群中 Pod 启动时间的百分位数(第 50、90 和 99 百分位),集群负载分别为 10%、25%、50% 和 100%。

10% 满25% 满50% 满100% 满
第 50 百分位.90 秒1.08 秒1.33 秒1.94 秒
第 90 百分位1.29 秒1.49 秒1.72 秒2.50 秒
第 99 百分位1.59 秒1.86 秒2.56 秒4.32 秒

至于 API 响应时间,以下图表显示了按操作类型和资源类型分组的 API 调用延迟的第 50、90 和 99 百分位。但是请注意,这还包括内部系统 API 调用,而不仅仅是用户发出的调用(在本例中由测试本身发出)。

get.pngput.png

delete.pngpost.png

list.png

某些资源仅出现在特定图表上,具体取决于该操作期间正在运行的内容(例如,当时没有放置命名空间)。

从结果中可以看出,我们 100 节点集群的目标提前完成了,即使在满载集群中,Pod 启动时间在第 99 百分位也比 5 秒快 14%。值得指出的是,LIST Pod 比任何其他操作都要慢得多。这是有道理的:在一个满载的集群中,有 3000 个 Pod,每个 Pod 大约几千字节数据,这意味着每次 LIST 操作都需要处理数兆字节的数据。

#####已完成的工作和未来计划

使 100 节点集群足够稳定以运行任何测试的初始性能工作涉及许多小的修复和调整,包括增加 apiserver 中的文件描述符限制以及在不同请求之间重用与 etcd 的 tcp 连接。

然而,构建一个稳定的性能测试只是将集群支持的节点数量增加十倍的第一步。作为这项工作的结果,我们已经付出了巨大的努力来消除未来的瓶颈,包括:

  • 将控制器重写为基于 watch 的:以前它们每隔几秒重新列出给定类型的对象,这给 apiserver 带来了巨大的负载。

  • 使用代码生成器生成转换和深拷贝函数:虽然使用 Go 反射的默认实现非常方便,但事实证明它们非常慢,与生成的代码相比慢了 10 倍。

  • 在 apiserver 中添加缓存,以避免多次读取 etcd 中的相同数据时进行反序列化

  • 降低更新状态的频率:考虑到状态变化缓慢的性质,仅在状态发生变化时更新 Pod 状态,并且仅每 10 秒更新一次节点状态才有意义。

  • 在 apiserver 中实现 watch 而不是将请求重定向到 etcd:我们希望避免多次从 etcd watch 相同的数据,因为在许多情况下,无论如何它都会在 apiserver 中被过滤掉。

展望我们 1000 节点集群的目标,建议的改进包括:

  • 将事件从 etcd 移出:它们更像是系统日志,既不是系统状态的一部分,也不是 Kubernetes 正常工作的关键。

  • 使用更好的 JSON 解析器:Go 中实现的默认解析器非常慢,因为它基于反射。

  • 重写调度器以使其更高效和并发

  • 提高 apiserver 和 Kubelet 之间通信的效率:特别是,我们计划减少每次更新节点状态时发送的数据大小。

这绝不是一个详尽的列表。我们将根据运行现有可扩展性测试和新创建的测试时观察到的瓶颈添加新元素(或删除现有元素)。如果您有特定的用例或场景希望我们解决,请加入我们!

  • 我们每周四太平洋时间上午 11 点举行 Kubernetes 规模特别兴趣小组会议,讨论正在进行的问题以及性能跟踪和改进计划。
  • 如果您在此之前有特定的性能或可扩展性问题,请加入我们在 Slack 上的可扩展性特别兴趣小组:https://kubernetes.slack.com/messages/sig-scale
  • 一般问题?随时加入我们在 Slack 上的 Kubernetes 社区:https://kubernetes.slack.com/messages/kubernetes-users/
  • 提交拉取请求或提出问题!您可以在我们的 GitHub 仓库中完成此操作。我们也热情鼓励大家通过自己的实验(及其结果)或拉取请求贡献来改进 Kubernetes。