背景#

我在homelab的k8s集群中使用helm部署了postgresql数据库,使用的是bitnami/postgresql这个chart。

setup-postgres: add-repos
    helm upgrade --install postgresql bitnami/postgresql -n database \
    --create-namespace \
    -f values/postgresql-values.yaml

启用了postgresql的metrics功能,prometheus operator会自动发现了这个服务,并创建了对应的ServiceMonitor对象。

... other settings ...
metrics:
  enabled: true  # 启用 Prometheus 监控
  serviceMonitor:
    enabled: true
    namespace: monitoring                       # ServiceMonitor 创建在哪个命名空间(Prometheus 能访问)
    labels:                                     # 保证与 Prometheus CR 的 selector 匹配
      release: prometheus                       # 问题配置
    interval: 30s

安装完成后查看prometheus的targets,发现没有采集到postgresql的指标数据。

# 端口转发
kubectl port-forward svc/kube-prometheus-stack-prometheus -n monitoring 9090:9090

# 在另一终端查看 active targets(或在浏览器打开 http://localhost:9090 -> Status -> Targets)
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job|test("postgres|postgresql"))'

为什么呢?

ServiceMonitor 动态发现服务的过程#

对于PostgreSQL这种通过ServiceMonitor动态发现的服务,Prometheus Operator 会根据 ServiceMonitor 的配置动态生成 Prometheus 的抓取配置。

graph LR
    subgraph Kubernetes
        SM[ServiceMonitor CRD] -- 1 声明 --> OP(Prometheus Operator);
        S[Target Service] -- 2 匹配 Selector --> OP;
        OP -- 3 动态生成 Config --> P(Prometheus Server);
    end
    P -- 4 周期性 Pull --> S;

    style OP fill:#9f9,stroke:#333,stroke-width:2px
    style P fill:#ff9,stroke:#333,stroke-width:2px
  1. 声明 (SM $\rightarrow$ OP): 用户创建 ServiceMonitor (SM),声明需要监控哪个 Service。

  2. 匹配 (S $\rightarrow$ OP): Operator (OP) 通过 Service 的标签与 SM 的 selector 进行匹配。

  3. 转换 (OP $\rightarrow$ P): Operator 自动将 SM 的意图转换成 Prometheus 的抓取配置,并应用到 Prometheus Server (P)。

  4. 执行 (P $\rightarrow$ S): Prometheus Server 动态地开始拉取目标 Service 的指标。

思路#

根据上述过程,可以从以下几个方面排查问题:

  1. 检查 PostgreSQL 是否暴露 metrics 端点。
  2. 检查 ServiceMonitor的配置是否可以正确匹配到 PostgreSQL 服务。
  3. 检查 Prometheus Operator是否可以正确匹配到 ServiceMonitor,并生成正确的抓取配置。

排查步骤#

1. 检查PostgreSQL是否暴露metrics端点#

kubectl port-forward svc/postgresql-metrics -n database 9187:9187
curl http://localhost:9187/metrics

可以看到返回了大量的指标数据,说明PostgreSQL的metrics端点是正常的。

2. 检查ServiceMonitor的配置是否正确#

kubectl get servicemonitor -n monitoring

可以看到输出

NAME                                             AGE
kube-prometheus-stack-alertmanager               23h
kube-prometheus-stack-apiserver                  23h
kube-prometheus-stack-coredns                    23h
kube-prometheus-stack-grafana                    23h
kube-prometheus-stack-kube-proxy                 23h
kube-prometheus-stack-kube-state-metrics         23h
kube-prometheus-stack-kubelet                    23h
kube-prometheus-stack-operator                   23h
kube-prometheus-stack-prometheus                 23h
kube-prometheus-stack-prometheus-node-exporter   23h
postgresql                                       4h33m

说明postgresqlServiceMonitor已经创建成功。

kubectl get servicemonitor postgresql -n monitoring -o yaml

输出

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  annotations:
    meta.helm.sh/release-name: postgresql
    meta.helm.sh/release-namespace: database
  creationTimestamp: "2025-10-26T02:10:26Z"
  generation: 2
  labels:
    app.kubernetes.io/component: metrics
    app.kubernetes.io/instance: postgresql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: postgresql
    app.kubernetes.io/version: 18.0.0
    helm.sh/chart: postgresql-18.1.1
    release: prometheus
  name: postgresql
  namespace: monitoring
  resourceVersion: "258365"
  uid: cc371f23-fd28-47b5-99f9-ef410a7ba0fa
spec:
  endpoints:
  - interval: 30s
    port: http-metrics
  namespaceSelector:
    matchNames:
    - database
  selector:
    matchLabels:
      app.kubernetes.io/component: metrics
      app.kubernetes.io/instance: postgresql
      app.kubernetes.io/name: postgresql

spec.namespaceSelectorspec.selector可以看到,ServiceMonitor配置的是监控database命名空间下,带有如下标签的服务:

app.kubernetes.io/component: metrics
app.kubernetes.io/instance: postgresql
app.kubernetes.io/name: postgresql

查看postgresql-metrics服务的标签:

kubectl get svc postgresql-metrics -n database --show-labels

输出

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE     LABELS
postgresql-metrics   ClusterIP   10.152.183.222   <none>        9187/TCP   4h42m   app.kubernetes.io/component=metrics,app.kubernetes.io/instance=postgresql,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=postgresql,app.kubernetes.io/version=18.0.0,helm.sh/chart=postgresql-18.1.1

所有标签都匹配成功,说明ServiceMonitor的配置是正确的。

3. 检查Prometheus Operator是否能正确匹配到ServiceMonitor#

kubectl get prometheus -n monitoring

可以看到输出

NAME                               VERSION   DESIRED   READY   RECONCILED   AVAILABLE   AGE
kube-prometheus-stack-prometheus   v3.7.2    1         1       True         True        23h
{}

这里可以看到prometheus的实例名称是kube-prometheus-stack-prometheus,该实例运行正常。

 kubectl get prometheus kube-prometheus-stack-prometheus -n monitoring -o yaml | yq '.spec.serviceMonitorNamespaceSelector, .spec.serviceMonitorSelector'

可以看到Prometheus的serviceMonitorSelector配置如下:

{}
matchLabels:
  release: kube-prometheus-stack

这说明Prometheus只会抓取带有release: kube-prometheus-stack标签的ServiceMonitor。 而我们的postgresqlServiceMonitor标签是release: prometheus,不匹配。

解决方法#

这个release标签是kube-prometheus-stack helm chart自动添加的,默认值是release: {{ .Release.Name }}。 所以我们需要修改postgresqlServiceMonitorrelease标签,使其与Prometheus的serviceMonitorSelector匹配。 修改values/postgresql-values.yaml,添加如下内容:

metrics:
  enabled: true  # 启用 Prometheus 监控
  serviceMonitor:
    enabled: true
    namespace: monitoring                       # ServiceMonitor 创建在哪个命名空间(Prometheus 能访问)
    labels:                                     # 保证与 Prometheus CR 的 selector 匹配
      release: kube-prometheus-stack
    interval: 30s

然后重新执行helm升级命令:

just setup-postgres

总结#

通过以上排查步骤,发现ServiceMonitorrelease标签与Prometheus的serviceMonitorSelector不匹配,导致Prometheus没有抓取到PostgreSQL的指标数据。修改标签后,Prometheus成功采集到了PostgreSQL的指标。

大多数应用中,我们并不需要考虑ServiceMonitor,而是直接对service增加prometheus相关的annotations就可以让prometheus operator自动对我们的服务进行metrics采集了。这个到写具体的服务的时候再说吧。

源码#

source code