弹性伸缩模式涵盖了多个维度的应用伸缩:通过调整 Pod 副本的数量进行横向伸缩,通过调整 Pod 的资源需求进行纵向伸缩,以及通过改变集群节点的数量进行集群本身的伸缩。虽然所有这些操作都可以手动执行,但在本章中,我们将探讨 Kubernetes 如何自动执行基于负载的缩放。

问题描述

Kubernetes 通过维护其声明性表达的期望状态,自动协调和管理由大量不可变容器组成的分布式应用。然而,由于许多工作负载的季节性,经常会随着时间的推移而变化,要弄清楚期望状态应该是怎样的,并不是一件容易的事情。准确地确定一个容器需要多少资源,以及一个服务在给定时间内需要多少个副本来满足服务级别协议,需要花费时间和精力。幸运的是,Kubernetes 可以很容易地改变容器的资源、服务所需的副本或集群中的节点数量。这样的改变可以手动进行,也可以给定特定的规则,以全自动的方式进行。

Kubernetes 不仅可以保留固定的 Pod 和集群设置,还可以监控外部负载和容量相关的事件,分析当前状态,并根据所需的性能进行自我扩展。这种观察是 Kubernetes 基于实际使用指标而不是预期的面,来适应和获得抗脆弱特性的一种方式。让我们来探讨一下实现这种行为的不同方式,以及如何将各种扩展方法结合起来,以获得更好的体验。

解决方案

缩放任何应用程序有两种主要方法:水平和垂直。在 Kubernetes 的世界里,水平扩展相当于创建更多 Pod 的副本。垂直扩展意味着给由 Pod 管理的运行容器更多的资源。虽然从纸面上看似乎很简单,但在不影响其他服务和集群本身的情况下,在共享云平台上创建一个自动扩展的应用配置需要大量的试验和错误。与以往一样,Kubernetes 提供了各种特性和技术来为我们的应用找到最佳设置,我们在这里简单探讨一下。

手动水平伸缩

手动缩放方式,顾名思义,是基于人工操作者向 Kubernetes 发出命令。这种方法可以在没有自动缩放的情况下使用,也可以用于逐步发现和调整应用程序的最佳配置,以匹配长期缓慢变化的负载。手动方法的一个优点是,它还可以进行预期性而不是只被动的变化:知道了季节性和预期的应用负载,就可以提前进行扩展,而不是通过自动缩放等方式对已经增加的负载做出反应。我们可以以两种方式进行手动缩放。

命令式伸缩

像 ReplicaSet 这样的控制器负责确保特定数量的 Pod 实例始终处于运行状态。因此,扩展 Pod 就像改变所需副本的数量一样简单。给定一个名为 random-generator 的部署,如例 24-1 所示,只需一条命令即可将其扩展到四个实例。

  1. # 例 24-1 在命令行上扩展部署的副本
  2. $ kubectl scale random-generator --replicas=4

在这样的变化之后,ReplicaSet 可以创建更多的 Pod 来扩大规模,或者是如果有更多的 Pod,删除它们以缩小规模。

声明式伸缩

虽然使用 scale 命令是琐碎简单的,并且有利于对紧急情况做出快速反应,但它并不能在集群之外保存这种配置。通常情况下,所有的 Kubernetes 应用都会将其资源定义存储在源码控制系统中,该系统还包括副本数量。从原始定义中重新创建 ReplicaSet 会将副本数量改回之前的数量。为了避免这样的配置漂移,并引入操作流程来进行回移植更改,更好的做法是在 ReplicaSet 或其他定义中声明性地更改所需的复制数量,并将更改应用到 Kubernetes 中,如例 24-2 所示。

  1. # 例 24-2 使用部署来声明性地设置复制数
  2. $ kubectl apply -f random-generator-deployment.yaml

我们可以扩展管理多个 Pod 的资源,如 ReplicaSet、Deployment 和 StatefulSet。请注意在扩展具有持久性存储的 StatefulSet 时的不对称行为。正如在第 11 章 “有状态服务” 中所描述的,如果 StatefulSet 有一个 .spec.volumeClaimTemplates 元素,它将在扩展时创建 PVC,但在缩减时不会删除它们,以保存存储不被删除。

