目录

自动扩缩容

前言

创建 Linode Kubernetes Engine 集群

压力 API

水平 Pod 自动缩放器 (HPA)

垂直 Pod 自动缩放器 (VPA)

集群比例自动扩缩器 (CPA)

集群自动扩缩器 (CA)

总结


自动扩缩容

前言

自动缩放是在没有人工干预的情况下增加或减少应用程序工作负载容量的过程。如果调整正确,自动缩放可以降低维护应用程序的成本和工程工作量。自动缩放的整个过程很简单。它首先确定一组指标,这些指标可以为 Kubernetes 何时应该扩展应用程序容量提供指标。接下来,一组规则确定应用程序是否应该按比例放大或缩小。最后,使用 Kubernetes API,可扩展或收缩应用程序可用的资源,以适应应用程序必须执行的工作。

自动缩放是一个复杂的过程,它比其他应用程序更好地服务于某些类别的应用程序。例如,如果应用程序的容量要求不经常变化,您最好为应用程序将处理的最高流量配置资源。同样,如果您可以可靠地预测应用程序负载,您可以在这些时间手动调整容量,而不是投资于自动扩展解决方案。

除了应用程序的可变负载外,自动扩展的其他主要动机还包括管理成本和容量。例如,集群自动扩展允许您通过调整集群中的节点数量来节省公共云上的资金。此外,如果您有一个静态基础架构,自动缩放将使您能够动态地管理工作负载的容量分配,以便您可以最佳地利用您的基础架构。

概括地说,自动缩放可以分为两类:

  1. 工作负载自动扩展:动态管理对单个工作负载的容量分配。
  2. 集群自动伸缩:动态管理集群的容量。

让我们首先深入了解在 Kubernetes 中扩展工作负载的细节。用于在 Kubernetes 上自动缩放工作负载的一些标准工具是 Horizontal Pod Autoscaler (HPA)、Vertical Pod Autoscaler (VPA) 和 Cluster Proportional Autoscaler (CPA)。要使用自动缩放器,我们需要一个集群和一个简单的测试应用程序,接下来我们将对其进行设置。

创建 Linode Kubernetes Engine 集群

Linode 提供称为 Linode Kubernetes Engine (LKE) 的托管 Kubernetes 产品。 入门很简单:注册一个免费的 Linode 帐户并按照 LKE 入职指南创建您的集群。

在本教程中,我创建了一个由两个节点(称为 Linodes)组成的集群,每个节点有 2 个 CPU 内核和 4 GB 内存,如下所示:

LKE集群

要使用集群,您需要集群的 kubeconfig 文件,您可以从集群概述部分下载该文件。 您可以使用多种策略来合并 kubeconfig 文件。 但是,我更喜欢使用 kubeconfig 文件的路径来更新 KUBECONFIG 环境变量。

现在让我们构建一个简单的应用程序,我们将使用它来测试各种自动缩放器。

压力 API

Pressure API 是一个简单的 .NET REST API,它允许您通过其两个端点对运行应用程序的 pod 施加 CPU 和内存压力:

  1. /memory/{numMegaBytes}/duration/{durationSec}: 该端点会将指定的兆字节数添加到内存中,并在指定的持续时间内保持压力。
  2. /cpu/{threads}/duration/{durationSec}: 该端点将在 CPU 上运行指定数量的线程,并在指定的持续时间内保持压力。

以下是应用程序的完整源代码:

using System.Xml;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapPost("/memory/{numMegaBytes}/duration/{durationSec}", (long numMegaBytes, int durationSec) =>
    {
        // ReSharper disable once CollectionNeverQueried.Local
        List<XmlNode> memList = new();

        try
        {
            while (GC.GetTotalMemory(false) <= numMegaBytes * 1000 * 1000)
            {
                XmlDocument doc = new();
                for (var i = 0; i < 1000000; i++)
                {
                    memList.Add(doc.CreateNode(XmlNodeType.Element, "node", string.Empty));
                }
            }
        }
        // Don't fail if memory is not available
        catch (OutOfMemoryException ex)
        {
            Console.WriteLine(ex);
        }

        Thread.Sleep(TimeSpan.FromSeconds(durationSec));
        memList.Clear();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        return Results.Ok();
    })
    .WithName("LoadMemory");

