Kubernetes 内置了对CPU和内存的监测能力,但实际扩缩容决策常常依赖这个范围之外的信号:队列中有多少待处理消息,上一次批处理任务花了多久,一个Pod维持着多少活跃WebSocket连接。当内置指标不够用时,指标导出器可以弥补这一缺口。
本文从零开始编写导出器,将它打包为容器并接入集群,让Prometheus以及最终的HorizontalPodAutoscaler能够消费这些指标。
指标导出器实际做什么
导出器是一个小型HTTP服务器,职责是通过/metrics端点以文本形式暴露应用状态。Prometheus定期抓取这个端点,存储时间序列数据,供查询、告警和自动扩缩容规则使用。
某些情况下可以直接给应用加入监测代码:在同一进程内嵌入Prometheus客户端库并提供/metrics,无需独立导出器。当数据源位于应用之外,或你无法控制应用代码时,独立导出器更合适。
Prometheus需要纯文本格式:每行一个指标,包括名称、可选标签和数值。客户端库处理序列化,因此通常只需决定测量什么,并在值变化时调用相应函数。
决定测量什么
编写代码之前,先确定信号的类型。本文介绍Prometheus数据模型中的三类主要指标:
- **Counter(计数器)**只会增加,适合累计处理请求数、任务数和错误数。不能用它表示会下降的值。
- **Gauge(仪表)**表示当前值的快照,可以自由增减。队列深度、活跃连接数和缓存大小都适用。
- **Histogram(直方图)**记录观测值分布,例如请求延迟。除了平均值,还可以计算p99、p50等分位数。
选定类型后,采用snake_case并遵循<namespace>_<name>_<unit>命名惯例。任务处理器可暴露worker_jobs_processed_total(计数器)、worker_queue_depth(仪表)和worker_job_duration_seconds(直方图)。清晰名称能节省后续排障时间。
建立项目
Go的Prometheus客户端是Kubernetes生态中常见的导出器选择,主要因为大多数官方Kubernetes组件也使用它。先创建模块,添加依赖:
mkdir my-exporter && cd my-exporter
go mod init example.com/my-exporter
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
注册指标
创建main.go。首先声明指标,并向Prometheus默认注册表注册。注册告诉客户端库这些指标存在,使它们在尚未记录第一次观测之前就出现在输出中:
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
jobsProcessed = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "worker_jobs_processed_total",
Help: "Total number of jobs processed, partitioned by status.",
},
[]string{"status"},
)
queueDepth = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "worker_queue_depth",
Help: "Current number of jobs waiting in the queue.",
})
jobDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "worker_job_duration_seconds",
Help: "Time spent processing a single job.",
Buckets: prometheus.DefBuckets,
})
)
func init() {
prometheus.MustRegister(jobsProcessed, queueDepth, jobDuration)
}
prometheus.MustRegister在重复注册时触发panic,让配置问题在启动时显露。若将导出器嵌入一个库,而其他包也会注册指标,优先使用prometheus.Register并自行处理错误。
采集真实数值
注册之后,需持续更新指标。可以随数据变化实时更新,也可以运行内部刷新循环。下面的轮询模式通过goroutine定期读取应用的数据源,并更新已注册指标。请把模拟数值替换为对数据库、内部API或消息代理的真实调用:
import (
"math/rand"
"time"
)
func collectMetrics() {
for {
// Replace these with real reads from your application.
depth := float64(rand.Intn(50))
queueDepth.Set(depth)
start := time.Now()
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
jobDuration.Observe(time.Since(start).Seconds())
jobsProcessed.WithLabelValues("success").Inc()
time.Sleep(5 * time.Second)
}
}
轮询间隔(此处为五秒)应短于Prometheus抓取间隔,让每次抓取都读到新值。原文说明,多数集群部署的默认抓取间隔为十五秒,因此这里留有充足余量。
暴露端点
在main中接起采集循环与HTTP处理器。在/metrics旁提供/healthz,作为Kubernetes存活探针的目标,避免在健康检查路由暴露指标:
func main() {
go collectMetrics()
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
log.Println("Listening on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("server error: %v", err)
}
}
构建镜像前,先在本地检查输出:
go run .
curl http://localhost:8080/metrics | grep worker_
应当看到三个# HELP、# TYPE区块以及当前指标值。原文以这些输出作为导出器正常工作、可以容器化的检查依据。
构建容器镜像
多阶段构建缩小最终镜像,并避免把Go工具链带进生产环境。第一阶段编译静态链接二进制;第二阶段只把二进制复制到最小基础镜像。示例使用Docker,同样模式也适用于Buildah、Podman等兼容OCI的构建工具:
FROM golang:1.21-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /exporter .
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /exporter /exporter
EXPOSE 8080
ENTRYPOINT ["/exporter"]
distroless/static:nonroot没有shell和包管理器,默认以非root用户运行,通常无需额外配置即可符合多数集群安全策略。
将<registry>替换为自己的仓库地址,构建并推送镜像:
docker build -t <registry>/my-exporter:v1.0.0 .
docker push <registry>/my-exporter:v1.0.0
通常应由CI/CD流水线自动执行,而不是手工执行这些命令。
部署到集群
运行导出器只需两份清单:Deployment管理Pod生命周期,Service为Prometheus提供稳定抓取地址。若你的用例更适合抓取每一个Pod,也可以配置为逐Pod抓取。
下面使用monitoring命名空间,这是将Prometheus和相关组件放在一起运行的常见约定。按实际集群情况调整命名空间。
Deployment使用适合轻量级、类似sidecar进程的保守资源限制,并用/healthz作为存活探针:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-exporter
namespace: monitoring
labels:
app.kubernetes.io/name: my-exporter
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: my-exporter
template:
metadata:
labels:
app.kubernetes.io/name: my-exporter
spec:
containers:
- name: exporter
image: <registry>/my-exporter:v1.0.0
ports:
- name: metrics
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
Service把端口命名为metrics,后面的ServiceMonitor会引用该名称:
apiVersion: v1
kind: Service
metadata:
name: my-exporter
namespace: monitoring
labels:
app.kubernetes.io/name: my-exporter
spec:
selector:
app.kubernetes.io/name: my-exporter
ports:
- name: metrics
port: 8080
targetPort: metrics
应用两份清单:
kubectl apply -f deployment.yaml -f service.yaml
告诉Prometheus去哪里抓取
抓取配置取决于Prometheus的安装方式。
方案一:Prometheus Operator与ServiceMonitor
如果使用Prometheus Operator或kube-prometheus-stack Helm chart,创建ServiceMonitor前必须已有Operator在集群运行。release标签必须匹配Prometheus资源配置的标签选择器;原文标准Helm安装示例使用kube-prometheus-stack:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-exporter
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
selector:
matchLabels:
app.kubernetes.io/name: my-exporter
endpoints:
- port: metrics
interval: 15s
path: /metrics
方案二:基于注解的发现
如果Prometheus使用基于注解的Pod发现,需要在Prometheus配置里有匹配的scrape_config规则。向负责安装的人确认规则是否存在。
无论采用哪种方式,都可以在Pod模板添加以下三个注解。Prometheus Operator会忽略它们,而基于注解的配置会自动读取:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080" # omit if not using annotation-based discovery
prometheus.io/path: "/metrics" # omit if not using annotation-based discovery
若不确定集群使用哪种配置,ServiceMonitor方式更明确,也更容易调试。
验证抓取
向Prometheus Service建立端口转发,打开targets页面,确认导出器已经被发现:
kubectl port-forward svc/prometheus-operated 9090 -n monitoring
访问http://localhost:9090/targets。my-exporter目标应处于UP状态。如果显示DOWN,检查ServiceMonitor的release标签是否匹配,并确认Pod正在运行:
kubectl get pods -n monitoring -l app.kubernetes.io/name=my-exporter
kubectl describe servicemonitor my-exporter -n monitoring
目标健康后,在表达式浏览器运行简单查询,确认数据正在流入:
rate(worker_jobs_processed_total{status="success"}[2m])
非零结果表示整条链路在工作:应用产生数据,Prometheus抓取数据,时间序列已存储且可查询。
下一步
可用的导出器只是基础。下一步是把这些指标提供给HorizontalPodAutoscaler,让工作负载根据实际驱动负载的信号扩缩容,而不局限于CPU。这需要指标适配器。常用的Prometheus Adapter会通过Kubernetes Custom Metrics API注册自定义指标。
注册之后,集群中的HorizontalPodAutoscaler可在metrics区块引用worker_queue_depth或worker_jobs_processed_total。
配置流程参阅多个指标与自定义指标的自动扩缩容。数据库、消息代理与云服务的现成导出器目录可从Prometheus导出器与集成开始。
作者:Victor David Effiok。原文:Building a Custom Metrics Exporter for Kubernetes,发表于2026-07-14,原页最后修改于2026-08-14。依据官方网站仓库许可证,采用CC BY 4.0。改动:完整中文翻译、链接和代码块排版调整;保留原文命令、代码注释及版本标签。
静态核对补充:示例使用golang:1.21-alpine,而依赖命令没有固定版本;不能保证今天下载的最新依赖与Go1.21兼容。应按目标环境固定并核对依赖版本。math/rand、time的导入代码展示的是增补片段,需合并到main.go已有import区块中,不能原样放在变量声明之后。原文未包含HPA适配器的完整部署配置,本文也不补造。本环境未编译、运行或部署这些示例。










暂无评论内容