:::info 另一个可以缩放但遵循不同命名规范的 Kubernetes 资源是 Job 资源,我们在第 7 章 “批处理作业” 中介绍过。通过改变 .spec.parallelism 字段而不是 .spec.replicas,一个 Job 可以被扩展到同时执行同一个 Pod 的多个实例。然而,语义效果是一样的:通过更多的处理单元作为单一逻辑单元来增加容量。 :::

这两种手动缩放风格(命令式和声明式)都希望人类观察或预测应用负载的变化,对缩放的程度做出决定,并将其应用到集群中。它们的效果是一样的,但它们不适合经常变化、需要持续适应的动态工作负载模式。接下来,让我们看看如何自己自动化地进行扩展决策。

水平自动伸缩(HPA)

许多工作负载具有随时间变化的动态性,很难有一个固定的扩展配置。但 Kubernetes 等云原生技术可以实现创建适应负载变化的应用。Kubernetes 中的自动伸缩允许我们定义一个变化的应用容量,这个容量不是固定的,而是保证刚好有足够的容量来处理不同的负载。实现这种行为的最直接的方法是使用 HorizontalPodAutoscaler(HPA)来水平扩展 Pod 的数量。

可以使用例 24-3 中的命令为随机生成器部署创建 HPA。要使 HPA 产生任何效果,重要的是部署要为 CPU 声明一个 .spec.resources.requests 限制,如第 2 章 “可预测需求” 中所述。另一个要求是,您必须启用度量服务器(Metrics Server),它是资源使用数据的群集范围聚合器。

  1. # 例 24-3 在命令行上创建HPA定义
  2. $ kubectl autoscale deployment random-generator --cpu-percent=50 --min=1 --max=5

前面的命令将创建例 24-4 所示的 HPA 定义:

  1. # 例 24-4 HPA 定义
  2. ---
  3. apiVersion: autoscaling/v2beta2
  4. kind: HorizontalPodAutoscaler
  5. metadata:
  6. name: random-generator
  7. spec:
  8. # 应始终运行的最小 Pod 数量
  9. minReplicas: 1
  10. # 在 HPA 能够扩大规模之前,最大的 Pod 数量
  11. maxReplicas: 5
  12. # 应与该 HPA 相关联的对象的参考
  13. scaleTargetRef:
  14. apiVersion: extensions/v1beta1
  15. kind: Deployment
  16. name: random-generator
  17. metrics:
  18. - resource:
  19. name: cpu
  20. target:
  21. # 期望的 CPU 使用量占 Pod、请求的 CPU 资源的百分比
  22. # 例如,当 Pod 的 .spec.resources.requests.cpu 为 200m 时,当平均超过 100m 的 CPU(=50%)被利用时,就会发生扩展
  23. averageUtilization: 50
  24. type: Utilization
  25. type: Resource

:::info 在例子 24-4 中,我们使用资源的 API 版本 v2beta2 来配置 HPA。这个版本正在积极开发中,功能上是 v1 版本的超集。v2 版本允许更多的标准,而不是 CPU 使用情况,比如内存消耗或特定应用的自定义指标。通过使用 kubectl get hpa.v2beta2.autoscaling -o yaml,你可以很容易地将 kubectl autoscale 创建的 v1 版 HPA 资源转换为 v2 版资源。 :::

这个定义指示 HPA 控制器保持 1 到 5 个 Pod 实例,以保留 Pod 的 .spec.resources.requests 声明中指定的 CPU 资源限制的 50% 左右的平均 Pod CPU 使用率。虽然可以将这样的 HPA 应用于任何支持 scale 子资源的资源,如 Deployment、ReplicaSet 和 StatefulSet,但你必须考虑副作用。部署会在更新期间创建新的 ReplicaSet,但不会复制任何 HPA 定义。如果您将 HPA 应用到由部署管理的 ReplicaSet 中,它不会被复制到新的 ReplicaSet 中,而是会丢失。更好的技术是将 HPA 应用到更高层次的部署抽象中,这样可以将 HPA 保留并应用到新的 ReplicaSet 版本。

