ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

深入解析 Prometheus Service Discovery:SD 设计准则与从零编写机制完整指南

深入解析 Prometheus Service Discovery:SD 设计准则与从零编写机制完整指南 深入解析 Prometheus Service DiscoverySD 设计准则与从零编写机制完整指南【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo本文以 Tempo 仓库中 vendored 的 Prometheus Service Discovery 设计文档 为骨架结合其配套源码discovery.go、manager.go、registry.go、targetgroup/targetgroup.go系统讲解服务发现SD机制的设计取舍、元数据映射规则、Discoverer/Config双接口实现规范以及新 SD 合入前的完整检查清单。读完你既能判断某个机制是否值得做成 SD也能照着接口契约独立实现一个可被 Prometheus 自动加载的服务发现插件。一、背景什么是 Prometheus Service Discoveryvendor/github.com/prometheus/prometheus/discovery/目录承载了 Prometheus 服务发现Service Discovery简称 SD的核心框架代码从接口定义、配置注册到管理器Manager编排而不包含各具体机制的实现。Prometheus 通过 SD 在抓取scrape之前自动发现目标机器与服务配合 relabel 规则对元数据进行筛选和改写。Tempo 项目本身并不直接使用 SD 机制该目录随 Prometheus 依赖一起被 vendored 进仓库作为 Prometheus 生态公共框架的一部分。理解这套框架对维护 Tempo 的 metrics-generator 中 Prometheus 相关配置、以及阅读任何 Prometheus 派生组件如 Alertmanager、Grafana Agent的源码都至关重要。从目录结构看本仓库 vendored 的 discovery 包只包含框架本身vendor/github.com/prometheus/prometheus/discovery/ ├── README.md # 本文主题文档 ├── discovery.go # Discoverer / Config / StaticConfig 接口与实现 ├── manager.go # Manager 编排器维护 provider、分发目标组 ├── registry.go # RegisterConfig 注册机制与 YAML 内联解析 ├── metrics.go / metrics_refresh.go / metrics_k8s_client.go / discoverer_metrics_noop.go └── targetgroup/ └── targetgroup.go # targetgroup.Group 数据结构二、设计决策一个功能该不该做成 SD文档开篇强调不是所有发现需求都适合做成原生 SD。判断标准分为几层。2.1 基本门槛机制必须成熟且被广泛使用至少要有多个组织实际在用而非某个公司内部的新发明。若是一个已有机制的变体同样不应当重新发明轮子——目标是把 Prometheus 接入基础设施里已有的SD而不是创造更多新的发现方式。必须能发现机器和/或服务仅用于发现同款软件其他实例的机制例如连上某个 Kafka 或 Cassandra 节点以找到其余节点不算服务发现。这种场景应交给决定哪台机器成为 Kafka 节点的上游系统通常是机器数据库或配置管理系统来处理。维护承诺解除新 SD 实现冻结moratorium后新增的要求是新实现必须有一位拥有推送权限on-team的专职维护者。2.2 file_sd应对无限变化的通用出口对于特别定制、形态各异的场景文档给出了明确答案用file_sd接入而不是在原生代码里支持每一种变化。这与 Prometheus 的一贯哲学一脉相承——每个方向只提供一种通用机制例如alertmanager webhook告警投递remote read / remote write远程存储node exporter textfile collector文本收集器任何涉及关系型数据库的发现需求都应当走file_sd。对于 Chef 这类配置管理工具虽然其数据库/API 理论上可做发现但惯用做法是利用 Chef 的模板能力把目标列表渲染成文件供file_sd读取。2.3 与 SDK 实现强相关的硬性约束文档还明确了三个容易踩坑的约束均可从源码得到印证配置只能来自配置文件SD 实现不得读取环境变量或额外文件来获取配置EC2 的DescribeInstances通过环境变量传参是反面典型。所有配置必须经配置文件流入这也是后面Config接口存在的理由。速率限制是硬伤曾因速率限制过低而拒绝 Amazon ECS 服务发现——低到仅适合小规模部署大集群根本无法使用。多类型机制要分开如果一个系统提供多种不同类型的 SD如 Kubernetes 的 node/pod/service/endpoints 等应通过配置选项选择具体类型而不是做一个巨型 SD再靠 relabel 挑选。失败即中止与 SD 通信失败时应中止而非返回部分数据。宁可基于过期的目标工作也不能基于部分/错误的元数据工作。对应源码中Manager对 provider 错误的处理与Discoverer的必须随 context 取消而返回约定。元数据不含敏感信息服务发现的信息在安全层面不被视为敏感——不要在元数据里返回 secrets任何能访问 Prometheus 服务器的人都能看到它们。三、从 SD 到 Prometheus 的元数据映射规则SD 的核心哲学尽可能提取 SD 返回的所有潜在有用信息具体用哪些交给用户通过 relabel 决定。这些信息统称为元数据metadata以每个目标一组键值对标签的形式暴露。3.1 标签命名约定键以__meta_sdname_key为前缀例如 Consul 的__meta_consul_tags。必须提供__address__标签值为目标的host:port优先用 IP避免 DNS 查询开销。除此之外不得暴露其他标签名。3.2 数组、映射与多端口的处理数据类型处理方式示例/依据数组如 tags 列表合并为单个标签值用逗号连接且首尾也加逗号[a,b,c]→,a,b,c,因为 relabel 正则默认全量锚定.*,a,.*无论a在列表何处都能匹配典范是__meta_consul_tags映射/哈希key/value全部加前缀后逐个暴露为标签EC2 标签Description→__meta_ec2_tag_Descriptionmydescription标签名只能含[_a-zA-Z0-9]非法字符替换为下划线多端口目标三种方案a) 暴露为列表b) 有名字则暴露为映射c)每个端口单独作为目标Kubernetes SD 采用此方案a) 和 b) 可组合机器类 SD 多网卡报告第一个/主网卡即可OpenStack、EC2、部分 Kubernetes 场景3.3 自定义应交给 relabel而非写死在 SD 里新 SD 的早期 PR 最常见的问题就是硬编码了作者自己环境的假设。SD 应当是通用的除为了适配元数据模型所必需的转换外不允许有业务逻辑、过滤或数据变换——任何定制化都应当通过 relabel 配置完成。唯一例外是性能优化当 SD 返回全量目标而用户只关心一小部分时如 EC2 整 region 的实例可以使用 SD 自身暴露的过滤能力如DescribeInstances的Filter参数但必须保证仅靠 relabel 也能实现同样的过滤效果。四、编写一个 SD 机制Discoverer 接口4.1 目标组的形态targetgroup.GroupSD 发现的相似目标会被分组为targetgroup.Group其结构定义在 targetgroup.go// Group is a set of targets with a common label set(production , test, staging etc.). type Group struct { Targets []model.LabelSet // 一组由标签集标识的目标每个目标以地址标签唯一标识 Labels model.LabelSet // 该组所有目标共享的公共标签 Source string // 描述该目标组的标识符 }该结构还实现了完整的 YAML/JSON 序列化UnmarshalYAML会把 YAML 中targets: [host:port, ...]的字符串列表自动展开为带__address__标签的LabelSet见 targetgroup.go。这也正是static_configs在配置文件中写targets列表即可工作的底层原因。4.2 Discoverer 接口契约SD 机制必须实现 discovery.go 中的Discoverer接口type Discoverer interface { Run(ctx context.Context, up chan- []*targetgroup.Group) }接口文档明确约定了三点行为源码注释见 discovery.goRun()由 Prometheus 调用以启动发现机制机制将全部目标组送入 channel之后持续监听变化每次更新可发送全部目标组也可只发送变化或新增的目标组——Manager两种情况都能处理Run()必须随 context 取消而返回返回时不应关闭更新 channel。4.3 完整示例全量推送与增量推送文档给出了一个完整的双目标组示例——file1mysql 组与file2postgres 组。首次调用Run()时两组都推送下去[]targetgroup.Group{ { Targets: []model.LabelSet{ {__instance__: 10.11.150.1:7870, hostname: demo-target-1, test: simple-test}, {__instance__: 10.11.150.4:7870, hostname: demo-target-2, test: simple-test}, }, Labels: model.LabelSet{job: mysql}, Source: file1, }, { Targets: []model.LabelSet{ {__instance__: 10.11.122.11:6001, hostname: demo-postgres-1, test: simple-test}, {__instance__: 10.11.122.15:6001, hostname: demo-postgres-2, test: simple-test}, }, Labels: model.LabelSet{job: postgres}, Source: file2, }, }关键规则一个 SD 实例发送的所有目标组其Source必须在该实例范围内唯一。分组粒度由实现决定甚至可以一组一个目标但Source唯一性是不可妥协的——因为Manager正是用map[tg.Source]*targetgroup.Group来定位哪个组需要更新见 manager.go 的注释与字段定义。增量更新当demo-postgres-2消失时只需把整个变化了的目标组重新送下去targetgroup.Group{ Targets: []model.LabelSet{ {__instance__: 10.11.122.11:6001, hostname: demo-postgres-1, test: simple-test}, }, Labels: model.LabelSet{job: postgres}, Source: file2, }组内目标全部消失发送Targets为空的同Source目标组targetgroup.Group{ Targets: nil, Source: file2, }这与Manager中targets字段以poolKey{setName, provider}Source为键的组织方式完全对应空Targets表示该组清空管理器据此从 map 中移除或下发空组。五、让 Prometheus 发现你的 SDConfig 接口与注册实现好发现逻辑只是第一步还必须实现discovery.Config接口并在包的init函数中调用discovery.RegisterConfig完成注册。接口定义在 discovery.gotype Config interface { // Name returns the name of the discovery mechanism. Name() string // NewDiscoverer returns a Discoverer for the Config // with the given DiscovererOptions. NewDiscoverer(DiscovererOptions) (Discoverer, error) // NewDiscovererMetrics returns the metrics used by the service discovery. NewDiscovererMetrics(prometheus.Registerer, RefreshMetricsInstantiator) DiscovererMetrics }DiscovererOptions携带运行期依赖见 discovery.gotype DiscovererOptions struct { Logger *slog.Logger Metrics DiscovererMetrics // Extra HTTP client options to expose to Discoverers. 实现可选择性读取 HTTPClientOptions []config.HTTPClientOption // SetName identifies this discoverer set. SetName string }Name()的约定短小、描述性强、全小写、唯一。它有两个用途——给 Logger 打标签以及构成 SD 在scrape_config/alertmanager_config中 YAML 键名的一部分即${NAME}_sd_configs。例如file对应file_sd_configs、kubernetes对应kubernetes_sd_configs。注册机制的底层实现在 registry.goRegisterConfig把Name()_sd_configs作为 YAML 字段名通过reflect动态构造结构体字段并按字段名排序插入。默认只注册static_configs一种对应StaticConfig其余全部由各实现包在init中自行注册。Configs类型的UnmarshalYAML/MarshalYAML依赖这套反射机制实现按类型分组的 YAML 解析UnmarshalYAMLWithInlineConfigs则让包含内联Configs字段的复合配置结构也能正确反序列化。若同名 Config 重复注册会直接panic。六、静态配置static_configs 的参考实现StaticConfig是框架内置的唯一 SD也是理解整套体系的最小范本见 discovery.gotype StaticConfig []*targetgroup.Group func (StaticConfig) Name() string { return static } func (c StaticConfig) NewDiscoverer(DiscovererOptions) (Discoverer, error) { return staticDiscoverer(c), nil } type staticDiscoverer []*targetgroup.Group func (c staticDiscoverer) Run(ctx context.Context, up chan- []*targetgroup.Group) { defer close(up) select { case -ctx.Done(): case up - c: } }注意三处细节Name()返回static对应 YAML 键static_configsNewDiscoverer不做任何工作直接返回自身Run()一次性把整个目标组列表送入 channel然后阻塞直到 context 取消。注释中甚至保留了一个历史遗留矛盾staticDiscoverer关闭了 channel而接口文档明确禁止关闭恰好印证了接口约定的演进过程。七、Manager消费目标组的编排层SD 机制负责产出manager.go 中的Manager负责消费与编排以poolKey{setName, provider}Source为两级键维护targets map[poolKey]map[string]*targetgroup.Group精确跟踪每个 provider 的每个目标组通过syncCh chan map[string][]*targetgroup.Group向抓取层发送按 job 分组的目标更新内部有updatert默认 5 秒批处理窗口和triggerSend信号通道将 provider 的高频更新合并后统一下发提供Name、Updatert、HTTPClientOptions、FeatureRegistry等函数式选项func(*Manager)供构造时定制NewManager还会把RegisteredConfigNames()返回的全部 SD provider 注册进特性开关feature registry支持按开关启用。八、新增 SD 的合入检查清单文档最后给出了一份新手容易遗漏的验证清单是实际提交 PR 前的必查项DeepEqual 验证把新配置加入config/testdata/conf.good.yml及相关测试确保 discovery config 可以被深比较。目录路径处理若配置直接或间接包含文件路径如带TLSConfig或HTTPClientConfig字段必须实现config.DirectorySetter。对应框架侧的支持见 discovery.go 中Configs.SetDirectory对DirectorySetter的遍历调用。统一导入注册从prometheus/discovery/install导入你的 SD 包——main包导入 install 包来注册全部内置 SD 机制即触发各包的init注册。文档同步在docs/configuration/configuration.md的scrape_config和alertmanager_config两处分别列出新 SD。文中还提示参考已合入的 Eureka SD PRprometheus/prometheus#3369可以快速了解一个完整 SD 涉及的所有文件面。九、总结Prometheus 的 Service Discovery 框架是一个高度接口化 注册化的设计targetgroup.Group定义数据的统一形态Discoverer契约约束增量/全量推送行为Config接口配合反射注册把每个机制无缝接入 YAML 配置体系Manager负责按 job 聚合分发。新增机制的核心原则可以浓缩为一句话尽可能多地提取原始信息、以标准__meta_*标签建模、零业务逻辑把一切定制交给 relabel。对于 Tempo 这类深度集成 Prometheus 生态的组件理解本目录的框架代码有助于在排查抓取配置、分析 SD 相关指标sd_*系列以及阅读依赖代码时快速定位问题。深入阅读本仓库的 discovery.go、registry.go 与 manager.go即可获得第一手接口契约的完整细节。【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表