如何在Golang中实现微服务的自动扩缩容HPA Go语言Custom Metrics Adapter

6次阅读

k8s.io/metrics 官方包不提供 custom metrics adapter 实现,因它仅含 client 和 metrics-server 内部逻辑,未暴露 getmetricbyselector 等 grpc 接口;hpa 依赖 custom.metrics.k8s.io apiservice 下的 grpc 服务而非 http 接口,故需自行实现符合规范的 grpc server。

如何在Golang中实现微服务的自动扩缩容HPA Go语言Custom Metrics Adapter

为什么 k8s.io/metrics 官方包不能直接支持自定义指标 HPA

go 语言里没有现成的、开箱即用的 Custom Metrics Adapter 实现,因为 kubernetescustom.metrics.k8s.io API 要求你实现一个符合特定 gRPC 接口规范的服务器,而官方 k8s.io/metrics 只提供 client 和 metrics-server 内部逻辑,不暴露 adapter 所需的 GetMetricBySelector 等方法。

常见错误现象是:写了个 Go HTTP server 暴露 /metrics,然后以为 HPA 能自动识别——结果 kubectl get hpa 显示 Unknown 或报错 failed to get metric

  • HPA controller 不拉取你的 HTTP 接口,它只调用你注册在 APIService 下的 gRPC 服务
  • 你必须实现 custom.metrics.k8s.io/v1beta2 的完整 gRPC 接口(不是 REST)
  • Kubernetes v1.26+ 已弃用 v1beta2,但目前主流 adapter(包括 prometheus-adapter)仍用它;v1beta3 尚未被广泛支持

怎么用 Go 写一个最小可用的 Custom Metrics Adapter

核心是启动一个 gRPC server,注册 CustomMetricsProviderServer,并对接你的指标源(比如 Prometheus、StatsD 或本地 expvar)。

实操建议:

立即学习go语言免费学习笔记(深入)”;

  • github.com/prometheus/client_golang/prometheus 拉取指标,别自己写 HTTP client 去轮询 —— 它自带重试和缓存
  • 不要硬编码 http://prometheus:9090,从环境变量读取,比如 PROMETHEUS_URL
  • 对每个 MetricIdentifier,你要解析 selector 标签(如 app=order-service),再拼 Prometheus query,例如:sum(rate(http_request_duration_seconds_count{job="order-service"}[2m]))
  • 返回值单位必须匹配 HPA 配置:CPU 是 m(毫核),内存是 Mi,自定义指标可以是任意字符串(如 requests_per_second),但要在 CRD 里声明

示例片段(关键逻辑):

func (s *server) GetMetricBySelector(ctx context.Context, req *custom_metrics.MetricRequest) (*custom_metrics.MetricValueList, error) {     metricName := req.MetricName     selector := labels.ConvertSelectorToLabelsMap(req.Selector)     query := fmt.Sprintf(`sum(rate(%s{app="%s"}[2m]))`, metricName, selector["app"])     result, err := s.promAPI.Query(ctx, query, time.Now())     // ... 处理 result.Vector() 提取 value     return &custom_metrics.MetricValueList{         Items: []custom_metrics.MetricValue{{             DescribedObject: req.DescribedObject,             MetricName:      metricName,             Timestamp:       metav1.Time{Time: time.Now()},             Value:           *resource.NewMilliQuantity(int64(v*1000), resource.DecimalSI),         }},     }, nil }

部署时最常踩的三个坑

Adapter 跑起来不等于 HPA 能用,Kubernetes 控制面有多个校验点。

  • APIService 对象没创建或状态为 False:检查 kubectl get apiservice v1beta2.custom.metrics.k8s.io -o wideConditions 里是否显示 Available=True;常见原因是 TLS 证书不匹配或 service port 错误
  • HPA 引用指标名写错:必须和 adapter 返回的 MetricName 完全一致,且大小写敏感;如果 adapter 返回 http_requests_total,HPA 里写 httpRequestsTotal 就会失败
  • RBAC 权限不足:adapter 的 ServiceAccount 至少需要 getlist Namespacesservicesendpoints,否则无法解析 DescribedObject 中的 namespace/service 名

验证命令:

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/default/services/order-service/http_requests_total" | jq .

要不要自己写?先看 prometheus-adapter 是否够用

90% 的 Go 微服务场景,不需要从零写 adapter —— prometheus-adapter 是用 Go 写的、社区维护、支持 relabel、支持多租户、可配置 query 模板,而且它本身就能对接你的 Prometheus。

只有当你遇到这些情况才值得自己写:

  • 指标源不是 Prometheus(比如直接读 kafka offset、redis key count、或进程内 expvar)
  • 需要动态计算指标(如“过去 5 分钟错误率 > 5%”这种布尔型指标,HPA 要求数值型,得转成 0/1)
  • 对延迟极其敏感,不能接受 prometheus-adapter 的两级缓存(Prometheus → adapter → HPA)

注意:prometheus-adapter 的 rules 配置里,seriesQueryresources 字段容易配错,导致 HPA 找不到目标 Pod;务必用 kubectl get --raw 直接测 endpoint 返回是否含预期 items

text=ZqhQzanResources