# relabeling · Prometheus 出了名的强大与烧脑

> relabeling 是「发现的目标」和「实际抓的目标」之间那台可编程的机器。这一章读 model/relabel 的 11 种 action、讲清那个统一的四步流水线心智模型，以及两个 relabel 阶段的关键区别——它同时也是控制基数最有力的工具。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-10-01
- 原文：https://daiw.org/manual/observability/relabeling
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# relabeling · Prometheus 出了名的强大与烧脑

[服务发现](https://daiw.org/manual/observability/service-discovery)吐出一堆带着 `__meta_*` 标签的目标，但“**发现的**”和“**该抓的**”往往不是一回事：你可能只想抓带某个注解的 pod、想把 K8s 的 namespace 变成指标标签、想丢掉某些没用的指标。干这活的是 **relabeling**——Prometheus 出了名的强大、也出了名的让人头疼的机制。

## 一台可编程的标签机器

<Callout type="info">
  **relabeling 的本质：**一条规则流水线，对每个目标（或每条指标）的标签集做变换，并可以在中途把它整个丢弃**。** 它是[服务发现](https://daiw.org/manual/observability/service-discovery)和[抓取](https://daiw.org/manual/observability/scrape-loop)之间那台**可编程的机器**——正因为它可编程，Prometheus 才能适配千奇百怪的环境，而核心一行不用改。

  代价是**烧脑**：它是声明式的、正则驱动的、多条规则顺序执行、且[有两个不同的阶段](https://daiw.org/manual/observability/relabeling)。几乎每个用 Prometheus 的人都在这儿栽过跟头。**但只要抓住一个统一的四步心智模型，它就不难了。**
</Callout>

## 统一的四步心智模型

看 `Config` 结构（真实源码 `model/relabel/relabel.go:86`），字段本身就说明了流程：

```go
type Config struct {
    // 从哪些标签取值,按 Separator 拼接
    SourceLabels model.LabelNames `yaml:"source_labels,flow,omitempty"`
    // 拼接时用的分隔符(默认 ";")
    Separator string `yaml:"separator,omitempty"`
    // 拿拼接结果去匹配这个正则
    Regex Regexp `yaml:"regex,omitempty"`
    // hashmod 用的模数
    Modulus uint64 `yaml:"modulus,omitempty"`
    // replace 时,结果写到哪个标签
    TargetLabel string `yaml:"target_label,omitempty"`
    // 替换模板(可用 $1 $2 引用正则捕获组)
    Replacement string `yaml:"replacement,omitempty"`
    // ...Action
}
```

**每一条 relabel 规则都是同样的四步**：

```mermaid
flowchart LR
    A["① 取值<br/>source_labels 按 separator 拼接"] --> B["② 匹配<br/>拿拼接结果去匹配 regex"]
    B --> C["③ 决策<br/>按 action 决定干什么"]
    C --> D["④ 写回/丢弃<br/>写 target_label,或丢掉整个目标"]
```

<Callout type="info">
  **记住这四步，所有 relabel 规则都是它的实例化。** 举个最常见的例子——“只抓带 `prometheus.io/scrape: true` 注解的 pod”：

  ```yaml
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]  # ① 取这个标签的值
    regex: "true"                                                            # ② 匹配 "true"
    action: keep                                                             # ③ 匹配则保留,否则丢弃目标
  ```

  再比如“把 K8s namespace 变成指标上的 `namespace` 标签”：

  ```yaml
  - source_labels: [__meta_kubernetes_namespace]   # ① 取值
    action: replace                                # ③ 替换
    target_label: namespace                        # ④ 写到 namespace 标签
  ```
  （没写 `regex` 时默认是 `(.*)`，`replacement` 默认是 `$1`，所以就是原样拷贝。）
</Callout>

## 11 种 action

源码 `model/relabel/relabel.go:46` 列出了全部 action：

| action | 干什么 | 实战用途 |
| --- | --- | --- |
| `replace` | 正则替换后写入 `target_label`（**默认 action**） | 最常用：改写 / 拷贝标签 |
| `keep` | **不匹配就丢弃**目标 | 只抓我想抓的 |
| `drop` | **匹配就丢弃**目标 | 排除我不想抓的 |
| `labelmap` | 按正则**批量拷贝**标签名 | 把所有 `__meta_*_label_*` 一次性变成指标标签 |
| `labeldrop` | **丢掉**匹配的标签 | **控基数**：删掉没用的高基数标签 |
| `labelkeep` | 只**保留**匹配的标签 | 白名单式清理 |
| `hashmod` | 取哈希取模写入标签 | **分片**：多个 Prometheus 各抓一部分 |
| `keepequal` | 两个标签**不相等就丢** | 少见 |
| `dropequal` | 两个标签**相等就丢** | 少见 |
| `lowercase` / `uppercase` | 大小写转换 | 规范化 |

<Callout type="info">
  **`hashmod` 值得单说——它是 Prometheus **水平分片**的官方解法。** [第 1 章说过 Prometheus 刻意单机自足](https://daiw.org/manual/observability/prometheus-architecture)，那目标太多一台抓不完怎么办？**分片**：

  ```yaml
  - source_labels: [__address__]
    action: hashmod
    modulus: 4              # 分 4 片
    target_label: __tmp_shard
  - source_labels: [__tmp_shard]
    regex: "0"              # 这台只抓第 0 片(其他 3 台分别配 1/2/3)
    action: keep
  ```

  哈希保证**同一个目标永远落在同一片**，4 台 Prometheus 各抓 1/4、互不重叠也不遗漏。**注意这仍然不是“分布式 Prometheus”——4 个实例彼此完全不知道对方存在**，只是各自被配置成抓不同的子集。[全局查询交给上层的 Thanos/Mimir](https://daiw.org/manual/observability/prometheus-architecture)。**保持核心简单、复杂性推到外层**，又一次。
</Callout>

## 两个阶段：relabel_configs vs metric_relabel_configs

**这是最容易搞混、也最该搞清的一点**：

<Callout type="warn">
  **relabeling 发生在**两个完全不同的时机**，对应两个配置项——用错地方规则根本不生效。**

  | | `relabel_configs` | `metric_relabel_configs` |
  | --- | --- | --- |
  | **时机** | **抓取前**，对**目标** | **抓取后**，对**每条指标** |
  | **作用对象** | 目标的标签（含 `__meta_*`、`__address__`） | 抓回来的每条 series 的标签 |
  | **`drop` 的效果** | **不抓这个目标**（省网络+CPU+存储） | 抓了但**丢掉这条指标**（只省存储） |
  | **典型用途** | 决定抓谁、设置 `__address__`/`__metrics_path__` | 丢掉不需要的指标、[删高基数标签](https://daiw.org/manual/observability/defining-metrics) |

  **关键差异：`relabel_configs` 里 drop 掉的目标，压根不会被访问；`metric_relabel_configs` 里 drop 掉的指标，已经被抓回来解析过了。** 所以：
  - 想**不抓某个目标** → 用 `relabel_configs`（便宜）。
  - 想**丢掉某个指标** → 只能用 `metric_relabel_configs`（因为指标是抓回来才知道的）。

  另外 **`__meta_*` 和 `__` 开头的标签在 `relabel_configs` 之后就被丢弃了**——它们只是 relabel 阶段的原料，不会进入最终 series。想留下某个 `__meta_*` 的值，**必须在 `relabel_configs` 里显式把它 `replace` 到一个正常标签上**。这是新手最常见的困惑：“我明明看到 SD 有这个标签，为什么指标上没有？”——**因为你没把它拷贝出来**。
</Callout>

## 实战：relabeling 是控基数最有力的工具

<Callout type="info">
  **`metric_relabel_configs` 是你面对[基数爆炸](https://daiw.org/manual/observability/defining-metrics)时的**急救手段**——尤其当你改不了那个应用的代码时（第三方组件、云厂商 exporter）。** 两个高频救命姿势：

  ```yaml
  metric_relabel_configs:
    # ① 丢掉整个没用的高基数指标
    - source_labels: [__name__]
      regex: "some_useless_high_cardinality_metric.*"
      action: drop

    # ② 保留指标,但删掉那个高基数标签(如带 UUID 的 path)
    - regex: "user_id|request_id|session_id"
      action: labeldrop
  ```

  **第 ② 招尤其实用**：很多 exporter 会吐出带 ID 的标签，让 series 数爆炸，但指标本身有用。`labeldrop` 把那个标签删掉，剩下的 series 会**自动合并**（标签少了，原本不同的 series 变成同一条）。

  但要注意一个陷阱：**`labeldrop` 后如果多条 series 塌缩成同一条，会产生“重复样本”错误**（同一时刻同一 series 两个值）。所以删标签前要确认：**删掉它之后，剩下的标签组合仍然唯一**。如果不唯一，你需要的其实是[在源头聚合](https://daiw.org/manual/observability/defining-metrics)，而不是 relabel。

  **最好的基数控制永远在[埋点那一刻](https://daiw.org/manual/observability/defining-metrics)**（[第 2 部分](https://daiw.org/manual/observability/defining-metrics)讲），relabel 是**改不了源头时的补救**。
</Callout>

## 小结与下一章

relabeling 是[服务发现](https://daiw.org/manual/observability/service-discovery)与[抓取](https://daiw.org/manual/observability/scrape-loop)之间那台**可编程的标签机器**——它让 Prometheus 能适配千奇百怪的环境而核心不改。抓住**统一的四步模型**就不烧脑：**① `source_labels` 取值按 `separator` 拼接 → ② 匹配 `regex` → ③ 按 `action` 决策 → ④ 写 `target_label` 或丢弃**。**11 种 action**（`model/relabel/relabel.go:46`）里最常用 `replace`（默认）/`keep`/`drop`/`labelmap`/`labeldrop`，而 **`hashmod` 是官方的水平分片解法**（[多实例各抓一片，彼此仍互不知情](https://daiw.org/manual/observability/prometheus-architecture)）。**两个阶段务必分清**：`relabel_configs`（[抓取前、对目标，drop 就不访问](https://daiw.org/manual/observability/relabeling)）vs `metric_relabel_configs`（[抓取后、对每条指标，drop 只省存储](https://daiw.org/manual/observability/relabeling)）；**`__meta_*` 标签在第一阶段后即丢弃**，想留必须显式 `replace` 出来。实战上 `metric_relabel_configs` 是**基数急救手段**（[`drop` 整个指标 / `labeldrop` 删高基数标签](https://daiw.org/manual/observability/defining-metrics)），但注意标签塌缩会引发重复样本——**根治永远在[埋点那一刻](https://daiw.org/manual/observability/defining-metrics)**。

目标定了、标签改好了，真正的抓取循环怎么跑？下一章收尾第 1 部分。👉 [抓取循环](https://daiw.org/manual/observability/scrape-loop)。

## 参考资料

- [1] Prometheus 源码 `model/relabel/relabel.go`（`Config:86`、11 种 `Action:46`、`Process` 实现）。
- [2] Prometheus 官方文档：Configuration — `<relabel_config>`、`metric_relabel_configs`。
- [3] 关联[服务发现的 `__meta_*` 标签](https://daiw.org/manual/observability/service-discovery)、[基数控制](https://daiw.org/manual/observability/defining-metrics)。
