概述

在 Prometheus 的监控体系中,服务发现(Service Discovery,简称 SD)是连接"被监控目标"与"采集引擎"的桥梁。当你的基础设施从几台虚拟机扩展到数百个 Kubernetes Pod、跨可用区云主机、Consul 注册节点时,手动维护 static_configs 就变成了一场灾难——每次扩容、缩容、迁移都要改配置、重启 Prometheus,告警还会因为 target 不可达而误报。

Prometheus 原生支持十余种服务发现机制,能够在目标变更后自动感知,无需重启。我将从静态配置出发,逐步深入 file_sd、kubernetes_sd、consul_sd、dns_sd、ec2_sd 等主流方案,并详细讲解 relabel_configs 标签管理这一核心能力,最后给出多集群监控的落地实践。

参考来源:Prometheus 官方文档 — Configuration

一、为什么需要服务发现

1.1 静态配置的局限

先看一个最简单的静态配置:

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets:
          - '192.168.1.10:9100'
          - '192.168.1.11:9100'
          - '192.168.1.12:9100'
        labels:
          env: 'production'
          region: 'beijing'

这在服务器数量固定时没什么问题。但考虑以下场景:

场景静态配置的痛点
Kubernetes Pod 扩缩容Pod IP 每次重建都变,手动改配置不现实
云厂商 Auto Scaling弹性伸缩后新实例无法被监控,存在盲区
蓝绿部署 / 金丝雀发布新版本实例需要自动加入监控
多机房迁移IP 段变更,需要批量修改配置
容器化微服务实例数量随时变化,生命周期短

1.2 服务发现的核心价值

┌─────────────┐         ┌───────────────────┐         ┌──────────────┐
│  服务注册中心  │ ← 感知 → │  Prometheus SD    │ ← 采集 → │  Target 实例  │
│ (Consul/K8s) │         │  (自动更新 target) │         │ (Exporter)   │
└─────────────┘         └───────────────────┘         └──────────────┘
                     ┌───────────────────┐
                     │  relabel_configs   │
                     │  (标签过滤/改写)    │
                     └───────────────────┘

服务发现机制让 Prometheus 从"被动配置"变为"主动感知":

  • 自动发现:新实例启动后自动加入监控,无需人工干预
  • 自动剔除:实例下线后自动从 target 列表中移除
  • 标签丰富:从服务注册中心获取元数据,自动打标签
  • 动态过滤:通过 relabel 灵活控制采集范围

二、file_sd:基于文件的服务发现

file_sd 是最简单也最灵活的服务发现方式。Prometheus 定期读取指定文件(JSON 或 YAML),文件内容的变更会自动生效。

2.1 配置示例

scrape_configs:
  - job_name: 'file-sd-nodes'
    file_sd_configs:
      - files:
          - '/etc/prometheus/targets/nodes/*.yml'
          - '/etc/prometheus/targets/databases/*.json'
        refresh_interval: 30s

目标文件格式(YAML):

# /etc/prometheus/targets/nodes/web-servers.yml
- targets:
    - 'web-01.example.com:9100'
    - 'web-02.example.com:9100'
  labels:
    env: 'production'
    role: 'web'
    region: 'beijing'
- targets:
    - 'web-03.example.com:9100'
  labels:
    env: 'staging'
    role: 'web'
    region: 'shanghai'

目标文件格式(JSON):

[
  {
    "targets": ["db-01.example.com:9100", "db-02.example.com:9100"],
    "labels": {
      "env": "production",
      "role": "database",
      "team": "dba"
    }
  }
]

2.2 file_sd 的适用场景

file_sd 本质上是一个"外部程序写入文件、Prometheus 读取文件"的解耦模式。它的优势在于:

  • 与现有系统集成简单:CMDB、资产管理脚本只需输出 JSON/YAML 文件
  • 版本控制友好:目标文件可以纳入 Git 管理
  • 无额外依赖:不需要部署 Consul 等注册中心

常见用法是配合脚本或 CI/CD 管道定期更新目标文件:

#!/bin/bash
# sync-from-cmdb.sh — 从 CMDB 同步监控目标
# 由 cron 每 5 分钟执行一次

CMDB_API="http://cmdb.internal/api/v1/hosts"
OUTPUT_DIR="/etc/prometheus/targets/nodes"

