多个 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",同时保留原有集群值。程序对这三个事实分别断言。解析器仅处理本实验中不含引号和转义序列的受控标签值,不是能够解析任意指标响应的通用组件,不能直接拿去处理未知导出内容。
...