现在,让我们来看看 HPA 如何取代人类操作员来确保自动缩放。在高层次上,HPA 控制器连续执行以下步骤:

  1. 检索根据 HPA 定义进行扩展的 Pod 的度量。指标不是直接从 Pod 读取,而是从 Kubernetes Metrics APIs 读取,Kubernetes Metrics APIs 提供聚合指标(如果配置了自定义和外部指标,甚至可以读取)。Pod 级别的资源度量是从 Metrics API 获得的,而所有其他度量都是从 Kubernetes 的 Custom Metrics API 检索的。
  2. 根据当前的度量值并针对所需的度量值计算所需的复制数。这里是一个简化版的公式:

第 24 章 · 弹性伸缩 - 图1

例如,如果有一个 Pod 的当前 CPU 使用度量值为指定 CPU 资源请求值的 90%,而期望值为 50%,则该 Pod 的数量为
复制品的数量将增加一倍,因为 1 × 90/50 = 2

:::info 实际的实现比较复杂,因为它要考虑多个运行中的 Pod 实例,覆盖多种度量类型,还要考虑许多角落情况和波动值。如果指定了多个指标,那么 HPA 就会分别评估每个指标,并提出一个所有指标中最大的值。在所有的计算之后,最终的输出是一个单一的整数,代表了保持测量值低于期望阈值的期望复制数。 :::

自动缩放资源的复制字段将用这个计算出的数字进行更新,其他控制器在实现和保持新的期望状态方面做他们的工作。图 24-1 显示了 HPA 的工作方式:监控指标并相应地改变声明的复制数。
image.png
图 24-1 Pod 水平自动伸缩机制

自动缩放是 Kubernetes 的一个领域,它有许多低级细节,这些细节仍在快速发展,每一个细节都会对自动缩放的整体行为产生重大影响。因此,本书无法涵盖所有细节,大体上,有以下几种度量类型:

  • 标准指标:这些度量用 .spec.metrics.resource.type 等于 Resource 来声明,代表资源使用度量,如 CPU 和内存。它们是通用的,可用于任何集群上同名的任何容器。您可以像我们在前面的例子中那样将它们指定为百分比,或者指定为绝对值。在这两种情况下,这些值都是基于保证的资源量,即容器资源请求值,而不是限制值。这些是最容易使用的度量类型,一般由 Metrics Server 或 Heapster 组件提供,它们可以作为集群附加组件启动。
  • 自定义度量:这些 .spec.metrics.resource.type 等于 Object 或 Pod 的度量需要更高级的集群监控设置,这可能因集群而异。顾名思义,Pod 类型的自定义度量描述了 Pod 特定的度量,而 Object 类型可以描述任何其他对象。自定义度量是在一个聚合的 API Server 中提供服务的,该服务器下的 custom.metrics.k8s.io API 路径,并由不同的度量适配器提供,如 Prometheus、Datadog、Microsoft Azure 或 Google Stackdriver。
  • 外部指标:此类别用于描述不属于 Kubernetes 集群的资源的指标。例如,您可能有一个Pod,它从基于云的队列服务中消费消息。很多时候,在这样的场景中,您可能希望根据队列深度来扩展消费者 Pod 的数量。这样的度量将由一个类似于自定义度量的外部度量插件来填充。