# 从 CMDB 拉取主机列表
curl -s "$CMDB_API?env=production" | \
  jq '[.[] | select(.status == "active") | {
    targets: [.hostname + ":9100"],
    labels: {
      env: .env,
      role: .role,
      region: .region,
      instance_id: .instance_id
    }
  }]' > "$OUTPUT_DIR/production.yml"

echo "[$(date)] Synced $(jq 'map(.targets) | flatten | length' $OUTPUT_DIR/production.yml) targets"

注意:file_sd 文件变更后,Prometheus 会在 refresh_interval 内感知。如果文件写入过程中被读到不完整内容,Prometheus 会忽略并保留上一次有效配置,不会因为文件写入中断而导致监控丢失。

三、kubernetes_sd:Kubernetes 服务发现

kubernetes_sd 是云原生环境下最常用的服务发现方式。Prometheus 可以直接从 Kubernetes API Server 获取需要监控的资源列表。

3.1 角色(Role)类型

kubernetes_sd 支持 7 种 role,每种 role 发现不同的 Kubernetes 资源:

Role发现对象典型用途
node集群节点监控节点资源(node-exporter)
pod所有 Pod监控应用自定义 metrics
serviceService按服务发现目标
endpointsEndpoints监控 Service 后端 Pod
ingressIngress 路由按入口路由发现
eplicesEndpointSlice同 endpoints,K8s 1.21+ 推荐
container容器按容器发现

3.2 监控节点(node role)

scrape_configs:
  - job_name: 'k8s-nodes'
    kubernetes_sd_configs:
      - role: node
    scheme: https
    tls_config:
      ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      insecure_skip_verify: true
    bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: kubernetes.default.svc:443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/${1}/proxy/metrics

这里用 relabel_configs 将 metrics_path 改为通过 API Server 代理访问节点 metrics。labelmap 动作将 K8s 节点标签(如 node-role.kubernetes.io/worker)映射为 Prometheus 标签。

3.3 监控 Pod(pod role)

scrape_configs:
  - job_name: 'k8s-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # 只采集配置了 prometheus.io/scrape 注解的 Pod
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      # 使用注解中指定的端口
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
        target_label: __address__
      # 使用注解中指定的 path
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      # 保留 namespace 标签
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      # 映射所有 Pod 标签
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)

这种模式通过 Pod 注解(annotations)控制是否被采集:

# Pod 添加注解后自动被 Prometheus 发现
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "8080"
    prometheus.io/path: "/metrics"

3.4 监控 Endpoints(endpoints role)

endpoints role 是发现 Service 后端实例的推荐方式,尤其适合监控 K8s Service 对应的应用指标:

scrape_configs:
  - job_name: 'k8s-endpoints'
    kubernetes_sd_configs:
      - role: endpoints
    relabel_configs:
      # 只采集有 prometheus.io/scrape 注解的 Service
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: (.+)
        replacement: ${1}
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_service_name]
        target_label: service

3.5 ServiceMonitor:更优雅的 K8s 监控声明

在 Kubernetes 生态中,Prometheus Operator 引入了 ServiceMonitor CRD,用声明式方式管理 scrape 配置,比手写 relabel 规则优雅得多:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  namespace: monitoring
  labels:
    release: prometheus  # 匹配 Prometheus Operator 的选择器
spec:
  selector:
    matchLabels:
      app: my-app  # 选择带有 app=my-app 标签的 Service
  namespaceSelector:
    matchNames:
      - production
      - staging
  endpoints:
    - port: metrics
      path: /metrics
      interval: 15s
      scrapeTimeout: 10s

ServiceMonitor 的优势:

  • 声明式:配置即代码,可纳入 GitOps
  • 按命名空间隔离:不同团队管理各自的 ServiceMonitor
  • 自动发现:创建 Service 资源即可被监控,无需修改 Prometheus 配置

四、consul_sd:Consul 服务发现

Consul 是 HashiCorp 出品的服务注册与发现工具,在非 Kubernetes 环境(虚拟机、裸金属)中广泛使用。

4.1 Consul SD 配置

