一、问题背景:弹性伸缩为啥突然“罢工”
很多做云原生应用的开发者,都用过Knative Serving来做服务的弹性伸缩——简单说就是服务访问量多的时候自动多开实例扛压力,没人访问的时候自动关掉省资源,不用人工盯着调。但不少人遇到过一个坑:明明配置了自定义指标(比如按接口请求延迟、队列长度伸缩),结果要么缩到只剩一个实例卡着不动,要么访问量爆了实例数纹丝不变,完全没弹性。我之前帮一家做在线课程平台的客户排查过这个问题,他们的直播回放服务,高峰期明明延迟已经飙到2秒,实例数还是只有3个,导致大量用户加载失败,后来发现就是自定义指标伸缩的配置异常导致的。
二、核心原理:Knative自定义指标伸缩怎么工作
要解决问题,得先搞懂Knative自定义指标伸缩的基本逻辑,不然改配置都是瞎蒙。Knative的弹性伸缩核心是一个叫Autoscaler的组件,它的工作流程分三步:
- 收集指标:从Prometheus(Knative默认用的监控组件)拉取你定义的自定义指标,比如接口的平均响应延迟、任务队列的长度;
- 计算实例数:用你配置的目标值,算出来当前需要多少个实例——比如目标延迟是500ms,现在平均延迟是2000ms,那需要的实例数就是原来的4倍;
- 调整实例:把算出来的实例数告诉Kubernetes的控制器,让它开或者关Pod。
这里的关键是“目标值”的配置,还有指标的拉取规则,只要有一个环节错了,弹性就会失效。
三、常见异常场景与处理方案
3.1 场景1:自定义指标拉取失败,Autoscaler拿不到数据
问题表现
最明显的是Knative的Autoscaler日志里会出现“failed to fetch metric”的错误,同时你用kubectl describe看服务的伸缩状态,会发现没有实例数的变化记录。比如我之前排查的课程平台案例,他们配置的是按接口延迟伸缩,但Autoscaler连延迟数据都拉不到,自然不会调整实例数。
技术栈与示例
这里我们用Knative Serving + Prometheus + Go(服务用Go写,方便模拟延迟)的技术栈来做示例,所有配置都符合Knative的官方规范。
首先,先确认Prometheus有没有正确拉取到服务的指标。先写一个简单的Go服务,暴露Prometheus格式的延迟指标:
package main
import (
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// 定义自定义指标:接口的平均响应延迟,单位是毫秒
var apiLatency = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "api_response_latency_ms", // 指标名称,必须和Prometheus里的一致
Help: "Average response latency of the API in milliseconds",
},
[]string{"path"}, // 标签,用来区分不同接口
)
func init() {
// 注册指标到Prometheus的默认注册表
prometheus.MustRegister(apiLatency)
}
func main() {
// 模拟课程列表接口,随机生成延迟
http.HandleFunc("/course/list", func(w http.ResponseWriter, r *http.Request) {
// 随机生成100-3000ms的延迟,模拟高峰期的情况
delay := time.Duration(100+rand.Intn(2900)) * time.Millisecond
time.Sleep(delay)
// 更新延迟指标,path标签为/course/list
apiLatency.WithLabelValues("/course/list").Set(float64(delay.Milliseconds()))
w.Write([]byte("course list"))
})
// 暴露Prometheus的指标接口,路径是/metrics
http.Handle("/metrics", promhttp.Handler())
log.Println("server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
然后,我们需要给这个Go服务写Knative的服务配置,同时配置自定义指标伸缩:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: course-service
namespace: default
spec:
template:
metadata:
annotations:
# 开启自定义指标伸缩,类型是目标指标(target)
autoscaling.knative.dev/target: "500" # 目标延迟是500ms
autoscaling.knative.dev/metric: "api_response_latency_ms" # 自定义指标名称
autoscaling.knative.dev/metric-type: "target" # 指标类型,必须是target
autoscaling.knative.dev/scale-down-delay: "1m" # 缩容延迟,1分钟
autoscaling.knative.dev/scale-up-delay: "10s" # 扩容延迟,10秒
spec:
containers:
- image: your-registry/course-service:v1 # 替换成你自己的镜像地址
ports:
- containerPort: 8080
排查与修复步骤
如果出现指标拉取失败,首先要检查三个地方:
- 指标名称是否完全一致:比如Go代码里的指标名是
api_response_latency_ms,Knative配置里的autoscaling.knative.dev/metric必须完全一样,大小写、下划线都不能错,差一个字符都拉不到; - Prometheus的抓取配置:要确保Prometheus的配置里有抓取这个服务的
/metrics接口的规则,比如:
scrape_configs:
- job_name: 'course-service'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: course-service
action: keep
metrics_path: '/metrics'
如果没有这个配置,Prometheus根本不会去拉这个服务的指标;
3. 指标的标签:如果指标有标签(比如我们的例子里的path标签),Knative配置里要指定标签的匹配规则,不然Autoscaler不知道要取哪个标签对应的指标,比如要加autoscaling.knative.dev/metric-selector: "path=/course/list",表示只取path标签为/course/list的延迟指标。
3.2 场景2:目标值配置不合理,伸缩逻辑出错
问题表现
比如访问量已经爆了,实例数还是上不去,或者稍微有点访问就开很多实例。比如之前有个客户配置的目标延迟是100ms,结果高峰期延迟到150ms,按道理应该扩容,但Autoscaler算出来的实例数反而比原来少,后来发现是目标值的单位错了——他们的指标是秒,但配置的目标值是按毫秒算的。
排查与修复步骤
- 确认指标的单位:比如你的指标是秒还是毫秒,目标值要和指标的单位一致。比如我们的例子里指标是毫秒,目标值是500(即500ms),如果指标是秒,目标值就要改成0.5;
- 确认指标类型:Knative的自定义指标分两种,一种是“目标指标”(target),比如延迟,是每个实例的目标值;另一种是“聚合指标”(aggregate),比如队列长度,是整个服务的总目标值。如果把队列长度的指标配置成target类型,伸缩逻辑就会错,比如总队列长度是100,目标值是10,那需要的实例数是10个,但如果配置成target,Autoscaler会认为每个实例的队列长度是100,需要的实例数是100/10=10?不对,其实target类型是按每个实例的指标值算,aggregate是按总指标值算,所以如果是总队列长度,必须配置
autoscaling.knative.dev/metric-type: "aggregate"; - 确认伸缩的上下限:比如你配置了
autoscaling.knative.dev/min-scale: "1"(最小实例数1),autoscaling.knative.dev/max-scale: "3"(最大实例数3),那哪怕需要10个实例,也最多只能开3个,这也是常见的坑,很多人忘了改最大实例数的限制。
3.3 场景3:Autoscaler的配置冲突,导致伸缩逻辑混乱
问题表现
比如你既配置了按请求数伸缩,又配置了按自定义指标伸缩,结果两个逻辑互相冲突,导致实例数波动异常。比如有的开发者为了保险,同时配置了autoscaling.knative.dev/target-requests: "100"(按请求数伸缩)和autoscaling.knative.dev/metric: "api_response_latency_ms"(按延迟伸缩),结果Autoscaler会按两个指标里算出来的最大实例数来调整,导致实例数突然变多。
排查与修复步骤
- 检查配置的冲突:Knative的Autoscaler不建议同时配置多种伸缩策略,除非你明确知道自己要什么。如果是自定义指标伸缩,要把默认的按请求数伸缩的配置去掉,比如不要加
autoscaling.knative.dev/target-requests; - 检查全局配置和服务级配置的冲突:Knative的全局Autoscaler配置(在ConfigMap里)可能会有默认的目标值,比如全局配置了
target: "100",而你的服务级配置了target: "500",这时候服务级配置会覆盖全局配置,但如果全局配置的指标类型和服务级的不一样,就会出问题,所以最好只在服务级配置自定义指标的所有参数,不要依赖全局配置。
四、应用场景、技术优缺点与注意事项
4.1 应用场景
自定义指标伸缩适合所有需要根据业务实际负载调整实例数的场景,比如:
- 在线服务:按接口延迟、错误率伸缩,保证服务质量;
- 异步任务服务:按任务队列长度伸缩,保证任务及时处理;
- 数据处理服务:按CPU使用率、内存使用率伸缩,保证数据处理的效率。 比如我们之前的课程平台,直播回放的服务,高峰期用户访问量暴增,接口延迟飙升,这时候用自定义指标伸缩,就能自动扩容实例,保证用户体验;平时访问量少的时候自动缩容,节省资源。
4.2 技术优缺点
优点
- 灵活性高:可以根据自己的业务指标来伸缩,而不是只能用请求数、CPU这些通用指标;
- 精准性强:能更准确地反映业务的实际负载,比如延迟高的时候,哪怕请求数不多,也需要扩容;
- 节省资源:比固定实例数的部署方式节省资源,比按通用指标伸缩更能适应业务的变化。
缺点
- 配置复杂:需要自己写指标的收集、暴露,配置Knative的伸缩参数,比默认的按请求数伸缩麻烦;
- 依赖监控组件:需要Prometheus等监控组件的支持,如果监控组件出问题,伸缩就会失效;
- 调试难度大:如果伸缩出问题,需要排查指标拉取、计算、调整等多个环节,不容易定位问题。
4.3 注意事项
- 指标的准确性:要确保你的自定义指标是准确的,比如延迟指标不能有延迟,不然Autoscaler的计算会出错;
- 伸缩的平滑性:要配置合理的扩容和缩容延迟,比如扩容延迟设成10秒,避免访问量突然增加时瞬间开太多实例,缩容延迟设成1分钟,避免访问量波动时频繁开关实例;
- 实例数的限制:要根据业务的实际情况配置最小和最大实例数,最小实例数不能设成0(除非你能接受服务启动的延迟),最大实例数不能设得太小(避免高峰期不够用);
- 监控Autoscaler的状态:要定期监控Autoscaler的日志和状态,及时发现指标拉取失败、计算错误等问题。
五、文章总结
Knative Serving的自定义指标伸缩是一个非常实用的功能,但配置不当很容易导致弹性失效。常见的异常场景包括指标拉取失败、目标值配置不合理、配置冲突等,解决这些问题的核心是搞懂自定义指标伸缩的原理,然后逐一排查每个环节的配置是否正确。
在实际使用中,要注意指标的准确性、伸缩的平滑性、实例数的限制,同时要监控Autoscaler的状态,及时发现和解决问题。如果你是第一次用自定义指标伸缩,建议先在测试环境里验证配置,比如模拟高峰期的负载,观察实例数的变化,确认伸缩逻辑正常后再上线。
最后,再给大家一个小技巧:如果遇到伸缩失效的问题,先看Autoscaler的日志,用kubectl logs -n knative-serving <autoscaler-pod-name>,日志里会明确告诉你是拉不到指标,还是计算有问题,或者是其他错误,这能帮你快速定位问题。
评论
围绕“Knative Serving的自定义指标伸缩配置异常引发的弹性失效处理方案”参与讨论