获得自动缩放的权利并不容易,涉及到一点点的实验和调整。以下是设置 HPA 时需要考虑的几个主要方面:

  • 度量选择(Metrics Selection):围绕自动缩放的最关键的决定之一可能是使用哪些指标。为了使 HPA 有用,度量值和 Pod 复制数量之间必须有直接的关联。例如,如果选择的度量是每秒查询次数(如每秒 HTTP 请求次数),增加 Pod 的数量会导致平均查询次数下降,因为查询会被分配到更多的 Pod 上。如果指标是 CPU 使用率,情况也是如此,因为查询率和 CPU 使用率之间有直接的关系(增加查询次数会导致 CPU 使用率增加)。对于其他指标,如内存消耗,情况并非如此。内存的问题是,如果一个服务消耗了一定的内存,那么启动更多的 Pod 实例很可能不会导致内存减少,除非应用程序是集群的,并且知道其他实例,并且有机制来分配和释放内存。如果内存没有被释放并反映在度量中,HPA会为了减少内存而创建越来越多的 Pod,直到达到复制上限阈值,这可能不是理想的行为。所以选择一个与 Pod 数量直接(最好是线性)相关的指标。
  • 防止波动(Preventing Thrashing):HPA 应用各种技术来避免快速执行冲突的决定,以免在负载不稳定时,导致复制数量的波动。例如,在扩容时,HPA 在 Pod 初始化时不考虑高 CPU 使用率的样本,确保对增加的负载有一个平滑的反应。在缩减规模时,为了避免因使用量的短暂下降而缩减规模,控制器会在一个可配置的时间窗口内考虑所有的规模建议,并从窗口内选择最高的建议。所有这些都使得 HPA 对随机指标波动更加稳定。
  • 延迟反应(Delayed Reaction):根据度量值触发扩展动作是一个多步骤的过程,涉及多个 Kubernetes 组件。首先,是 cAdvisor(容器顾问)代理定期为 Kubelet 收集度量值。然后,度量服务器定期收集 Kubelet 的度量。HPA 控制器循环也会定期运行,并对收集到的度量进行分析。HPA 缩放公式引入了一些延迟反应,以防止波动/颤动(如上一点所解释)。所有这些活动累积成原因和缩放反应之间的延迟。通过引入更多的延迟来调整这些参数,会使 HPA 的响应降低,但减少延迟会增加平台的负载,并增加颤动。配置 Kubernetes 来平衡资源和性能是一个不断学习的过程。

Knative Serving

Knative Serving 允许更高级的水平缩放技术。这些先进的功能包括 “Scale-to-Zero”,即支持一个 Service 的 Pod 集合可以被缩减为 0,只有当一个特定的触发器发生时,比如一个传入的请求,才会被放大。为了实现这一点,Knative 建立在服务网格 Istio 之上,而 Istio 又为 Pod 提供透明的内部代理服务。Knative Serving 为无服务器框架提供了基础,可以实现更灵活快速的横向扩展行为,超越 Kubernetes 标准机制。

对 Knative Serving 的详细讨论超出了本书的范围,因为这仍然是一个非常年轻的项目,值得自己出书。我们在 “参考资料”更 中添加了更多 Knative 资源的链接。

垂直自动伸缩(VPA)

水平扩展比垂直扩展更受欢迎,因为它的破坏性更小,尤其是对于无状态服务。但对于有状态的服务来说,情况就不一样了,在这种情况下,垂直扩展可能是首选。垂直扩展有用的其他场景是根据实际负载模式来调整服务的实际资源需求。我们已经讨论过为什么当负载随时间变化时,确定一个 Pod 的正确副本数量可能会很困难,甚至是不可能的。垂直扩展在识别容器的正确请求和限制方面也有这样的挑战。Kubernetes 垂直 Pod 自动缩放器(VPA)旨在通过根据真实世界的使用反馈自动调整和分配资源的过程来解决这些挑战。

正如我们在第 2 章 “可预测的需求” 中看到的,Pod 中的每个容器都可以指定它的 CPU 和内存请求,这影响了管理 Pod 的排程位置。从某种意义上说,Pod 的资源请求和限制形成了 Pod 和调度器之间的合同,这使得 Pod 的资源得到一定的保证,或者防止 Pod 被调度。将内存请求设置的过低,会导致节点更加紧密,进而导致内存外错或工作负载因内存压力而被驱逐。如果 CPU 限制过低,则会出现 CPU 饥饿和工作负载表现不佳的情况。另一方面,指定过高的资源请求会分配不必要的容量,导致资源浪费。尽可能准确地获取资源请求是很重要的,因为它们会影响集群的利用率和横向扩展的效果。让我们看看VPA如何帮助解决这个问题。