scrape_configs:
  - job_name: 'consul-services'
    consul_sd_configs:
      - server: 'consul:8500'
        services:
          - 'web'
          - 'api'
          - 'worker'
        tags:
          - 'production'
        refresh_interval: 30s
    relabel_configs:
      # 从 Consul 元数据中提取标签
      - source_labels: [__meta_consul_service]
        target_label: service
      - source_labels: [__meta_consul_node]
        target_label: node
      - source_labels: [__meta_consul_service_id]
        target_label: service_id
      - source_labels: [__meta_consul_datacenter]
        target_label: datacenter
      # 提取服务标签
      - source_labels: [__meta_consul_tags]
        target_label: env
        regex: '.*,production,.*'
        replacement: 'production'
      # 过滤:只采集标记了 metrics 的服务
      - source_labels: [__meta_consul_service_metadata_metrics]
        action: keep
        regex: .+

4.2 Consul 元数据

Consul SD 提供丰富的 __meta_ 标签可供 relabel 使用:

元数据标签说明
__meta_consul_address服务地址
__meta_consul_dc数据中心
__meta_consul_service服务名
__meta_consul_service_id服务实例 ID
__meta_consul_service_address服务地址
__meta_consul_service_port服务端口
__meta_consul_tags服务标签(逗号分隔)
__meta_consul_service_metadata_<key>自定义元数据

4.3 服务注册到 Consul

应用启动时将自己注册到 Consul:

{
  "ID": "web-01",
  "Name": "web",
  "Tags": ["production", "metrics"],
  "Address": "192.168.1.10",
  "Port": 9100,
  "Meta": {
    "metrics": "true",
    "team": "platform"
  },
  "Check": {
    "HTTP": "http://192.168.1.10:9100/metrics",
    "Interval": "10s"
  }
}

通过 Meta 字段附加自定义元数据,Prometheus 可通过 __meta_consul_service_metadata_<key> 访问。

五、dns_sd:DNS 服务发现

dns_sd 通过 DNS 查询发现目标,适合使用 SRV 记录或 A 记录管理服务的场景。

scrape_configs:
  - job_name: 'dns-sd'
    dns_sd_configs:
      - names:
          - '_metrics._tcp.service.consul'      # SRV 记录
          - 'api.service.production.consul'
        type: A
        port: 9100
        refresh_interval: 30s
      - names:
          - '_prometheus._tcp.example.com'      # SRV 记录
        type: SRV

dns_sd 的优势在于无需额外组件,只要基础设施支持 DNS 即可。缺点是 DNS 记录信息有限,无法像 Consul 那样携带丰富的元数据。

生产建议:dns_sd 最适合作为 Consul 的后端——Consul 自动管理 DNS 记录,Prometheus 通过 dns_sd 查询,实现服务发现的解耦。

六、ec2_sd:AWS EC2 服务发现

如果你的基础设施部署在 AWS 上,ec2_sd 可以直接从 EC2 API 发现实例:

scrape_configs:
  - job_name: 'ec2-nodes'
    ec2_sd_configs:
      - region: us-east-1
        access_key: AKIAIOSFODNN7EXAMPLE
        secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        port: 9100
        refresh_interval: 60s
        filters:
          - name: tag:Monitoring
            values: [enabled]
    relabel_configs:
      - source_labels: [__meta_ec2_availability_zone]
        target_label: az
      - source_labels: [__meta_ec2_instance_id]
        target_label: instance_id
      - source_labels: [__meta_ec2_private_ip]
        target_label: private_ip
      # 提取实例标签
      - action: labelmap
        regex: __meta_ec2_tag_(.+)
      # 只采集标记 Monitoring=enabled 的实例
      - source_labels: [__meta_ec2_tag_Monitoring]
        action: keep
        regex: enabled

类似地,Azure 用 azure_sd_configs,GCP 用 gce_sd_configs,OpenStack 用 openstack_sd_configs

七、relabel_configs:标签管理的核心

relabel_configs 是 Prometheus 服务发现中最强大的机制,它在 target 被采集之前对标签进行过滤、改写、映射。理解 relabel 是掌握 Prometheus SD 的关键。

7.1 relabel 执行时机

