规划可扩缩性


本页面介绍构建可扩缩的 GKE 集群时的一般最佳实践。您可以将这些建议应用于所有集群和工作负载,实现最佳性能。对于您计划大规模扩缩的集群,这些建议尤为重要。最佳实践适用于负责预配基础架构的管理员以及准备 kubernetes 组件和工作负载的开发者。

什么是可扩缩性?

在 Kubernetes 集群中,可扩缩性是指集群在实现其服务等级目标 (SLO) 的同时扩大规模的能力。Kubernetes 也有自己的一组 SLO

Kubernetes 是一个复杂的系统,其扩缩能力取决于多种因素。其中一些因素包括节点池中节点的类型和数量、节点池的类型和数量、可用的 Pod 数量、资源分配给 Pod 的方式以及 Service 的数量或 Service 背后的后端数量。

可用性最佳实践

选择区域级或可用区级控制平面

由于架构差异,地区级集群更适合用来实现高可用性。区域级集群在一个区域的多个计算可用区有多个控制平面,而可用区级集群在单个计算可用区中有一个控制平面。

如果升级可用区级集群,则控制平面虚拟机会停机,在此期间,Kubernetes API 不可用,直到升级完成为止。

在区域级集群中,控制平面在集群维护(例如轮替 IP 地址、升级控制平面虚拟机或者调整集群或节点池的大小)期间仍然可用。升级区域级集群时,总共会有三分之二的控制平面虚拟机始终会在滚动升级期间运行,因此 Kubernetes API 仍然可用。同样,单地区故障不会导致区域级控制平面停机。

不过,可用性较高的地区级集群也有一些弊端:

  • 更改集群的配置会花费较长的时间,因为所做的更改必须传播到区域级集群中的所有主虚拟机,而不是地区级集群中的单个控制平面。

  • 您可能无法像区域级集群那样频繁地创建或升级可用区级集群。如果无法在其中一个可用区创建虚拟机(无论是因为容量不足还是因为其他暂时性问题),则您无法创建或升级集群。

由于存在这些弊端,因此地区级集群和区域级集群适用于不同的使用场景:

  • 如果您对可用性的要求较低,请使用可用区级集群快速创建或升级集群。
  • 如果可用性比灵活性更重要,请使用区域级集群。

创建集群时,请谨慎选择集群类型,因为您在创建集群后无法更改集群类型。如需作出调整,您必须创建新的集群,然后将流量迁移到该集群。您可以在集群之间迁移生产流量,但难以进行大规模迁移。

选择多区域或单区域节点池

如需实现高可用性,Kubernetes 控制平面及其节点需要分布在不同地区。GKE 提供了两种类型的节点池:单可用区节点池和多可用区节点池。

如需部署高可用性应用,请使用跨地区均匀分布节点的多地区节点池,将您的工作负载分布到某个区域中的多个计算地区

如果您的所有节点都位于同一个可用区,则当该可用区变为无法到达的可用区时,您将无法调度 Pod。使用多可用区节点池会有一些弊端:

  • GPU 只能在特定的地区中使用。您可能无法在区域的所有地区中获取这些 GPU。

  • 单个区域内各个可用区之间的往返延迟可能比单个区域内各个资源之间的往返延迟更高。对于大多数工作负载而言,这种差异应该无关紧要。

  • Compute Engine 价格页面提供了同一地区中的不同区域之间的出站流量价格。

扩缩最佳实践

基础架构

Kubernetes 工作负载需要网络、计算资源和存储空间。您需要提供足够的 CPU 和内存才能运行 Pod。不过,底层的基础架构有更多参数可能会影响 GKE 集群的性能和可扩缩性。

集群网络

使用 VPC 原生集群是网络默认设置,并且是设置新的 GKE 集群的推荐选项。VPC 原生集群可实现更大的工作负载、更多节点以及一些其他优势

在此模式下,VPC 网络的所有 Pod IP 地址都有一个次要范围。随后,系统会为每个节点分配其自身 Pod IP 地址的次要范围片段。这样,VPC 网络就可以自然而然地知道如何将流量路由到 Pod,而无需依赖于自定义路由。一个 VPC 网络最多可以连接 15,000 个虚拟机

