K8s hpa 自动扩缩容概览与使用(一)

目录

K8s 原生 hpa

K8s 原生 hpa 的文档为 Horizontal Pod Autoscaling。hpa 是 Kubernetes 默认支持的应用自动扩缩容的一种方式。

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。prometheus-adapter 实现了 custom.metrics.k8s.io,后者可以将 prometheus 的指标转换为 hpa 能认识的指标,需要定义 prometheus adapter rule,将对 custom.metrics.k8s.io api 的查询转换为对 prometheus metrics 的查询。

另外开源社区 keda 实现了 external.metrics.k8s.io,关于 keda 会有专门文章介绍。

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"
            }
        }
    ]
}

metrics server 在安装时会通过一个名称为 v1beta1.metrics.k8s.io 的 apiservice 资源注册服务, metrics server 启动后,会周期性的向 kubelet 拉指标,但是其仅存储最近的两个指标点,并根据这个两个指标计算利用率。关于 metrics server 提供的 api 可以参考 resource-metrics-api.md

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

prometheus-adapter

项目 prometheus-adapter 提供了可用 hpa 使用的自定义 metrics,其实现了 custom.metrics.k8s.io。如果集群中已经部署了 prometheus,那么可以不用部署 metrics-server,而是部署 prometheus-adapter,该组件可将 prometheus 收集的指标转换为 hpa 可使用的指标(包括 pod 的 cpu 和内存利用率等)。

在 hpa 中,支持将 metricstype 设置为 Pods,即通过 pod 的自定义指标来进行扩缩容,在下面配置中,期望将 http_requests_per_second 监控指标的平均值设置为 100。那 hpa 如何获取这个指标值呢?需要通过 prometheus adapter 来进行。

metrics:
- type: Pods
  pods:
    metric:
      name: http_requests_per_second  # 自定义指标名称
    target:
      type: AverageValue
      averageValue: 100  # 目标每秒 100 请求

为了获取上述监控指标的值,我们需要定义 adapter 的 rule,将 custom.metrics.k8s.io 转换为对 prometheus 监控指标的查询。

# prometheus-adapter-values.yaml
rules:
  default: false
  custom:
  - seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
    resources:
      overrides:
        namespace: {resource: "namespace"}
        pod: {resource: "pod"}
    name:
      matches: "^(.*)_total"
      as: "${1}_per_second"
    metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'

在上面配置中,通过 name 中的 matches 以及 as 将 http_requests_total 命名为 http_requests_per_second,并通过 metricsQuery 给出了查询表达式。其中 <<.Series>>: 会被替换为原始指标名 http_requests_total;<<.LabelMatchers>> 会被替换为来自 hpa 请求的标签匹配条件;<<.GroupBy>>: 会被替换为分组标签,这里是 podnamespace

其中,hpa 是通过类似下面服务查询 http_requests_per_second 监控指标的。

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests_per_second" | jq .

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

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