服务发现 → 产生原始 target (含 __meta_ 标签)
    ┌─────────────────────┐
    │   relabel_configs    │  ← 采集前,控制是否采集、改写地址/路径
    └──────────┬──────────┘
    ┌─────────────────────┐
    │     采集 metrics    │
    └──────────┬──────────┘
    ┌─────────────────────┐
    │  metric_relabel_configs │  ← 采集后,控制是否存储、改写 metric 标签
    └─────────────────────┘
  • relabel_configs:在采集之前执行,可以控制是否采集目标、修改采集地址、路径、Scheme
  • metric_relabel_configs:在采集之后、存储之前执行,可以丢弃不需要的 metric 或改写 metric 标签

7.2 relabel 动作(Action)

Action作用典型场景
replace替换或新增标签值改写 __address__、添加自定义标签
keep保留匹配的 target,丢弃不匹配的只采集特定 namespace 的 Pod
drop丢弃匹配的 target排除特定环境的目标
labelmap将一批标签映射为新标签映射 K8s/Consul 的 __meta_ 标签
labelkeep保留匹配的标签清理冗余标签
labeldrop丢弃匹配的标签移除高基数标签
lowercase标签值转小写统一标签格式
uppercase标签值转大写统一标签格式
hashmod标签值取模多 Prometheus 分片采集

7.3 实战示例

只采集生产环境的 Pod:

relabel_configs:
  - source_labels: [__meta_kubernetes_namespace]
    action: keep
    regex: (production|production-.+)

根据标签分片(双副本 Prometheus 各采集一半 target):

relabel_configs:
  - source_labels: [__address__]
    modulus: 2       # 总共 2 个分片
    target_label: __tmp_hash
    action: hashmod
  - source_labels: [__tmp_hash]
    regex: 0         # 当前 Prometheus 只采集 hash=0 的 target
    action: keep

丢弃不需要的高基数 metric:

metric_relabel_configs:
  - source_labels: [__name__]
    regex: 'go_(gc|memstats)_.+'
    action: drop

重命名 metric 标签:

metric_relabel_configs:
  - source_labels: [__name__]
    regex: 'http_requests_total'
    target_label: __name__
    replacement: 'http_requests_total'
    action: replace

八、标签管理好的实践

标签是 Prometheus 时间序列的维度标识,良好的标签设计直接影响查询效率和存储开销。

8.1 标签设计原则

原则说明示例
低基数标签值数量有限env="prod" ✓,user_id="12345"
有业务意义标签用于聚合和过滤service="payment" ✓,ip="10.0.1.5" 通常无意义
统一命名团队约定标签命名规范envserviceteamseverity
控制数量每条时间序列标签不宜超过 10 个过多标签影响查询性能

8.2 标签命名规范

# 推荐的标签层次
env       → environment 标识 (production/staging/dev)
service   → 微服务名称
instance  → 实例标识(Prometheus 自动添加)
team      → 负责团队
severity  → 告警级别 (critical/warning/info)

8.3 避免高基数标签

# 危险:user_id 作为标签,每个用户一条时间序列
metric_relabel_configs:
  - source_labels: [user_id]
    target_label: user_id  # ✗ 灾难性做法

# 正确:移除高基数标签,仅保留聚合数据
metric_relabel_configs:
  - action: labeldrop
    regex: 'user_id|session_id|request_id'

存储成本提醒:Prometheus 中每条时间序列的存储开销约 1-3 KB。如果有 10 万用户,仅 user_id 标签就会产生 10 万条时间序列,严重拖慢查询和写入性能。

九、多集群监控方案

在多 Kubernetes 集群或多数据中心环境下,服务发现需要跨越集群边界。

9.1 方案一:Prometheus + Thanos Sidecar(推荐)

┌─── Cluster A (K8s) ────────────────────┐
│  Prometheus-A ─── Thanos Sidecar ──────┼──┐
│  (kubernetes_sd: 本集群)               │  │
└────────────────────────────────────────┘  │
                                              │  Thanos Store
┌─── Cluster B (K8s) ────────────────────┐  │  (全局查询)
│  Prometheus-B ─── Thanos Sidecar ──────┼──┘
│  (kubernetes_sd: 本集群)               │
└────────────────────────────────────────┘

每个集群部署独立的 Prometheus,使用 kubernetes_sd 监控本集群。Thanos Sidecar 将数据上传到对象存储,Thanos Query 提供全局查询入口。

