Prometheus Operator:选对 ServiceMonitor 和 PodMonitor

Go 指标服务的部署、ServiceMonitor/PodMonitor 选择器配置与采集验证。

云原生可观测

ServiceMonitor 按 Service 标签发现采集端点,PodMonitor 直接按 Pod 标签发现端点。部署 Go 指标服务后,选择一种方式接入 Prometheus,再验证 Targets、请求速率和 P95。集群需已安装 Prometheus Operator 及对应 CRD。

两种 Monitor 的区别

对比项 ServiceMonitor PodMonitor
选择对象 Service 的标签 Pod 的标签
是否需要 Service 需要 不需要
端口名称来自哪里 Service 的端口名 Pod 声明的容器端口名
常见用途 已有稳定 Service 的业务应用 无需 Service 的进程、独立采集端口或 sidecar

两者最终都可以发现多个 Pod,并非一个按服务采集、另一个才能按 Pod 采集。同一端点通常选一种方式,避免重复抓取。

短生命周期 Job 可能在下次抓取前结束,单靠 PodMonitor 不能保证采到数据,需要根据任务特点另行设计。

部署 Go 指标服务

在新目录创建模块,安装 Prometheus 客户端:

Shell
go mod init example.com/demo-go-metrics
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp

将以下内容保存为 main.go,服务同时提供业务、健康与指标接口:

Go
package main

import (
    "log"
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    requests := promauto.NewCounter(prometheus.CounterOpts{
        Name: "demo_http_requests_total", Help: "Hello requests.",
    })
    duration := promauto.NewHistogram(prometheus.HistogramOpts{
        Name: "demo_http_request_duration_seconds",
        Help: "Hello request duration in seconds.",
    })
    build := promauto.NewGaugeVec(prometheus.GaugeOpts{
        Name: "demo_build_info", Help: "Build information.",
    }, []string{"version"})
    build.WithLabelValues("v1.0.0").Set(1)

    mux := http.NewServeMux()
    mux.Handle("/metrics", promhttp.Handler())
    mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("ok"))
    })
    mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        defer func() { duration.Observe(time.Since(start).Seconds()) }()
        requests.Inc()
        w.Write([]byte("hello"))
    })
    server := &http.Server{
        Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second,
    }
    log.Fatal(server.ListenAndServe())
}
Shell
go mod tidy
go run .
# 在另一个终端检查
curl -fsS http://127.0.0.1:8080/metrics

容器构建示例,Go 版本与工程约定保持一致:

Dockerfile
FROM golang:1.24 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN CGO_ENABLED=0 go build -o /out/app .

FROM scratch
COPY --from=build /out/app /app
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/app"]

构建并推送到自己的镜像仓库,再创建 Namespace 与 Deployment:

YAML
apiVersion: v1
kind: Namespace
metadata:
  name: demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-go
  namespace: demo
spec:
  replicas: 2
  selector:
    matchLabels: {app: demo-go}
  template:
    metadata:
      labels: {app: demo-go}
    spec:
      containers:
        - name: demo-go
          image: YOUR_REGISTRY/demo-go-metrics:1.0.0
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests: {cpu: 50m, memory: 64Mi}
            limits: {cpu: 500m, memory: 256Mi}
          readinessProbe:
            httpGet: {path: /healthz, port: http}
          livenessProbe:
            httpGet: {path: /healthz, port: http}

把镜像占位符替换为真实地址;私有仓库按集群配置拉取凭据。

配置 Service 和 ServiceMonitor

上一步部署的应用运行在 demo 命名空间,Pod 标签为 app: demo-go,容器声明名为 http 的 8080 端口。确认 Pod Ready、/metrics 可访问后继续。

为 ServiceMonitor 配置 Service:

YAML
apiVersion: v1
kind: Service
metadata:
  name: demo-go
  namespace: demo
  labels:
    app: demo-go