另一种已弃用且支持不超过 1500 个节点的方法是使用基于路由的集群。基于路由的集群不适合大型工作负载。它会消耗 VPC 路由配额,并且缺少 VPC 原生网络的其他优势。其方法是向 VPC 网络中每个新节点的路由表添加新的自定义路由

专用集群

在常规 GKE 集群中,所有节点都具有公共 IP 地址。在专用集群中,节点仅具有内部 IP 地址,用于将节点隔离,防止其与互联网之间建立入站和出站连接。GKE 使用 VPC 网络对等互连将运行 Kubernetes API 服务器的虚拟机连接到集群的其余部分。这样便可以提高 GKE 控制平面和节点之间的吞吐量,因为流量使用专用 IP 地址进行路由。

使用专用集群具有额外的安全优势,即节点不会向互联网公开。

集群负载均衡

GKE Ingress 和 Cloud Load Balancing 配置和部署负载均衡器,以在集群外部及向公共互联网公开 Kubernetes 工作负载。GKE Ingress 和 Service 控制器会代表 GKE 工作负载部署转发规则、网址映射、后端服务和网络端点组等对象。这些资源都有固有的配额和限制,这些限制也适用于 GKE。如果任何特定 Cloud Load Balancing 资源达到其配额,它会阻止正确部署给定的 Ingress 或 Service,并且资源的事件中会出现错误。

下表介绍了使用 GKE Ingress 和 Service 时的扩缩限制:

负载均衡器 每个集群的节点限制
内部直通式网络负载均衡器
外部直通式网络负载均衡器 每个区域 1000 个节点
外部应用负载均衡器
内部应用负载均衡器 没有节点限制

如果您需要进一步扩大规模,请与您的 Google Cloud 销售团队联系以提升此限制界限。

DNS

GKE 中的服务发现通过 kube-dns 提供,kube-dns 是集中资源,用于为集群内运行的 Pod 提供 DNS 解析。对于大型集群或请求负载较高的工作负载,这可能会成为瓶颈。GKE 会根据集群大小自动扩缩 kube-dns,以增加其容量。如果此容量仍然不够,GKE 会针对具有 NodeLocal DNSCache 的每个节点提供 DNS 查询的分布式本地解析。这将在每个 GKE 节点上提供在本地对查询做出响应的本地 DNS 缓存,从而分配负载并缩短响应时间。

管理 VPC 原生集群中的 IP 地址

VPC 原生集群使用三个 IP 地址范围:

  • 节点子网的主要范围:默认为 /20(4092 个 IP 地址)。
  • Pod 子网的次要范围:默认为 /14(262,144 个 IP 地址)。但是,您可以配置 Pod 子网。
  • Service 子网的次要范围:默认为 /20(4096 个地址)。但是,创建此 Service 子网后,您便无法更改此范围。

如需了解详情,请参阅 VPC 原生集群的 IP 地址范围

IP 地址限制和建议:

  • 节点限制:节点限制由每个节点的主要和 Pod IP 地址决定。节点和 Pod IP 地址范围中必须有足够的地址,才能预配新节点。默认情况下,由于 Pod IP 地址的限制,您只能创建 1024 个节点。
  • 每个节点的 Pod 限制:默认情况下,每个节点的 Pod 限制为 110 个 Pod。但是,您可以配置较小的 Pod CIDR,通过每节点更少的 Pod 数量来实现高效使用。
  • 超出 RFC 1918 的扩缩:如果您需要的 IP 地址多于 RFC 1918 定义的专用空间内的可用地址,我们建议您使用非 RFC 1918 专用地址PUPI,以获得更大的灵活性。
  • Service 和 Pod 的次要 IP 地址范围:默认情况下,您可以配置 4096 个 Service。但是,您可以通过选择 Service 子网范围来配置更多 Service。次要范围一经创建便无法修改。在创建集群时,请确保选择足够大的范围,以适应预期增长。但是,您可以在之后使用不连续的多 Pod CIDR 为 Pod 添加更多 IP 地址。如需了解详情,请参阅 Pod 没有足够的可用 IP 地址空间