9.2 方案二:联邦集群

# 全局 Prometheus 配置
scrape_configs:
  - job_name: 'federate'
    scrape_interval: 30s
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="node"}'
        - '{job="kubernetes"}'
        - '{__name__=~"up|prometheus_.*"}'
    static_configs:
      - targets:
          - 'prometheus-cluster-a:9090'
          - 'prometheus-cluster-b:9090'
    relabel_configs:
      - source_labels: [__address__]
        regex: 'prometheus-(.+):9090'
        target_label: cluster
        replacement: '${1}'

联邦方案的优势是简单,但会增加子 Prometheus 的查询负载,适合小规模集群。大规模场景推荐 Thanos 或 VictoriaMetrics。

9.3 方案三:远程写入

# 各集群 Prometheus 配置远程写入
remote_write:
  - url: 'https://mimir-central.example.com/api/v1/push'
    headers:
      X-Scope-OrgID: 'tenant-a'
    write_relabel_configs:
      # 只上传关键指标,减少带宽
      - source_labels: [__name__]
        regex: 'up|node_.+|container_.+|http_requests_total'
        action: keep

各集群 Prometheus 通过 remote_write 将数据集中推送到中心存储(Mimir/Thanos Receive/VictoriaMetrics),实现集中监控。

十、多方案对比

维度file_sdkubernetes_sdconsul_sddns_sdec2_sd
适用环境通用Kubernetes虚拟机/混合DNS 基础设施AWS EC2
元数据丰富度低(手写)高(K8s 标签/注解)高(Consul tags/meta)中(EC2 tags)
自动发现半自动(需脚本)全自动全自动半自动全自动
额外依赖K8s API ServerConsul ServerDNS ServerAWS API
运维复杂度低(K8s 原生)
典型场景CMDB 集成云原生监控微服务注册发现简单 DNS 服务发现AWS 基础设施监控

十一、常见问题与排查

11.1 Target 显示为 down

# 检查 target 状态
curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.health != "up")'

# 查看 target 的 lastError
curl -s http://localhost:9090/api/v1/targets | \
  jq '.data.activeTargets[] | select(.health != "up") | {scrapeUrl, lastError, labels}'

常见原因:

  • 网络不通:Prometheus 与 target 之间网络策略限制
  • 证书问题:https 采集时证书不匹配
  • 认证失败:bearer token 过期或权限不足
  • relabel 误配:relabel 将 __address__ 改成了错误的地址

11.2 Target 列表为空

# 查看 service discovery 发现的原始 target
curl -s http://localhost:9090/api/v1/targets?state=any | jq '.data.droppedTargets'

如果 droppedTargets 有数据但 activeTargets 为空,说明 relabel_configs 的 keep/drop 规则过滤掉了所有 target。检查 keep 正则是否正确。

11.3 标签缺失或错误

# 查看 target 的所有标签
curl -s http://localhost:9090/api/v1/targets | \
  jq '.data.activeTargets[0].discoveredLabels'

discoveredLabels 包含所有 __meta_ 前缀的原始标签,确认服务发现是否返回了预期的元数据。

总结

服务发现是 Prometheus 监控体系的"神经末梢",决定了监控覆盖率和自动化程度。核心要点:

  • 选择合适的 SD 方案:Kubernetes 环境用 kubernetes_sd,非 K8s 环境用 consul_sd 或 file_sd,云厂商用原生 SD(ec2/gce/azure)
  • 掌握 relabel_configs:它是标签管理的核心,决定了哪些 target 被采集、标签如何映射、数据如何分片
  • 设计合理的标签体系:低基数、有业务意义、命名统一,避免高基数标签拖垮 Prometheus
  • 多集群用 Thanos/VM:联邦方案适合小规模,大规模场景优先 Thanos 或 VictoriaMetrics 远程写入
  • 善用 ServiceMonitor:在 K8s 环境下,Prometheus Operator + ServiceMonitor 是声明式管理监控的标配

服务发现没有"推荐方案",只有"最适合你环境的方案"。理解每种 SD 机制的工作原理和适用场景,才能做出正确的选型决策。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Prometheus 官方文档 — Configuration — Prometheus 官方,参考了Prometheus 官方文档 — Configuration相关内容