app.MapPost("/cpu/{threads}/duration/{durationSec}", (int threads, int durationSec) =>
    {
        CancellationTokenSource cts = new();
        for (var counter = 0; counter < threads; counter++)
        {
            ThreadPool.QueueUserWorkItem(tokenIn =>
            {
#pragma warning disable CS8605 // Unboxing a possibly null value.
                var token = (CancellationToken)tokenIn;
#pragma warning restore CS8605 // Unboxing a possibly null value.
                while (!token.IsCancellationRequested)
                {
                }
            }, cts.Token);
        }

        Thread.Sleep(TimeSpan.FromSeconds(durationSec));
        cts.Cancel();
        Thread.Sleep(TimeSpan.FromSeconds(2));
        cts.Dispose();
        return Results.Ok();
    })
    .WithName("LoadCPU");

app.Run();

您无需担心应用程序的详细信息。 我已经在 GitHub Packages 上发布了应用程序的容器镜像,您可以在您的 K8s 规范中使用它,我们将在后续部分中构建这些规范。

您可以从以下 GitHub 存储库下载应用程序的源代码和本教程的其他工件:

本教程中使用的 Kubernetes 规范可在代码存储库的规范文件夹中找到。 使用以下规范将应用程序部署到您的 LKE 集群:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pressure-api-deployment
spec:
  selector:
    matchLabels:
      app: pressure-api
  replicas: 1
  template:
    metadata:
      labels:
        app: pressure-api
    spec:
      containers:
        - name: pressure-api
          image: ghcr.io/rahulrai-in/dotnet-pressure-api:latest
          ports:
            - containerPort: 80
          resources:
            limits:
              cpu: 500m
              memory: 500Mi
---
apiVersion: v1
kind: Service
metadata:
  name: pressure-api-service
  labels:
    run: php-apache
spec:
  ports:
    - port: 80
  selector:
    app: pressure-api

您的应用程序现在已准备好接受请求,但只能在集群内访问。 稍后我们将使用临时 pod 向我们的 API 发送请求。 现在让我们讨论自动缩放器系列中最常见的自动缩放器:Horizontal Pod Autoscaler (HPA)。

水平 Pod 自动缩放器 (HPA)

Horizontal Pod Autoscaler 允许您根据当前负载动态调整集群中的 pod 数量。 Kubernetes 通过捆绑到 kube-controller-manager 中的 HorizontalPodAutoscaler 资源和控制器原生支持它。 HPA 依靠 Kubernetes Metrics Server 来提供 PodMetrics。 Metrics Server 从集群中每个节点上运行的 kubelet 收集 pod 的 CPU 和内存使用情况,并通过 Metrics API 将它们提供给 HPA。

下图说明了该过程中涉及的组件:

水平 Pod 自动缩放

Metrics Server 轮询 kubelet 的 Summary API 端点以收集 Pod 中运行的容器的资源使用指标。 HPA 控制器每 15 秒(默认情况下)轮询 Kubernetes API 服务器的 Metrics API 端点,它代理到 Metrics Server。此外,HPA 控制器持续监视 HorizontalPodAutoscaler 资源,该资源维护自动缩放器配置。接下来,HPA 控制器根据配置更新部署(或其他配置的资源)中的 pod 数量以匹配需求。最后,Deployment 控制器通过更新 ReplicaSet 来响应更改,这会更改 pod 的数量。

我们知道 Metrics 服务器是 HPA 和 VPA 的先决条件。按照官方 Metrics Server 指南中提到的说明将其安装到您的集群上。如果您在安装过程中遇到 TLS 问题,请使用代码仓库的 spec 文件夹中提供的 metrics-server.yaml 规范,如下所示:

kubectl apply -f spec/metrics-server.yaml