在安装了 VPA 和度量服务器的集群上,我们可以使用 VPA 定义来演示 Pod 的垂直自动伸缩,如例 24-5:

  1. # 例 24-5 自动垂直缩放示例
  2. ---
  3. apiVersion: poc.autoscaling.k8s.io/v1alpha1
  4. kind: VerticalPodAutoscaler
  5. metadata:
  6. name: random-generator-vpa
  7. spec:
  8. selector:
  9. # 标签选择器识别要管理的 Pod
  10. matchLabels:
  11. app: random-generator
  12. updatePolicy:
  13. # 关于 VPA 如何应用变化的更新政策
  14. updateMode: "Off"

VPA 的定义包括以下主要部分:

  • 标签选择器(Label Selector):通过确定它应该处理的 Pod 来指定要缩放的内容。
  • 更新策略(Update Policy):控制 VPA 如何应用更改。Initial 模式只允许在 Pod 创建时分配资源请求,而不是之后。默认的 Auto 模式允许在创建时为 Pod 分配资源,但此外,它可以在 Pod 的生命周期内通过驱逐和重新安排 Pod 来更新 Pod。值为 Off 时,禁止自动更改 Pod,但允许建议资源值。这是为发现容器的正确尺寸而进行的一种模拟运行,但不直接应用它。

VPA 定义还可以有一个资源策略,它影响 VPA 如何计算推荐的资源(例如,通过设置每个容器的资源下限和上限边界)。

根据配置的 .spec.updatePolicy.updateMode,VPA 涉及不同的系统组件。所有三个 VPA 组件 — 推荐器、接纳插件和更新器 — 都是解耦的、独立的,可以用其他实现来替换。具有产生推荐的智能模块是推荐器,它的灵感来自于 Google 的 Borg 系统。目前的实现是分析容器在一定时期(默认为 8 天)负载下的实际资源使用情况,产生直方图,并选择该时期的高百分位值。除了指标之外,它还考虑了资源,特别是与内存相关的 Pod 事件,如驱逐和 OutOfMemory 事件。

在我们的例子中,我们选择了 .spec.updatePolicy.updateMode 等于 Off,但还有其他两个选项可供选择,每个选项对缩放 Pod 的潜在干扰程度不同。让我们看看 updateMode 的不同值是如何工作的,从非破坏性开始到更具破坏性的顺序:

  • updateMode: Off:VPA 推荐器收集 Pod 指标和事件,然后产生推荐。VPA 推荐总是存储在 VPA 资源的 status 属性中。然而,这就是关闭模式的作用。它分析并产生建议,但不将其应用于 Pod。这种模式对于深入了解 Pod 资源消耗情况很有用,而不会引入任何变化和造成中断。如果需要的话,这个决定是留给用户的。
  • updateMode: Initial:在这种模式下,VPA 更进一步。除了由推荐器组件形成的活动外,它还激活了 VPA 准入(Admission)插件,该插件仅将推荐应用于新创建的 Pod。例如,如果一个 Pod 通过 HPA 手动扩展,由 Deployment 更新,或因任何原因被驱逐并重新启动,Pod 的资源请求值将由 VPA 接纳控制器更新。
    该控制器是一个转换的准入插件,可覆盖与 VPA 标签选择器匹配的新 Pod 的请求。这种模式不会重新启动正在运行的 Pod,但它仍然具有部分破坏性,因为它改变了新创建的 Pod 的资源请求。这反过来又会影响新 Pod 的调度位置。更重要的是,有可能在应用推荐的资源请求后,Pod被调度到不同的节点,这可能会产生意想不到的后果。或者更糟糕的是,如果集群上没有足够的容量,Pod可能不会被调度到任何节点。
  • updateMode: Auto:除了如前所述的推荐创建及其对新创建的 Pod 的应用外,在该模式下,VPA 还激活其更新的组件。该组件驱逐与其标签选择器相匹配的运行中的 Pod。驱逐之后,Pod 会被 VPA 准入插件组件重新创建,更新其资源请求。所以这种方法是最具有破坏性的,因为它会重新启动所有的 Pod 来强制应用建议,并且可能会导致前面描述的意外调度问题。

