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 客户端:
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,服务同时提供业务、健康与指标接口:
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())
}go mod tidy
go run .
# 在另一个终端检查
curl -fsS http://127.0.0.1:8080/metrics容器构建示例,Go 版本与工程约定保持一致:
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:
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:
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 来抓取同一端点:
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: 15sport: http 对应 Pod 中声明的容器端口名。旧示例中的 targetPort 在当前 PodMetricsEndpoint API 中已标为弃用,优先使用命名端口,并用集群已安装的 CRD 核对支持字段。
选择器与端点检查
Monitor 创建成功,只表示资源已保存。继续检查两层命名空间与两层对象选择:
| 配置 | 选择范围 |
|---|---|
| Prometheus 的 Monitor Namespace Selector | 到哪些命名空间找 Monitor |
| Prometheus 的 Monitor Selector | 选中哪些 Monitor 标签 |
Monitor 的 namespaceSelector |
到哪里寻找 Service 或 Pod |
Monitor 的 selector |
匹配哪些 Service 或 Pod 标签 |
检查当前配置:
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-goPrometheus 还需要对应的发现权限与网络访问能力。跨命名空间采集时,RBAC、NetworkPolicy 和 Monitor 的命名空间选择都要核对。
PromQL 验证
没有 Target:先查 Monitor 是否被选中、标签是否匹配,以及 Service 后端或 Pod 端口是否存在。必要时查看 Operator 日志和资源事件。
Target 存在但为 DOWN:查看 Scrape Error,检查端口、路径、TLS、鉴权、网络策略和超时。不能用“资源已创建”代替抓取成功。
Target 为 UP:再查业务指标。up=1 说明抓取成功,不代表每个指标的业务含义都正确。
up{namespace="demo"}给 /hello 发送一些请求,再看计数和延迟。查询窗口至少应包含数次抓取;刚启动时请求量不足,分位数可能没有有效结果。
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:
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。