现在让我们配置 HorizontalPodAutoscaler 对象,将我们的部署扩展到五个副本,并根据内存资源的平均利用率将其缩小到一个副本,如下所示:

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: pressure-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: pressure-api-deployment
  minReplicas: 1
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 40

如果内存的平均利用率保持在 40% 以上,HPA 将增加副本数,反之亦然。 您也可以扩展规则以包括 CPU 利用率。 在这种情况下,HPA 控制器将根据规则的组合确定最大副本数,并使用最高值。

在开始之前,让我们在两个不同的终端窗口中观察 HPA 和部署,以实时查看副本数的变化。

kubectl get hpa pressure-api-hpa --watch

kubectl get deployment pressure-api-deployment --watch

为了触发 HPA,我们将启动一个临时 pod 并指示它向 /memory/{numBytes}/duration/{durationSec} 端点发送请求。 以下命令将触发 HPA 扩展 Pod 以减少内存压力。

kubectl run -i --tty mem-load-gen --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- --post-data= http://pressure-api-service/memory/1000/duration/180; done"

您可以在终端窗口中观看 HPA 更新部署的副本计数。 请注意针对目标的活动利用率的增长,如下所示:

HPA 在执行

同时,您可以看到副本更新如下:

HPA 触发的副本数增加 使用 HPA 时需要注意以下几点: 1.您的应用程序应该能够在不同实例之间共享负载。 2. 你的集群应该有足够的容量来容纳 Pod 数量的扩展。这可以通过提前配置所需容量并使用警报提示您的平台操作员向集群添加更多容量来解决。您还可以使用集群自动扩展来自动扩展集群。我们将在本教程后面讨论此功能。 3. CPU 和内存可能不是您的应用程序做出扩展决策的正确指标。在这种情况下,您可以使用带有自定义指标的 HPA(或 VPA)作为替代方案。要使用自定义指标进行自动缩放,您可以使用自定义指标适配器而不是 Kubernetes 指标服务器。流行的自定义指标适配器是 Prometheus 适配器和 Kubernetes 事件驱动自动缩放器 (KEDA)。 在我们继续之前,删除您刚刚创建的 HPA 并重置部署的副本计数,如下所示:

kubectl delete hpa/pressure-api-hpa

kubectl scale --replicas=2 deployment/pressure-api-deployment

让我们讨论一下 Kubernetes 中可用的另一种自动缩放器:Vertical Pod Autoscaler (VPA)。

垂直 Pod 自动缩放器 (VPA)

Vertical Pod Autoscaler 允许您动态调整单个实例的资源容量。 在 pod 的上下文中,这涉及更改 pod 可用的 CPU 和内存资源量。 与包含在核心 Kubernetes 中的 HPA 不同,VPA 要求您安装除 Metrics Server 之外的三个控制器组件。 下图说明了 Kubernetes 组件及其与 VPA 的交互:

垂直 Pod 自动缩放器

  1. Recommender:根据 Pod 资源的使用情况,确定最优的 CPU 和内存值。
  2. Admission 插件:根据 Recommender 的推荐,在创建 Pod 时,改变 Pod 的资源请求和限制。
  3. Updater:驱逐 pod,以便准入插件拦截其重新创建请求。

按照 VPA 自述指南中的安装说明准备集群。 安装完成后,您可以通过运行以下命令来验证 VPA 组件的运行状况:

kubectl get pods -l "app in (vpa-recommender,vpa-admission-controller,vpa-updater)" -n kube-system

VPA pod 的运行状况

让我们了解 VPA 的缩放操作是如何工作的。 Pod 规范中的资源请求声明确保 Kubernetes 为 Pod 保留最少的所需资源。 当 VPA 检测到 pod 接近其资源消耗限制时,它将自动计算一组新的、更合适的值。 如果您在 pod 规范中同时定义资源请求和资源限制,则 VPA 将在更新值时保持请求:限制比率。 因此,每当 VPA 更新资源请求时,它也会更改资源限制。

