目录
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 )]
该公式的计算过程为:
- 计算当前 metrics 指标与期望 metrics 的比率。
- 用这个比率乘以当前副本数,并向上取整。
结合我们上边的例子,这个公式的效果是使 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>>
: 会被替换为分组标签,这里是 pod
和 namespace
。
其中,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 来扩缩容应用。