如需了解详情,请参阅节点限制范围迁移到 GKE 时规划 IP 地址

配置节点以获得更好的性能

GKE 节点是常规的 Google Cloud 虚拟机。 它们的一些参数(例如核心数量或磁盘大小)可能会影响 GKE 集群的性能。

减少 Pod 初始化时间

您可以在工作负载请求请求时使用映像流式传输功能从符合条件的容器映像流式传输数据,从而缩短初始化时间。

出站流量

在 Google Cloud 中,分配给实例的机器类型和核心数决定其网络容量。出站带宽上限介于 1 到 32 Gbps 之间,而默认 e2-medium-2 机器的出站带宽上限为 2 Gbps。如需详细了解带宽限制,请参阅共享核心机器类型

IOPS 和磁盘吞吐量

在 Google Cloud 中,永久性磁盘的大小决定了磁盘的 IOPS 和吞吐量。GKE 通常使用永久性磁盘作为启动磁盘以及支持 Kubernetes 的永久性卷。增加磁盘大小会增加 IOPS 和吞吐量,直至达到特定限制

每个永久性磁盘写入操作都会占用虚拟机实例的累计网络出站流量上限。因此,磁盘(尤其是 SSD)的 IOPS 性能除了取决于磁盘大小以外,还取决于该实例中的 vCPU 数量。由于网络出站流量对写入吞吐量的限制,核心数量较少的虚拟机具有较低的写入 IOPS 上限。

如果虚拟机实例的 CPU 不足,则您的应用将无法接近 IOPS 上限。一般说来,您应该针对每 2000-2500 IOPS 的预期流量准备一个可用的 CPU。

对于需要高容量或大量磁盘的工作负载,您需要考虑单个虚拟机可以挂接的永久性磁盘的数量上限。对于常规虚拟机,该上限为 128 个磁盘(总大小为 64 TB),而共享核心虚拟机的上限为 16 个永久性磁盘(总大小为 3 TB)。Google Cloud(而不是 Kubernetes)会强制实施此上限。

监控控制平面指标

使用可用的控制平面指标来配置监控信息中心。您可以使用控制平面指标来观察集群的健康状况,观察集群配置更改的结果(例如,部署其他工作负载或第三方组件),也可以在排查问题时使用控制平面指标。

需要监控的最重要指标之一是 Kubernetes API 的延迟时间。当延迟时间增加时,这表示系统过载。请注意,传输大量数据的 LIST 调用的延迟时间预计将比较小的请求长得多。

Kubernetes API 延迟时间增加也可能是由于第三方准入网络钩子响应缓慢引起的。您可以使用指标来测量网络钩子的延迟时间以检测此类常见问题。

Kubernetes 开发者最佳实践

使用 list 和 watch 模式,而不是定期列出

作为 Kubernetes 开发者,您可能需要创建具有以下要求的组件:

  • 您的组件需要定期检索一些 Kubernetes 对象的列表。
  • 您的组件需要在多个实例中运行(如果是 DaemonSet,甚至需要在每个节点上运行)。

即使定期检索的对象的状态未发生变化,此类组件也会在 kube-apiserver 上造成负载高峰。

最简单的方法是使用定期 LIST 调用。但是,对于调用方和服务器而言,这种方法效率低下且费用高昂,因为每次调用时都必须加载所有对象到内存中、序列化并传输。过度使用 LIST 请求可能会使控制平面过载或导致此类请求的严重拥堵。

您可以通过在 LIST 调用中设置 resourceVersion=0 参数来改进组件。这允许 kube-apiserver 使用内存中对象缓存并减少 kube-apiserver 与 etcd 数据库和相关处理之间的内部交互次数。

我们强烈建议避免使用重复的 LIST 调用,并将其替换为 list 和 watch 模式。列出对象一次,然后使用 Watch API 获取状态的增量更改。与定期的 LIST 调用相比,此方法可缩短处理时间并最大限度地减少流量。如果对象未发生更改,则不会生成额外的负载。