我们将定义一个 VPA 策略来自动调整 CPU 和内存请求,而无需添加更多 pod 来处理工作负载,如下所示:

apiVersion: "autoscaling.k8s.io/v1"
kind: VerticalPodAutoscaler
metadata:
  name: pressure-api-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: pressure-api-deployment
  updatePolicy:
    updateMode: Recreate
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 0m
          memory: 0Mi
        maxAllowed:
          cpu: 1
          memory: 2000Mi
        controlledResources: ["cpu", "memory"]
        controlledValues: RequestsAndLimits

该规范将适用于部署的所有容器。最小和最大阈值将确保 VPA 在合理范围内运行。受控资源字段指定将由 VPA 自动缩放的资源。

VPA 支持四种更新模式。 Recreate 和 Auto 模式是唯一激活自动缩放的模式。但是,这些用例有限。 Initial 模式将在创建时对设置的资源值应用准入控制,但它会阻止更新程序驱逐任何 pod。最有用的模式是关闭模式。在这种模式下,VPA 不会扩展资源。但是,它会推荐资源值。您可以使用此模式在应用程序投入生产之前对其进行全面的负载测试和分析期间计算其最佳资源值。推荐值可应用于生产部署规范,节省工程劳力。

应用之前的规范并执行以下命令来观察自动缩放器:

kubectl get vpa/pressure-api-vpa --watch

我们现在将使用以下命令应用 CPU 压力来激活 VPA:

kubectl run -i --tty mem-load-gen --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- --post-data= http://pressure-api-service/cpu/10/duration/180; done"

一段时间后,执行以下命令查看 VPA 生成的建议。

kubectl describe vpa/pressure-api-vpa

以下是上一个命令的截断输出,其中显示了 VPA 的建议:

VPA 的建议

使用目标值作为 CPU 和内存请求的基准建议。 如果 VPA 规范中定义的上限和下限不是最优的,则使用 Uncapped Target 作为基线,表示在没有 minAllowed 和 maxAllowed 限制的情况下产生的目标估计。

由于我们启用了垂直自动缩放,新创建的 pod 将具有由准入控制器应用的 VPA 注释。 以下命令将显示 pod 的注释:

kubectl get pod  <pod name> -o jsonpath='{.metadata.annotations}'

这是上一个命令的输出(来自 K9s 控制台):

Pod 注释 在移动到列表中的下一个缩放器之前,让我们删除自动缩放器并重置我们的部署。

kubectl delete vpa/pressure-api-vpa
kubectl scale --replicas=1 deployment/pressure-api-deployment

集群比例自动扩缩器 (CPA)

Cluster Proportional Autoscaler (CPA) 是一个水平的 Pod 自动扩缩器,它根据集群中的节点数量来扩展副本。 与其他自动缩放器不同,它不依赖 Metrics API,也不需要 Metrics Server。 此外,与我们看到的其他自动扩缩器不同,CPA 不使用 Kubernetes 资源进行扩展,而是使用标志来识别目标工作负载,并使用 ConfigMap 来扩展配置。 下图说明了 CPA 的组成部分:

集群比例自动扩缩器

CPA 的用例相对有限。 例如,CPA一般用于横向扩展集群DNS等平台服务,需要随着集群上部署的工作负载进行扩展。 CPA 的另一个用例是有一个简单的机制来扩展工作负载,因为它不需要使用 Metrics Server 或 Prometheus Adapter。

您可以使用其 Helm chart在您的集群上安装 CPA。 使用以下命令添加 cluster-proportional-autoscaler Helm 存储库,如下所示:

helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
helm repo update

您可以在chart的values中定义自动缩放规则,该文件会创建具有指定配置的 ConfigMap。 您可以稍后编辑 ConfigMap 以更改自动缩放器的行为,而无需重新安装图表。

创建一个名为 cpa-values.yaml 的文件并添加以下内容:

config:
  ladder:
    nodesToReplicas:
      - [1, 3]
      - [2, 5]
options:
  namespace: default
  target: "deployment/pressure-api-deployment"

