对阿里云环境下ACK做扩缩容指标优化

  1. 1. 背景
  2. 2. 存在的问题
  3. 3. 针对性解决方案
    1. 3.1. prometheus-adapter
      1. 3.1.1. 规则配置
    2. 3.2. KEDA
      1. 3.2.1. 规则配置
    3. 3.3.

背景

Kubernetes的应用扩缩指标仅有CPU,通过这种单一指标来指导单一应用进行扩缩相对比较低效而且滞后。

在部分场景(大促营销/奖品开奖/定时秒杀)下,应用常态负载不高,出现扩容的情况多数为请求量激增导致的CPU使用率上升。而CPU负载飙高的情况下,应用多半已经难以正常运行开始报错退出了,此时再开始慢吞吞的扩容,等待扩容完成后,大量的流量也已经消失了。

另一个问题是HPA的独立性导致的:
举个例子,在流量激增的情况下,流量会先经过应用A的鉴权接口,此时A可能负载上升,发生扩容,而其余流量在这之后进入B应用,此时的流量可能有一部分已经由于A的网关限流功能被限制了,但对于其他应用来说依然存在较高的压力,此时CPU出现上升,才会出现这部分应用的扩容操作,也是一个问题。

存在的问题

  • 应用扩缩指标单一,不能准确反应负载
  • 各个应用扩缩策略相对独立,在应用有需求的时候无法做到根据其他应用的需求进行上下游的应用同时扩缩

针对性解决方案

为解决这些问题,我们有下面两种选择:

prometheus-adapter

先来说说prometheus-adapter,prometheus在配置中指定单一prometheus的地址,我们可以通过helm来拉取helm chart,修改参数后进行部署。

1
2
3
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm pull --untar prometheus-community/prometheus-adapter

由于默认镜像在github的镜像仓库中,推荐同步到私有库后更换镜像地址:

1
2
3
4
5
6
repository: PRIVATE_REGISTRY/prometheus-adapter
# if not set appVersion field from Chart.yaml is used
tag: "v0.11.2"
pullPolicy: IfNotPresent
pullSecrets:
- acr-registry-token

我们还需要配置prometheus的地址:

1
2
3
4
5
prometheus:
# 采用阿里云提供的聚合实例地址
url: http://xxxxxxx.arms.aliyuncs.com
port: 9090
path: "xxxxxxxxxxxxx"

规则配置

以下是重头戏:

我们此处以应用QPS为例:

查询应用QPS的promql为:

1
sum(irate(envoy_vhost_route_upstream_rq{envoy_clusterid="<YPUR_GW_ID>",route="<YOUR_ROUTE_NAME>"}[2m]))

由于我们通过envoy_clusterid以及route来匹配规则,而prometheus-adapter的custom目前只匹配pod_namenamespace两个标签,所以我们采用external rules

1
2
3
4
5
6
7
8
9
10
11
12
13
external: 
# prometheus的指标查询
- seriesQuery: 'envoy_vhost_route_upstream_rq'
resources:
# namespaced设定为false,使指标跨namespace查询
namespaced: false
name:
# 匹配指标名称的正则表达式
matches: "^(.*)"
# 匹配到的表达式最终在kubernetes
as: "${1}_per_second"
# 最终查询的promQL
metricsQuery: 'sum(irate(<<.Series>>{<<.LabelMatchers>>}[1m]))'

注: 在最终的查询PromQL中,seriesQuery会替换metricsQuery中的<<.Series>>,而<<.LabelMatchers>>则会根据HPA中配置的值来替换。

设置好后直接应用

1
$helm install  prometheus-adapter  ./prometheus-adapter  -n prometheus-adapter --create-namespace 

安装完成后可通过命令查看对应的resource:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/envoy_vhost_route_upstream_rq_per_second" |jq . 
{
"kind": "MetricValueList",
"apiVersion": "external.metrics.k8s.io/v1beta1",
"metadata": {
"selfLink": "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/envoy_vhost_route_upstream_rq_per_second"
},
"items": [
{
"metricName": "nginx_vts_server_requests_per_second",
"timestamp": "2023-12-20T11:30:47Z",
"value": "644230m",
"selector": null
}
]
}

然后我们可以通过HPA来定义这个规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: my-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-static
minReplicas: 1
maxReplicas: 10
metrics:
- type: External
external:
metric:
name: envoy_vhost_route_upstream_rq_per_second
selector:
matchLabels:
route: "xxxxx-router" # 路由名称
envoy_clusterid: "gw-xxxxxxxxx" # 填写阿里云网关id
target:
type: Value
value: 5

当这个值超过5的时候就会触发扩容。

KEDA

KEDA和prometheus-adapter不同,针对prometheus的配置放在HPA中,所以直接安装即可,1.22以及1.24版本的集群安装2.8.1版本

1
2
$helm repo add kedacore https://kedacore.github.io/charts
$helm install keta kedacore/keda -n keda --versio 2.8.1

规则配置

自定义指标扩缩容配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: prometheus-scaledobject
namespace: test
spec:
scaleTargetRef:
name: www-static-base # deployment名称
minReplicaCount: 2 # 最小副本数
maxReplicaCount: 20 # 最大副本数
triggers:
# 配置多个扩缩容规则
- type: prometheus
metadata:
metricName: envoy_vhost_route_upstream_rq
serverAddress: xxx # 阿里云 Prometheus 私有地址
threshold: '10'
query: sum(irate(envoy_vhost_route_upstream_rq{envoy_clusterid="gw-xxxxxxx",route="xxxxxxx"}[2m])) # 分别填写网关id 以及路由id

其中triggers支持配置多种规则,KEDA内置了多种不同的scaler,如kafka,prometheus,CPU以及根据其他应用等比扩缩,一个示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
triggers:
# 根据prometheus指标扩缩容
- type: prometheus
metadata:
metricName: envoy_vhost_route_upstream_rq
serverAddress: <PROMETHEUS_ADDRESS>
threshold: '10'
query: <PROMQL>
# 容器CPU负载
- type: cpu
metricType: Utilization # Allowed types are 'Utilization' or 'AverageValue'
metadata:
type: Utilization # Deprecated in favor of trigger.metricType; allowed types are 'Utilization' or 'AverageValue'
value: "600"
# 定时扩缩
- type: cron
metadata:
timezone: Asia/Shanghai
start: 0 0 * * *
end: 45 0 * * *
desiredReplicas: "10"
# 根据比例扩缩
- type: kubernetes-workload
metadata:
podSelector: 'app=backend' # 目标应用标签选择器
value: '0.5' # 比例 = (选择应用副本数/被扩缩的应用副本数)
# 根据kafka指标扩缩
- type: kafka
metadata:
bootstrapServers: kafka.svc:9092
consumerGroup: my-group
topic: test-topic
lagThreshold: '5'
activationLagThreshold: '3'
offsetResetPolicy: latest
allowIdleConsumers: false
scaleToZeroOnInvalidOffset: false
excludePersistentLag: false
version: 1.0.0
partitionLimitation: '1,2,10-20,31'
sasl: plaintext
tls: enable
unsafeSsl: false