如果您使用 Go 语言,那么请查看 SharedInformerSharedInformerFactory,了解实现此模式的 Go 包。

限制 watch 和 list 产生的不必要流量

Kubernetes 在内部使用 watch 发送关于对象更新的通知。即便 watch 需要的资源比定期 LIST 调用要少得多,处理大型集群中的 watch 也会占用大量集群资源并影响集群性能。最大的负面影响来自于创建从多个位置观察频繁更改对象的 watch。例如,从某个在所有节点上运行的组件观察所有 Pod 的数据。如果您在集群上安装第三方代码或扩展程序,它们可能会在后台创建此类 watch。

我们推荐以下最佳实践:

  • 减少 watch 和 LIST 调用产生的不必要处理和流量。
  • 避免创建从多个位置观察频繁更改对象的 watch(例如 DaemonSet)。
  • (强烈建议)创建一个中央控制器,该控制器在单个节点上监视和处理所需的数据。
  • 仅监视一部分对象,例如每个节点上的 kubelet 只观察调度到同一节点上的 Pod。
  • 避免部署执行大量 watch 或 LIST 调用,从而影响集群性能的第三方组件或扩展程序。

限制 Kubernetes 对象清单大小

如果您需要实现要求高 Pod 吞吐量的快速操作(例如调整或更新大型工作负载),请确保将 Pod 清单大小保持在最低限度,最好低于 10 KiB。

Kubernetes 会将资源清单存储在 etcd 中。每次检索资源时(包括使用列表和观察模式时),系统都会发送整个清单。

清单大小有以下限制:

  • etcd 中每个对象的大小上限:大约 1.5 MiB。
  • 集群中所有 etcd 对象的总配额:预配置的配额大小为 6 GiB。其中包含一个变更日志,其中包含最近 150 秒的集群历史记录中所有对象的所有更新。
  • 高流量期间的控制平面性能:较大的清单大小会增加 API 服务器的负载。

对于很少处理的单个对象,只要其大小不超过 1.5 MiB,通常无需担心清单大小。但是,对于许多经常处理的对象(例如非常大的工作负载中的 pod),清单大小超过 10 KiB 可能会导致 API 调用延迟增加,整体性能降低。特别是,列表和监视内容可能会受到大清单的显著影响。您可能还有 etcd 配额方面的问题,因为在高 API 服务器流量期间,过去 150 秒内的修订版本量可能会快速累积。

为了缩减 Pod 清单的大小,可以利用 Kubernetes ConfigMap 来存储部分配置,尤其是集群中多个 Pod 共享的部分。例如,环境变量通常由工作负载中的所有 Pod 共享。

请注意,如果 ConfigMap 对象与 Pod 一样多、一样大且处理频率一样高,也可能会遇到类似的问题。在减少整体流量时,提取部分配置最为有用。

停用自动装载默认服务账号

如果 Pod 中运行的逻辑不需要访问 Kubernetes API,您应停用自动装载默认服务账号,以避免创建相关的 Secret 和 watch。

如果您在创建 Pod 时未指定服务账号,Kubernetes 会自动执行以下操作:

  • 将默认服务账号分配给 Pod。
  • 装载服务账号凭据作为 Pod 的 Secret。
  • 对于每个已装载的 Secret,kubelet 会创建一个 watch,以观察每个节点上该 Secret 的更改。

在大型集群中,这些操作意味着数千个不必要的 watch,这可能会在 kube-apiserver 上产生大量负载。

为 API 请求使用协议缓冲区而不是 JSON

在实现 Kubernetes API 概念中所述的扩缩能力强的组件时,请使用协议缓冲区

Kubernetes REST API 支持使用 JSON 和协议缓冲区作为对象的序列化格式。默认使用 JSON,但协议缓冲区在大规模性能方面的效率更高,因为它需要更少的 CPU 密集型处理并通过网络发送更少的数据。列出大量数据时,与 JSON 处理相关的开销可能会导致超时。

后续步骤