在 K8s 中使用原生 hpa 进行应用扩缩容

目录

K8s 原生 hpa

K8s 原生 hpa 的文档为 Horizontal Pod Autoscaling。 hpa 依赖 metrics server提供指标(或者 prometheus-adapter),安装方式为

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 非安全模式,否则可能无法启动
kubectl patch -n kube-system deployment metrics-server --type=json \
  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
# 安装完成之后,可以使用 top 命令查看 pod cpu 利用率。
kubectl top pod local-path-provisioner-684f458cdd-r2j5q -n local-path-storage

快速入手

先通过一个例子,大概看下 hpa 的工作过程。这部分可以参考官方文档 HorizontalPodAutoscaler Walkthrough,思路就是创建一个 deployment,然后是创建针对这个 deployment 的 hpa 资源,然后增加负载,观察扩缩容效果。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

创建针对上述 deployment 的 hpa。指定当平均 cpu 利用率超过 50% 时进行扩容。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

下一步是增加这个 php-apache 的负载,通过下面命令执行,即每隔 10 ms 访问 php-apache 服务,其中 php-apache 是我们上面创建的 deployment 对应的 service。

kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never \
-- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

观察扩缩容效果,可以看到在上强度之后,先是扩容;负载降下来之后,然后又进行了缩容。

lr90@mo hpa % kubectl get hpa php-apache --watch
NAME         REFERENCE               TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache   0%/50%    1         5         1          3m35s
php-apache   Deployment/php-apache   168%/50%   1         5         1          4m
php-apache   Deployment/php-apache   250%/50%   1         5         4          4m15s
php-apache   Deployment/php-apache   250%/50%   1         5         5          4m30s
php-apache   Deployment/php-apache   236%/50%   1         5         5          5m
php-apache   Deployment/php-apache   102%/50%   1         5         5          5m15s
php-apache   Deployment/php-apache   9%/50%     1         5         5          5m30s
php-apache   Deployment/php-apache   0%/50%     1         5         5          5m45s
php-apache   Deployment/php-apache   0%/50%     1         5         5          10m
php-apache   Deployment/php-apache   0%/50%     1         5         1          10m

原生 hpa 是怎么工作的

工作过程

K8s 中的 ControllerManager hpa 控制器会周期性的评估 pod 资源使用情况,并决定是否扩缩容,这个周期由 kube-controller-manager 的参数 --horizontal-pod-autoscaler-sync-period 决定,默认是 15s,也就是说最快负载变化 15s 后开始扩缩容

在每个周期中,hpa控制器首先根据 hpa 资源中配置的 scaleTargetRef 找到一组 pod(根据其 .spec.selector 字段),然后根据 metrics api 获取 pod 的平均资源使用率,结合 hpa 中配置的期望资源利用率计算出一个期望副本数,并与当前副本数进行比较,以扩容或者缩容。计算公式参考下面的扩容算法

扩容算法

官方文档 algorithm-details 有说明,这里概述一下。 从大致思路上来讲,该算法希望将容器的平均资源使用率维持在一个特定的数值,如果高于此利用率,则扩容;如果低于此利用率,则缩容。auto-scaler 控制器通过下面公式计算期望副本数。具体是:

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

该公式的计算过程为:

  1. 计算当前 metrics 指标与期望 metrics 的比率。
  2. 用这个比率乘以当前副本数,并向上取整。

结合我们上边的例子,这个公式的效果是使 cpu 利用率大致在 50% 左右。我们通过几个例子来解释这个公式:假设当前有两个副本,每个副本利用率是 80%,那期望副本数是 ceil(2*(8/5))=4 即要扩容两个副本出来;假设当前有两个副本,每个副本利用率是 30%,那期望副本数是 ceil(2*(3/5))=2,可以发现当前既不用扩容,也不用缩容;那如果每个副本利用率是 20%,则 ceil(2*(2/5))=1,则要缩容一个副本。

除了这个公式带来的设计思路,auto-scaler 控制器还有一些原则(或者 case 要处理):

  • currentMetricValue 是负载所属 pod 的平均值,比如同属一个 deploy 的所有 pod的利用率平均值。利用率是使用值跟 request 的比值,因此当资源使用量大于 request 时(比如允许超配),可能会出现资源使用率大于 100% 的情况
  • 选择 pod 时,不考虑 deletiontimestamp 不为 nil,或者 failed 状态的 pod。
  • 如果 pod 的指标缺失,不会被纳入统计。对于缺失的指标,hpa 处理比较谨慎,在通过公式计算出是该扩容还是缩容之后,再通过这些指标缺失的 pod 来进行简单校准。在计算出来要进行缩容时,认为指标缺失的 pod 利用了 100% 资源;在扩容时,认为缺失的 pod 利用了 0% 的资源,尽量避免扩缩容,其思想是在出现指标缺失时,尽量避免做出扩缩容动作,以免误判。