您可以指定 CPA 使用的两种缩放方法之一:

  1. 线性:与集群中有多少节点或核心成正比地扩展您的应用程序。
  2. 梯形图:使用阶梯函数来确定节点:副本和/或核心:副本的比率。

在上面的例子中,如果集群中有一个节点,两个节点有五个副本,CPA 会将部署扩展到三个副本。 现在让我们安装图表并为其提供配置。

helm upgrade --install cluster-proportional-autoscaler\
    cluster-proportional-autoscaler/cluster-proportional-autoscaler --values cpa-values.yaml

安装 CPA 后,您会发现它会将 pressure-api-deployment 部署扩展到 5 个副本,因为我们的集群有两个节点。

让我们在移动到列表中的下一个自动缩放器之前删除 CPA,如下所示:

helm delete cluster-proportional-autoscaler

我们已经研究了几种使用核心 Kubernetes 和社区构建的附加组件自动扩展工作负载的方法。 接下来,我们将讨论如何扩展 Kubernetes 集群本身。

集群自动扩缩器 (CA)

在 Kubernetes 集群中手动添加和删除容量会显着增加集群管理成本和工程量。 Cluster Autoscaler 自动向集群添加和删除工作节点以满足所需的容量。 CA 与 HPA 配合得很好。一旦 HPA 开始接近计算资源限制,CA 就可以计算满足短缺的节点数量并将新节点添加到集群中。此外,当 CA 确定节点长时间未充分利用时,它可以将 pod 重新调度到其他节点,并从集群中删除未充分利用的节点。

Cluster Autoscaler 的实施因云提供商而异。 Azure 和 AWS 等一些云提供商支持 Cluster API。 Cluster API 使用它的 Kubernetes 算子来管理集群基础设施。 Cluster Autoscaler 将更新节点计数的操作卸载到 Cluster API 控制器。如果您在实施之前考虑以下事项,集群自动缩放可能会有所帮助:

  1. 确保您了解应用程序在负载下的行为方式,并消除阻碍应用程序水平扩展的瓶颈。
  2. 了解云提供商可能实施的扩展上限。
  3. 了解集群在需要时可以扩展的速度。

在 LKE 上启用集群自动缩放很简单。首先,导航到集群概览页面,然后单击 Autoscale Pool 按钮。

LKE 集群概览页面

然后,在以下对话框中,输入 LKE 应维护的最小和最大节点数,如下所示:

启用 LKE 集群自动伸缩

LKE 集群 autoscaler 响应由于计算资源不足而无法调度的 Pending pod。 自动缩放器监控未充分利用的节点并将它们从集群中移除以缩减集群。

我们从一个双节点集群开始,每个集群有两个 CPU 内核和 4 GB 内存。 要触发集群自动扩缩器,我们可以向我们的应用程序添加更多副本,如下所示:

kubectl scale --replicas=15 deployment/pressure-api-deployment

执行命令后,你会发现部署的几个pod达到了pending状态,如下:

等待调度的 Pod

不久之后,LKE 将更多节点添加到集群中,并将其中一些节点调度到新节点上,如下所示:

LKE 扩展集群

你会发现有些 Pod 仍然处于 pending 状态,因为我们指示 LKE 向外扩展至最多四个节点。 最后,要清理环境,请执行以下命令:

kubectl delete deployment/pressure-api-deployment

总结

我们讨论了水平自动缩放、垂直自动缩放和集群自动缩放的概念,以及它们的用例和注意事项。

  • 如果您的应用程序经常受到容量需求变化的影响,您可以使用 HPA 水平扩展它们。
  • VPA 可以帮助您确定应用程序的最佳资源价值。
  • CPA 可以帮助您解决需要随集群中的工作负载扩展的应用程序的扩展需求。
  • 如果您的工作负载可以扩展超出集群的容量,请使用 CA 自动扩展集群本身。
  • 如果您正在考虑像 LKE 这样的托管 Kubernetes 服务,请寻找具有内置自动缩放工具的解决方案来减少您的工作量。
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