如图 24-2 所示,Kubernetes 被设计成用不可变的 Pod 规范定义来管理不可变的容器。虽然这简化了水平扩展,但却为垂直扩展引入了挑战,比如需要删除和重新创建 Pod,这会影响调度并导致服务中断。即使在 Pod 缩减规模并希望在不中断的情况下释放已经分配的资源时也是如此。

:::danger 另一个问题是围绕着 VPA 和 HPA 共存,因为这些自动缩放器目前还没有意识到对方,这可能导致不必要的行为。例如,如果 HPA 使用的是 CPU 和内存等资源指标,而 VPA 也在影响同样的值,那么你可能最终会得到水平缩放的 Pod,而这些 Pod 也是垂直缩放的(因此是双重缩放)。 :::

我们在这里不做更多的详细介绍,因为 VPA 还在测试阶段,在投入使用后可能会有变化。但这是一个有可能大幅改善资源消耗的功能。
image.png
图 24-2 Pod 垂直自动伸缩机制

集群自动伸缩(CA)

本书中的模式主要使用 Kubernetes 的基本元素和资源,针对的是使用已经设置好的 Kubernetes 集群的开发人员,这通常是一个操作性的任务。由于这是一个与工作负载的弹性和扩展相关的主题,我们将在这里简单介绍 Kubernetes Cluster Autoscaler(CA)。

:::tips 云计算的宗旨之一就是按需付费的资源消费。我们可以在需要的时候消耗云服务,而且只需要消耗多少。CA 可以在 Kubernetes 运行的地方与云提供商互通有无,在高峰期请求增加节点,或者在其他时间关闭闲置节点,降低基础设施成本。HPA 和 VPA 执行 Pod 级扩展,确保集群内的服务容量弹性,而 CA 则提供节点扩展性,确保集群容量弹性。 :::

CA 是一个 Kubernetes 插件,必须开启并配置最小和最大节点数。只有当 Kubernetes 集群运行在云计算基础架构上时,它才能发挥作用,在这个基础架构上,节点可以按需配置和退役,并且这个基础架构支持 Kubernetes CA,如 AWS、Microsoft Azure 或 Google Compute Engine。

所有主要的云提供商都支持 Kubernetes CA。然而,为了实现这一点,云提供商已经编写了插件,导致供应商锁定和不一致的 CA 支持。幸运的是,有 Cluster API Kubernetes 项目,旨在为集群创建、配置和管理提供 API。所有主要的公共和私有云提供商,如 AWS、Azure、GCE、vSphere 和 OpenStack 都支持这一计划。这也允许 CA 在企业内部的 Kubernetes 安装上。Cluster API 的核心是一个在后台运行的机器控制器,为此已经有七种独立的实现,如 Kubermatic machine-controller 或 Open-Shift 的 machine-api-operator。值得关注的是 Cluster API,因为它可能成为未来任何集群自动伸缩的骨干。

CA 主要执行两个操作:向集群中添加新节点或从集群中移除节点。我们来看看这些操作是如何执行的。

增加一个新节点(扩大规模)