配置扩缩容行为

在使用 hpa 进行扩缩容时,有时候业务的资源使用率会导致 hpa 控制器在扩容、缩容之间不停切换,这种情况称为 flapping,hpa 提供一些参数可以优化这种行为。

扩缩容策略(scaling policies)

hpa 资源的 behavior 字段允许我们配置扩缩容策略,下面的配置配置了两个缩容策略:1)在一分钟内最多缩容 4 个 pod;2)在一分钟内最多缩容 10% 的 pod。当有多个扩缩容策略存在时,hpa 的策略 selectPolicy: Max 是:选择会导致最多副本数变化的策略。假设此 hpa CR 对应的 deployment 少于 40 个副本,那么 10% 小于 4,因此会选择策略 1);当副本数大于 40 时,将会选择策略2。(注意文档里提到了一个 case,如果有 72 个副本,72 的 10% 是 7.2,但在计算时,会 round up 到 8,因此会选择最多缩容 8 个副本。)

我们可以将 selectPolicy: Min 来实现 选择会导致最少副本数变化的策略;配置 selectPolicy: Disabled 为禁用扩容或者缩容。另外 peroidSeconds 的最大配置是 1800,也就是半小时。

spec:
  behavior:
    scaleDown:
      policies:
      - type: Pods
        value: 4
        periodSeconds: 60
      - type: Percent
        value: 10
        periodSeconds: 60
      selectPolicy: Max
      stabilizationWindowSeconds: 180

稳定窗口(stabilization window)

稳定窗口配置是为了缓解 flapping 也就是波动行为。具体配置是上面 yaml 中的 stabilizationWindowSeconds: 180 配置,这个配置是说:如果要进行缩容,请参考过去 5 分钟(180秒)计算出来的期望状态,并选择一个会导致副本数变化最大的配置。

默认行为(default behavior)

默认行为是不配置 behavior 时的默认参数,这个参考 Default Behavior

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
    - type: Pods
      value: 4
      periodSeconds: 15
    selectPolicy: Max

hpa 指标

hpa 获取指标是通过 K8s apiserver aggregation api 接口进行的,简单的说,当你访问 aggregation api 时,apiserver 实际是将你的请求转发到了集群里的一个应用服务。是扩展 apiserver 的一种方式。metrics-server 注册了一个 apiservices metrics.k8s.io,我们可以通过下面命令查看:

lr90@mo hpa % kubectl get apiservices | grep metrics
v1beta1.metrics.k8s.io                 kube-system/metrics-server   True        10m

我们通过查看这个 apiservice 具体配置可以查看其指向的具体 service,比如上面的 metrics-server,其最终访问的是 kube-system 下面的 metrics-server 这个 service。

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: metrics-server
    namespace: kube-system
    port: 443
  version: v1beta1
  versionPriority: 100

除了 metrics.k8s.io api, hpa 还支持自定以 api custom.metrics.k8s.io 以及外部metrics api external.metrics.k8s.io,metrics-server 没有提供后面两种 apiservices,不过其替代品 metrics。prometheus-adapter 实现了,后者可以将 prometheus 的指标转换为 hpa 能认识的指标。

metrics server

集群部署 metrics server 之后,可以通过 metrics server 提供的内存和 cpu 指标来进行扩缩容。我们可以通过下面命令了解 metrics server 给我们提供的 api:

kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/php-apache-5b56f9df94-jw6tw

返回的结果是 json 格式,内容如下。

{
    "kind": "PodMetrics",
    "apiVersion": "metrics.k8s.io/v1beta1",
    "metadata": {
        "name": "php-apache-5b56f9df94-jw6tw",
        "namespace": "default",
        "creationTimestamp": "2024-04-06T14:13:43Z",
        "labels": {
            "pod-template-hash": "5b56f9df94",
            "run": "php-apache"
        }
    },
    "timestamp": "2024-04-06T14:13:34Z",
    "window": "14.888s",
    "containers": [
        {
            "name": "php-apache",
            "usage": {
                "cpu": "107804n",
                "memory": "12108Ki"
            }
        }
    ]
}

自定义指标(custom metrics)

项目 prometheus-adapter 提供了可用 hpa 使用的自定义 metrics。prometheus-adapter 是 metrics-server 的可替代品,如果集群中已经部署了 prometheus,那么可以不用部署 metrics-server,而是部署 prometheus-adapter,该组件可将 prometheus 收集的指标转换为 hpa 可使用的指标。

文章《Kubernetes自定义指标HPA》写的很细致,值得学习。

本文概述了如何使用 hpa 自动扩缩容应用,后面介绍下怎么使用 keda 来扩缩容应用。