spec:
  selector:
    app: demo-go
  ports:
    - name: metrics
      port: 8080
      targetPort: http
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: demo-go
  namespace: monitoring
  labels:
    release: prometheus
spec:
  namespaceSelector:
    matchNames: [demo]
  selector:
    matchLabels:
      app: demo-go
  endpoints:
    - port: metrics
      path: /metrics
      interval: 15s

这里有三处不同含义:Service 标签用于被 Monitor 选中,Service 的 selector 用于选择 Pod,endpoints.port 使用 Service 的端口名 metrics。

命名空间需要预先存在,集群也需要安装相应 CRD。示例中的 release: prometheus 只是标签约定,必须按实际 Prometheus 选择器调整。

PodMonitor 配置

如果不需要 Service,针对同一应用可以改用下面的配置。不要同时应用上面的 ServiceMonitor 来抓取同一端点:

YAML
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: demo-go
  namespace: monitoring
  labels:
    release: prometheus
spec:
  namespaceSelector:
    matchNames: [demo]
  selector:
    matchLabels:
      app: demo-go
  podMetricsEndpoints:
    - port: http
      path: /metrics
      interval: 15s

port: http 对应 Pod 中声明的容器端口名。旧示例中的 targetPort 在当前 PodMetricsEndpoint API 中已标为弃用,优先使用命名端口,并用集群已安装的 CRD 核对支持字段。

选择器与端点检查

Monitor 创建成功,只表示资源已保存。继续检查两层命名空间与两层对象选择:

配置 选择范围
Prometheus 的 Monitor Namespace Selector 到哪些命名空间找 Monitor
Prometheus 的 Monitor Selector 选中哪些 Monitor 标签
Monitor 的 namespaceSelector 到哪里寻找 Service 或 Pod
Monitor 的 selector 匹配哪些 Service 或 Pod 标签

检查当前配置:

Shell
kubectl get prometheus -n monitoring -o yaml
kubectl get servicemonitor,podmonitor -n monitoring --show-labels
kubectl get svc,pods -n demo --show-labels
kubectl get endpointslices -n demo \
  -l kubernetes.io/service-name=demo-go

Prometheus 还需要对应的发现权限与网络访问能力。跨命名空间采集时,RBAC、NetworkPolicy 和 Monitor 的命名空间选择都要核对。

PromQL 验证

没有 Target:先查 Monitor 是否被选中、标签是否匹配,以及 Service 后端或 Pod 端口是否存在。必要时查看 Operator 日志和资源事件。

Target 存在但为 DOWN:查看 Scrape Error,检查端口、路径、TLS、鉴权、网络策略和超时。不能用“资源已创建”代替抓取成功。

Target 为 UP:再查业务指标。up=1 说明抓取成功,不代表每个指标的业务含义都正确。

PromQL
up{namespace="demo"}

给 /hello 发送一些请求,再看计数和延迟。查询窗口至少应包含数次抓取;刚启动时请求量不足,分位数可能没有有效结果。

Shell
kubectl port-forward -n demo deployment/demo-go 18080:8080
# 在另一个终端执行
for i in $(seq 1 50); do
  curl -fsS http://127.0.0.1:18080/hello >/dev/null
done

示例应用若定义了相应 Counter 和经典 Histogram,可继续检查请求速率与 P95:

PromQL
sum(rate(demo_http_requests_total[5m]))

histogram_quantile(
  0.95,
  sum by (le) (rate(demo_http_request_duration_seconds_bucket[5m]))
)

指标名称需与应用实际输出一致;不同服务或路由需要分别计算时,应在聚合中保留对应标签。

删除 Monitor 后,范围查询仍可能看到历史序列。是否还有当前采集,应检查 Targets 和采集配置,而不是只看历史图上是否还有 up。

参考

一丝痕迹

很高兴,在这里遇见你。

头像,随邮箱而来。

填写邮箱后,会自动匹配 Gravatar。