如果您的应用程序具有可变负载(白天、周末或节假日期间的繁忙时间,而其他时间的负载较少),您需要不同的容量来满足这些需求。您可以从云计算提供商那里购买固定容量,以覆盖高峰期,但在不太繁忙的时期支付费用会降低云计算的优势。这就是 CA 真正有用的地方。
当一个 Pod 被水平或垂直扩展时,无论是手动还是通过 HPA 或 VPA,副本必须被分配到有足够容量的节点上,以满足要求的 CPU 和内存。如果集群中没有足够容量的节点来满足 Pod 的所有要求,Pod 就会被标记为 unschedulable,并保持在等待状态,直到找到这样的节点。CA 监控这样的 Pod,看看增加一个新节点是否能满足 Pod 的需求。如果答案是肯定的,它就会重新调整集群的大小,容纳等待的 Pod。
CA 不能通过随机的节点来扩展集群,它必须从集群运行的可用节点组中选择一个节点。它假设节点组中的所有机器都具有相同的容量和相同的标签,并且它们运行着由本地清单文件或 DaemonSet 指定的相同 Pod。这个假设对于 CA 估计一个新节点会给集群增加多少额外的 Pod 容量是必要的。
如果多个节点组都能满足等待 Pod 的需求,那么 CA 可以通过不同的策略配置选择节点组,称为扩展器。扩展器可以通过优先考虑成本最低、资源浪费最少、容纳大多数 Pod,或者只是随机地扩展一个节点组,增加一个节点。在成功选择节点结束时,云提供商应该在几分钟内提供一台新机器,并在 API Server 中注册为一个新的 Kubernetes 节点,准备托管等待的 Pod。

移除节点(缩减规模)

在不中断服务的情况下缩减 Pod 或节点总是涉及较多,需要很多检查。如果不需要缩减规模,并且一个节点被识别为不需要,CA 就会执行缩减规模。如果一个节点满足以下主要条件,它就有资格进行缩减。

  • 超过一半的容量未使用 — 即节点上所有 Pod 的所有 CPU 和内存请求之和小于节点所有可调用资源容量的 50%。
  • 节点上所有可移动的 Pod(不是由 Manifest 文件在本地运行的 Pod 或由 DaemonSet 创建的 Pod)都可以放在其他节点上。为了证明这一点,CA 进行了调度模拟,并确定了每一个会被驱逐的 Pod 的未来位置。Pod 的最终位置仍然由调度器决定,可以是不同的,但模拟确保了 Pod 有备用容量。
  • 没有其他原因阻止节点的删除,比如通过注释排除节点的缩减。
  • 没有不能移动的 Pod,比如 PodDisruptionBudget 不能满足的 Pod,本地存储的 Pod,防止驱逐的注释标记的 Pod,没有控制器创建的 Pod,或者系统 Pod。

所有这些检查都是为了确保没有删除不能在不同节点上启动的 Pod。如果前述所有条件在一段时间内都为真(默认为 10 分钟),该节点就有资格被删除。通过将该节点标记为不可调度,并将其上的所有 Pod 转移到其他节点上,从而删除该节点。

图 24-3 总结了 CA 如何与云提供商和 Kubernetes 交互以扩展集群节点。
image.png
图 24-3 集群自动缩放机制

:::tips 你可能已经知道了,扩展 Pod 和节点是解耦但互补的程序。HPA 或 VPA 可以分析使用指标、事件和扩展 Pod。如果集群容量不足,CA 就会启动并增加容量。当集群负载因批处理作业、重复性任务、持续集成测试或其他需要临时增加容量的峰值任务而出现不规则情况时,CA也很有帮助。它可以增加和减少容量,并大大节省云基础设施成本。 :::

伸缩等级

在本章中,我们探讨了各种用于扩展已部署工作负载的技术,以满足其不断变化的资源需求。虽然人类操作员可以手动执行这里列出的大多数活动,但这并不符合云原生思维。为了实现大规模分布式系统管理,必须实现重复活动的自动化。首选的方法是自动化扩展,让人类操作员专注于 Kubernetes 操作员(Operator)还不能自动化的任务。

让我们来回顾一下所有的扩展技术,从更精细到更粗粒度的顺序,如图 24-4 所示。
image.png
图 24-4 应用伸缩等级

应用调优

在最细微的层面上,有一种应用调优技术我们没有在本章中涉及,因为它不是一个与 Kubernetes 相关的活动。然而,你可以采取的第一个行动就是调整在容器中运行的应用程序,以最好地使用分配的资源。这项活动并不是每次扩展服务时都会执行,但在投入生产之前必须首先执行。例如,对于 Java 运行时,就是正确地调整线程池的大小,以便最好地利用容器得到的可用 CPU 份额。然后调整不同的内存区域,如堆、非堆和线程栈大小。调整这些值通常是通过配置更改而不是代码更改来进行的。

