多个 Prometheus 向同一系统提供数据时,下游需要知道信息来自哪个采集器或环境。全局 external_labels 可以在与外部系统通信时补充这些上下文。但它不会重写每条本地存储序列,也不会覆盖对外序列中已经存在的同名标签。理解这两条边界,才能避免用错误的查询位置判断配置是否生效,也能避免误以为全局值一定压过目标值。

本地存储与对外身份并不相同

本地查询 up 通常看到抓取和重新标记产生的标签,外部标签 prom_region 并不一定出现。它没有出现在本地查询中,不能证明配置失败。应将适当的对外接口与本地结果比较;联邦接口提供了一种直接观察差异的方法,无需为了验证这个行为先接入远端存储系统。检查位置必须与希望验证的机制对应。

配置契约也涉及远端存储和告警等外部通信,但各条下游路径还可能有自己的重新标记与处理步骤。验证某条路径时,需要观察它实际到达的消费者。本地评估的记录规则不会因为保存了一条新序列,就自动获得所有外部标签。外部标签在相关通信边界补充,不是统一改写所有本地查询结果的机制。

序列已有的标签优先

如果目标序列已经具有 cluster="target-cluster",全局外部标签配置为 cluster="collector-cluster",不会把目标值替换掉。外部标签补充的是缺少的标签名称。这是已定义的行为,不能编造成新版本遇到冲突必定拒绝或记录错误。因此,本地指标对外导出时保留的集群值,可能不同于操作人员以为全局配置会强制设置的值。

需要区分目标身份与采集器身份时,应约定独立的采集器标签名,并检查导出器和重新标记规则是否与之冲突。联邦场景中,目标端的 honor_labels 还决定如何处理源标签与本端抓取标签的冲突。这个接收端行为,与源端如何向联邦响应补充外部标签是两件事,不能因为一侧正确便跳过另一侧检查。

稳定标签不会自动实现去重

改变全局标签值,会改变之后导出序列的身份。已经保存在下游的历史仍在旧标签下,不会被重写。修改选择器和面板时,应考虑这条历史边界。添加很多标签也不必然有益:固定标签提供上下文,不同采集器的不同取值则增加消费者需要处理的身份区别,应根据实际查询需求决定,而不是把能写的字段都写进去。

两个采集器抓取相同目标时,可能在不同副本标签下导出重复观测。直接跨副本求和,会让流量看起来翻倍。外部标签让这些副本可以被区分,却不会替下游配置高可用去重。标签也不是租户授权边界;如果存在隔离要求,应核对存储系统真实的权限和查询行为,不能仅依赖名称看起来来自不同环境。

告警重新标记发生在补充外部标签之后。因此,高可用采集器可以在发送等价告警前仅去掉副本区分字段,同时为需要它的存储链路保留该指标标签。仍须核对最终告警标签确实等价,不能把真正区分责任或环境的字段也删掉。度量副本去重与告警身份归一化属于不同路径,需要分别验收。

坏掉的样子

团队可能在本地查询中寻找外部标签,找不到便反复修改本来有效的配置。相反,也可能看到联邦输出带有预期标签,就认为远端写入和告警路由都已验证。应检查具体路径和最终标签,包括下游重新标记的影响,而不是把一个接口的结果当成全部集成已经成功的证据。目标标签与全局值相同的测试,还会掩盖真正的冲突优先级。

快速验证

保存为 external-labels.py 后执行 python3 external-labels.py。实验需要 Docker 与 Prometheus 3.5.1 镜像,未缓存时可能由 Docker 下载。单个一次性 Prometheus 抓取自己,随后比较本地查询响应与文本格式的联邦输出。所有 HTTP 请求都停留在容器回环地址,不发布宿主机端口,也没有外部网络访问。

import json
import re
import subprocess
import uuid

# Requires Docker and the public Prometheus 3.5.1 image.
# All HTTP traffic stays on loopback inside one disposable container.
script = r"""set -eu
cat > /tmp/prom.yml <<'CONFIG'
global:
  scrape_interval: 1s
  scrape_timeout: 500ms
  external_labels:
    cluster: collector-cluster
    prom_region: lab-region
    replica: one
scrape_configs:
  - job_name: self
    static_configs:
      - targets: ['127.0.0.1:9090']
        labels:
          cluster: target-cluster
CONFIG
/bin/prometheus --config.file=/tmp/prom.yml --storage.tsdb.path=/tmp/data \
  --web.listen-address=127.0.0.1:9090 > /tmp/prom.log 2>&1 &
for attempt in $(seq 1 30); do
  if wget -qO /tmp/ready.json 'http://127.0.0.1:9090/api/v1/query?query=up%20%3D%3D%201'; then
    if grep -q '"status":"success"' /tmp/ready.json && ! grep -q '"result":\[\]' /tmp/ready.json; then
      wget -qO /tmp/local.json 'http://127.0.0.1:9090/api/v1/query?query=up'
      wget -qO /tmp/federated.txt --header='Accept: text/plain; version=0.0.4' \
        'http://127.0.0.1:9090/federate?match%5B%5D=up'
      cat /tmp/local.json
      printf '\n'
      cat /tmp/federated.txt
      exit 0
    fi
  fi
  sleep 1
done
cat /tmp/prom.log >&2
exit 1
"""
name = "xbcatm-prom-live-" + uuid.uuid4().hex[:12]
try:
    result = subprocess.run(
        ["docker", "run", "--rm", "--name", name, "--network", "none", "-i",
         "--entrypoint", "/bin/sh", "prom/prometheus:v3.5.1", "-s"],
        input=script, text=True, capture_output=True, timeout=45,
    )
finally:
    subprocess.run(["docker", "rm", "-f", name],
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if result.returncode:
    raise RuntimeError(result.stderr or result.stdout)
local_json, _, exported = result.stdout.partition("\n")
payload = json.loads(local_json)
assert payload["status"] == "success"
local = next(a["metric"] for a in payload["data"]["result"]
             if a["metric"]["job"] == "self")
line = next(line for line in exported.splitlines() if line.startswith("up{"))
# The fixture's controlled label values contain no quotes or escape sequences.
outbound = dict(re.findall(r'(\w+)="([^"]*)"', line))
assert outbound["prom_region"] == "lab-region" and outbound["replica"] == "one"
assert outbound["cluster"] == local["cluster"] == "target-cluster"
assert "prom_region" not in local and "replica" not in local
print(json.dumps({"federated_labels": outbound, "local_labels": local}, indent=2))

本地标签包含 cluster="target-cluster",但没有 prom_region 和 replica。联邦输出补入 prom_region="lab-region" 与 replica="one",同时保留原有集群值。程序对这三个事实分别断言。解析器仅处理本实验中不含引号和转义序列的受控标签值,不是能够解析任意指标响应的通用组件,不能直接拿去处理未知导出内容。

退出时仅清理本次唯一名称的容器。结果证明源端联邦行为及本地与对外的区别,没有验证联邦接收端、远端写入接收器、高可用去重或通知送达。这些集成检查仍须观察各自的下游接口和实际配置,才能获得对应路径的完成证据。

来源