Prometheus HTTP Service Discovery:端点协议、响应格式与实现原理深度解析
【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus
本文基于 Prometheus 仓库中的 HTTP SD 官方文档,系统讲解 HTTP 服务发现(HTTP SD)的定位与适用场景、SD 端点必须满足的协议要求(请求头、状态码、Content-Type 与响应体格式)、完整的<http_sd_configs>配置写法,并结合 discovery/http/http.go、discovery/refresh/refresh.go 等源码剖析刷新循环、故障缓存与可观测指标的实现细节。读完后,你能够独立实现一个符合规范的 HTTP SD 端点,并在生产环境中正确配置、排障和监控它。
HTTP SD 的定位:通用目标发现的"万能接口"
Prometheus 内置了多种服务发现机制(静态配置、Kubernetes、EC2、DNS 等),而 HTTP SD 是一种**通用型(generic)**服务发现:它允许从任意 HTTP 端点动态拉取抓取目标,本质上是为"自定义发现机制"提供了一个标准对接接口。文档明确说明,HTTP SD 与 Prometheus 已支持的其他服务发现机制互补,同时也是 File-based Service Discovery 的替代方案。
两种通用发现机制的核心差异如下(引自 docs/http_sd.md):
| 项目 | File SD | HTTP SD |
|---|---|---|
| 事件驱动(Event Based) | 是,基于 inotify | 否 |
| 更新频率 | 即时(得益于 inotify) | 按refresh_interval周期性刷新 |
| 数据格式 | YAML 或 JSON | 仅 JSON |
| 传输方式 | 本地文件 | HTTP/HTTPS |
| 安全机制 | 文件系统权限 | TLS、Basic auth、Authorization header、OAuth2 |
从源码结构看,这一差异的根源在于两者的刷新模型完全不同:File SD 监听文件变更事件实时生效;而 HTTP SD 基于refresh_interval定时轮询,底层复用通用的 discovery/refresh/refresh.go 中的Discovery结构体——Run方法先立即执行一次刷新,随后用time.NewTicker按固定间隔周期性调用RefreshF(即 HTTP SD 的Refresh方法)。
配置<http_sd_configs>:参数与校验规则
在 docs/configuration/configuration.md 中,<http_sd_config>的完整字段定义为:
# 从哪个 URL 拉取目标 url: <string> # 重新查询端点的刷新间隔 [ refresh_interval: <duration> | default = 60s ] # HTTP 客户端设置,包括认证方式(如 Basic auth、Authorization)、 # 代理配置、TLS 选项、自定义 HTTP 头等等 [ <http_config> ]其中http_config与 scrape 任务的 HTTP 客户端配置一致,支持 basic auth、bearer token、OAuth2、自定义 headers、proxy、TLS 等全部标准选项。一个典型的实际配置示例:
scrape_configs: - job_name: "dynamic-nodes" http_sd_configs: - url: "https://sd.example.com/prometheus/targets.json" refresh_interval: 30s basic_auth: username: "reader" password_file: /etc/prometheus/sd-password tls_config: ca_file: /etc/prometheus/certs/ca.crt relabel_configs: - source_labels: [__meta_prometheus_job] target_label: job metric_relabel_configs: []refresh_interval的默认值为 60 秒,定义在 discovery/http/http.go 的DefaultSDConfig中(RefreshInterval: model.Duration(60 * time.Second))。
值得注意的是,SDConfig.UnmarshalYAML(discovery/http/http.go#L80-L101)在解析配置时执行了严格的校验规则,这些约束直接影响配置是否合法:
url为必填项,缺失时报错URL is missing;- URL scheme 必须是
http或https,否则报错URL scheme must be 'http' or 'https'; - URL 必须包含 host,否则报错
host is missing in URL; - 最后还会调用
HTTPClientConfig.Validate()校验内联的 HTTP 客户端配置。
也就是说,file://、ftp://等非 http(s) 协议都会被直接拒绝,端点不可达的问题在配置加载阶段就会暴露出来。
SD 端点必须满足的协议要求
如果你要实现一个 HTTP SD 端点(例如基于 CMDB、IaC 平台或自研资产库),docs/http_sd.md 列出了一组硬性要求,它们都能在源码中得到逐条印证:
1. 请求方式与请求头
在每个刷新周期(默认 1 分钟),Prometheus 对端点发起一次GET请求,响应内容被原样消费、不做任何修改。Refresh方法(discovery/http/http.go#L153-L160)会设置三个请求头:
req.Header.Set("User-Agent", userAgent) req.Header.Set("Accept", "application/json") req.Header.Set("X-Prometheus-Refresh-Interval-Seconds", strconv.FormatFloat(d.refreshInterval.Seconds(), 'f', -1, 64))X-Prometheus-Refresh-Interval-Seconds:携带当前的刷新间隔(秒),端点可以据此调整返回数据粒度或做缓存策略;User-Agent:Prometheus 版本信息(version.PrometheusUserAgent());Accept: application/json:声明期望的响应类型。
文档同时强调:响应内容 "consumed as is, unmodified",端点必须自己保证数据质量。
2. 响应状态码与 Content-Type
- 端点必须返回HTTP 200。任何非 200 状态码都会触发失败计数,
Refresh返回server returned HTTP status <status>错误——discovery/http/http_test.go 的TestHTTPInvalidCode用 400 响应验证了这一点; Content-Type响应头必须为application/json。源码中的校验正则相当严格:
matchContentType = regexp.MustCompile( `^(?i:application\/json(;\s*charset=("utf-8"|utf-8))?)$`)TestContentTypeRegex(discovery/http/http_test.go#L173-L230)给出了完整的正反用例:application/json;charset=UTF-8、Application/JSON;Charset="utf-8"均匹配成功;而application/jsonl; charset="utf-8"、application/json;charset=UTF-9、application/json;(空 charset 值)等都会被拒绝。如果 Content-Type 不是 JSON(例如text/plain),刷新会失败并返回unsupported content type "..."错误(TestHTTPInvalidFormat验证了该场景);
- 响应体必须是UTF-8编码。
3. 空结果与全量返回
- 当没有目标要下发时,端点仍须返回 HTTP 200,且 body 为空列表
[]; - 目标列表是无序的;
- 每次刷新必须返回全量目标列表,不支持增量更新——这意味着端点不能只返回"新增/变更"的目标,否则 Prometheus 会丢失未出现的旧目标;
- Prometheus 实例不会发送自己的主机名,因此端点无法区分某次请求是否为重启后的第一次请求。
响应体格式:target group 数组
HTTP SD 的响应体是一个 target group 数组,每个 group 包含targets(地址列表)和labels(标签映射):
[ { "targets": [ "<host>", ... ], "labels": { "<labelname>": "<labelvalue>", ... } }, ... ]文档给出的完整示例,展示了按数据中心和组件分组下发目标:
[ { "targets": ["10.0.10.2:9100", "10.0.10.3:9100", "10.0.10.4:9100", "10.0.10.5:9100"], "labels": { "__meta_datacenter": "london", "__meta_prometheus_job": "node" } }, { "targets": ["10.0.40.2:9100", "10.0.40.3:9100"], "labels": { "__meta_datacenter": "london", "__meta_prometheus_job": "alertmanager" } }, { "targets": ["10.0.40.2:9093", "10.0.40.3:9093"], "labels": { "__meta_datacenter": "newyork", "__meta_prometheus_job": "alertmanager" } } ]仓库内的测试夹具 discovery/http/fixtures/http_sd.good.json 是一个最小化的合法响应:
[ { "labels": { "__meta_datacenter": "bru1" }, "targets": [ "127.0.0.1:9090" ] } ]TestHTTPValidRefresh(discovery/http/http_test.go#L35-L81)用该夹具端到端验证了合法响应会被解析为预期的 target group,并且此时失败计数保持为 0。
有两点源码行为值得端点开发者了解:
__meta_url元标签会被自动注入。Refresh在解析成功后,为每个 group 追加__meta_url标签,值即拉取目标的 SD URL(discovery/http/http.go#L202-L206,常量httpSDURLLabel = model.MetaLabelPrefix + "url")。该标签在 relabeling 阶段 可用,典型用法是通过relabel_configs把__meta_xxx类标签映射为正式标签(如target_label: job),未映射的__meta_前缀标签随后会被丢弃。- 数组中的
null项会直接导致刷新失败,报错nil target group item found。端点序列化时必须避免输出空元素。
故障缓存、超时与"消失目标"的处理
刷新失败时沿用旧列表
文档指出:Prometheus 会缓存目标列表,如果某次刷新出错,则继续沿用当前(上一次成功获取的)目标列表,且该列表不跨重启持久化。从源码看,这一行为由通用的刷新循环保证:discovery/refresh/refresh.go 的Run方法中,周期性refresh调用一旦返回错误,仅记录Unable to refresh target groups日志并continue,不向下游 channel 发送任何更新——下游发现管理器因此保持上一次的 target group 状态不变。这保证了 SD 端点的瞬时故障不会导致已发现的抓取目标集体下线。
另外,HTTP 客户端的超时被设置为与刷新间隔相同(client.Timeout = time.Duration(conf.RefreshInterval),见 discovery/http/http.go#L131),可以推断其意图是:一次刷新请求最多占用一个完整刷新周期,避免慢端点阻塞刷新循环。
目标组"消失"如何被表达
由于每次刷新都是全量返回,"目标组从端点消失"需要一个显式信号。Refresh用tgLastLength记住上一次返回的 group 数量,若本次数量变少,则为缺失的下标补充空更新(discovery/http/http.go#L209-L214):
// Generate empty updates for sources that disappeared. l := len(targetGroups) for i := l; i < d.tgLastLength; i++ { targetGroups = append(targetGroups, &targetgroup.Group{Source: urlSource(d.url, i)}) } d.tgLastLength = l每个 group 的 source ID 由urlSource生成为<url>:<i>(URL 加上下标),discovery/http/http_test.go 的TestSourceDisappeared用例专门验证了:从 3 个 group 缩到 1 个、再换成全新 2 个 group 的多轮序列中,消失的下标会收到空 group 更新,从而让下游把对应目标移除。
可观测性指标
HTTP SD 暴露三类指标用于监控发现过程本身:
| 指标 | 类型 | 说明 |
|---|---|---|
prometheus_sd_http_failures_total | counter | HTTP SD 刷新失败次数,定义于 discovery/http/metrics.go#L35-L40;Refresh中每一个错误分支(网络错误、非 200 状态、Content-Type 不匹配、JSON 解析失败、nil group)都会Inc() |
prometheus_sd_refresh_failures_total | counter | 通用刷新失败计数,定义于 discovery/metrics_refresh.go,由刷新循环在每次refreshf出错时递增 |
prometheus_sd_refresh_duration_seconds | histogram | 通用刷新耗时直方图,同样定义于 discovery/metrics_refresh.go,可用于观察 HTTP SD 的刷新性能 |
文档特别提示了一个运维细节:这些指标在底层 scrape job 于配置重载时消失后会被一并移除(即指标注册器随发现器生命周期注销,Unregister逻辑见 discovery/http/metrics.go#L56-L58)。因此如果配置热加载删除了该 job,再基于这些指标的历史序列做告警时要注意序列中断属于正常现象。
推荐的排障组合是:先查prometheus_sd_http_failures_total是否增长(失败原因看 Prometheus 日志中的Unable to refresh target groups),再看prometheus_sd_refresh_duration_seconds的 P99 是否逼近refresh_interval(端点变慢的信号)。
安全模型:URL 不保密,认证走标准机制
文档明确了 HTTP SD 的安全边界:SD 的 URL 本身不被视为机密信息,任何认证凭据和 API key 都必须通过标准的 HTTP 客户端认证机制传递,而不是拼进 URL query string。结合<http_config>的字段,可用机制包括:
- TLS(
tls_config,含客户端证书双向认证); - Basic auth(
basic_auth,支持password_file); - Bearer token / 自定义 Authorization header(
authorization); - OAuth2(
oauth2)。
这与 discovery/http/http.go 的实现一致:SD 的 HTTP 客户端由config.NewClientFromConfig(conf.HTTPClientConfig, "http", ...)从HTTPClientConfig(config.HTTPClientConfig,yaml inline 展开进 SDConfig)构建,因此继承 Prometheus 全站的完整客户端安全能力。
小结:实现与接入 HTTP SD 的检查清单
- 端点返回 HTTP 200,
Content-Type: application/json,UTF-8 编码; - 每次刷新返回全量target group 数组,无目标时返回
[],不输出null元素; - 响应体字段只有
targets与labels,利用__meta_前缀标签配合relabel_configs完成job、instance等标签映射; - 可读取
X-Prometheus-Refresh-Interval-Seconds请求头做服务端缓存; - 配置中
url必填且必须为 http/https 协议,refresh_interval默认 60s; - 认证凭据放入
basic_auth/authorization/oauth2,不要放进 URL; - 上线后关注
prometheus_sd_http_failures_total与prometheus_sd_refresh_duration_seconds。
延伸阅读路径(均相对仓库根目录):
- docs/http_sd.md:HTTP SD 官方设计文档(本文主体来源);
- docs/configuration/configuration.md:
<http_sd_config>与<http_config>字段参考; - discovery/http/http.go:SD 配置解析、URL 校验与
Refresh实现; - discovery/http/metrics.go 与 discovery/metrics_refresh.go:失败计数与刷新耗时指标;
- discovery/refresh/refresh.go:通用周期性刷新循环与故障沿用逻辑;
- discovery/http/http_test.go 与 discovery/http/fixtures/http_sd.good.json:合法/非法响应的测试用例与样例数据。
【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考