容器原生应用程序使用启动脚本,可以根据分配的容器资源而不是共享的全节点容量,为应用程序计算好线程数、内存大小的默认值。使用这样的脚本是很好的第一步。你还可以更进一步,使用 Netflix 的 Adaptive Concurrency Limits 库等技术和库,应用程序可以通过自我剖析和适应来动态计算其并发限制。这是一种应用内自动缩放,无需手动调整服务。

调整应用程序可能会导致类似代码更改的回归,必须在一定程度上进行测试。例如,改变应用程序的堆大小可能会导致应用程序被杀,出现 OutOfMemory 错误,水平缩放将无济于事。另一方面,如果你的应用没有正确地消耗为容器分配的资源,那么垂直或水平地扩展 Pod,或者提供更多的节点,都不会有太大的效果。因此,在这个层面上对规模进行调整会影响所有其他的扩展方法,并且可能会造成破坏,但必须至少进行一次,以获得最佳的应用行为。

垂直自动伸缩

假设应用有效地消耗了容器资源,下一步就是在容器中设置正确的资源请求和限制。前面我们探讨了 VPA 如何自动化发现和应用由实际消耗驱动的最佳值的过程。这里一个重要的问题是,Kubernetes 要求从头开始删除和创建 Pod,这就有可能出现短时间或意外的服务中断。将更多的资源分配给一个资源匮乏的容器,可能会使 Pod 无法调度,并更多地增加其他实例的负载。增加容器资源可能还需要对应用进行调整,以最佳地使用增加的资源。

水平自动伸缩

前面两种技术是一种垂直缩放的形式,我们希望通过调整现有的 Pod,但不改变它们的数量,从而获得更好的性能。下面两种技术是一种水平缩放的形式:我们不触动 Pod 规范,但我们改变 Pod 和节点数。这种方法减少了引入任何回归和中断的机会,并且可以更直接地实现自动化。HPA 是目前最流行的扩展形式。虽然最初,它只通过 CPU 和内存指标支持提供最小的功能,但现在使用自定义和外部指标允许更高级的缩放用例。

假设你已经执行了一次前面的两种方法来确定应用设置本身的良好值,并确定了容器的资源消耗,从此,你可以启用 HPA,并让应用适应资源需求的变化。

集群自动伸缩

HPA 和 VPA 中描述的缩放技术只在集群容量的边界内提供弹性。只有在 Kubernetes 集群内有足够的空间时,你才能应用它们。CA 在集群容量层面引入了灵活性。CA 是对其他扩容方法的补充,但也是完全解耦的。它不关心额外容量需求的原因,也不关心为什么会有未使用的容量,也不关心改变工作负载配置文件的是人类操作者,还是自动伸缩器。它可以扩展集群以保证需求的容量,也可以收缩集群以腾出一些资源。

一些讨论

弹性和不同的扩展技术是 Kubernetes 仍在积极发展的一个领域。HPA 最近增加了适当的度量支持,而 VPA 还在实验中。另外,随着无服务器编程模式的普及,扩展到零和快速扩展已经成为重点。Knative Serving 是一个 Kubernetes 插件,它恰恰满足了这一需求,为 “Scale-to-Zero” 提供了基础,Knative 和底层服务网格进展迅速,并引入了非常令人兴奋的新云原生基元。我们正在密切关注这一领域,并推荐您也关注 Knative。

给定一个分布式系统所需的状态规范,Kubernetes 可以创建和维护它。它还可以通过不断地监控和自我修复,确保其当前状态与所需状态相匹配,从而使其具有可靠性和对故障的弹性。虽然一个弹性和可靠的系统对于今天的许多应用来说已经足够好了,但 Kubernetes 更进一步。一个小型但配置正确的 Kubernetes 系统在重载下不会崩溃,反而会扩展 Pod 和节点。所以在面对这些外部压力时,系统会变得更大更强,而不是更弱更脆,这让 Kubernetes 具备了抗脆弱的能力。

参考资料