
深入 Prometheus 服务发现SD 的设计原则、元数据模型与实现机制【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus本文基于 Prometheus 仓库 discovery/README.md 展开系统讲解 Prometheus 服务发现Service Discovery, SD的设计准则——什么样的机制值得成为内置 SD、SD 元数据如何映射为标签、实现中有哪些隐性约定并深入 Discoverer 与 Config 两个核心接口结合 discovery/registry.go、discovery/install/install.go 等源码说明一个新 SD 从编写、注册到进入scrape_config的完整链路以及新增 SD 必须核验的清单。什么样的机制才值得成为 Prometheus 的 SDdiscovery/目录承载了 Prometheus 的服务发现组件。面对社区不断提出的再加一种 SD需求官方文档首先给出的是一道筛选题这个机制做成 SD 是否合理。内置 SD 的准入门槛从 discovery/README.md 的表述看一个合格的 SD 机制需要满足足够成熟机制应当已被相当程度地确立至少被多个组织实际使用能发现机器和/或服务即它确实承担了找出运行在某处的机器/服务这一职责有承诺的维护者值得注意的是随着解除过去对新 SD 实现的禁令moratorium新 SD 被额外要求必须有一位拥有 push 权限的承诺维护者即 Prometheus 团队成员不能是新发明或已有机制的变体Prometheus 的目标是接入你基础设施里已经存在的 SD而不是发明更多做服务发现的方式也不为缺少 SD/配置管理基础设施的用户打补丁式地新增机制同类软件互相发现不算 SD例如连上一个 Kafka/Cassandra 节点来发现其他节点并不是服务发现——应该去看决定某台机器成为 Kafka 服务器的那个系统通常是机器数据库或配置管理系统。file_sd面向无限变种的单一通用机制对于特别定制或特殊的场景官方给出的答案是 discovery/file/file.go 提供的file_sd这一通用挂接点。其背后的哲学是对于有无限变种的场景Prometheus 倾向于提供一个通用机制而不是逐一原生支持——这与 Alertmanager 的 webhook、remote read/remote write、node exporter 的 textfile collector 是同一思路。典型例子任何需要与关系型数据库交互的发现逻辑都应改用file_sd。对于 Chef 这类配置管理系统虽然它们有数据库/API原则上可以直连做发现但惯用做法是用 Chef 的模板能力直接写出一个文件交给file_sd。仓库中还有一个官方佐证这一思路的样例documentation/examples/custom-sd 展示了通过file_sd适配器接入不在官方发行版内的自定义 SD 的完整方式——一个实现了Discoverer的 Consul 示例配合适配器把结果写成file_sd可读的文件无需修改 Prometheus 本体。从 SD 到 Prometheus 的映射元数据模型这是整篇文档中最具普适性的部分。SD 的一般原则是尽可能提取 SD 里所有可能有用的信息作为元数据metadata把取舍交给用户通过 relabelling 完成。标签的命名规则__meta_sdname_key与__address__元数据以键值对标签形式按目标暴露规则很严格键统一以__meta_sdname_key为前缀sdname是 SD 机制名key是具体信息项每个目标必须有一个__address__标签值为host:port优先用 IP 地址以避免 DNS 查询除上述之外不应暴露其他标签名。这一约定在内置实现中处处可见。以 Consul 为例discovery/consul/consul.go 中集中定义了全部元数据标签addressLabel model.MetaLabelPrefix consul_address nodeLabel model.MetaLabelPrefix consul_node metaDataLabel model.MetaLabelPrefix consul_metadata_ tagsLabel model.MetaLabelPrefix consul_tags serviceLabel model.MetaLabelPrefix consul_service // ... 还有 dc、namespace、partition、tagged_address_、service_id 等model.MetaLabelPrefix即__meta_。discovery/consul/consul_test.go 中require.Equal(t, test-dc, string(target.Labels[__meta_consul_dc]))这类断言也印证了该命名在测试层面的固化。数组、Map 与多端口的编码约定文档对 SD 返回数据里的复合结构给出了明确的翻译规则避免各实现自行其是数组 → 逗号包裹的单标签。如标签列表[a, b, c]应编码为,a,b,c,首尾各加一个逗号。原因是 relabelling 的正则是全锚定的首尾逗号让.*,a,.*这样的模式无论a出现在列表何处都能正确命中。文档给出的经典例子是__meta_consul_tags。Map/Hash → 加前缀的一组标签。例如 EC2 的 Description 标签会变成__meta_ec2_tag_Descriptionmydescription。由于标签名只允许[_a-zA-Z0-9]非法字符需替换为下划线做净化。多端口目标的三种策略a) 暴露为列表b) 若端口有名称则暴露为 mapc) 每个端口各自成为一个目标。Kubernetes SD 采用的就是策略 ctarget per port且 a) 与 b) 可以组合。多网卡对机器类 SDOpenStack、EC2、部分 Kubernetes一个目标可能有多块网卡。目前的做法是只报告第一块/主网卡的详细信息即已够用。SD 必须保持无业务逻辑文档特别警告新 SD 的初始 PR 常常包含作者自己环境的硬编码假设。SD 实现应当是通用的任何定制都应交给 relabelling除把它适配进元数据模型所必需的转换外不应包含业务逻辑、过滤或对 SD 返回数据的变换。其他实现考量性能、配置来源、失败与安全全量倾倒 可选过滤SD 的设计意图是倾倒所有可能的目标。例如 EC2 SD 的设想用法是取回整个区域的 EC2 实例在一个scrape_config里完成一切。大部署中如果只关心其中一小部分全量拉取可能造成性能问题——此时可以提供 SD 本身暴露的过滤能力对 EC2 就是DescribeInstances的Filter选项但文档强调两点这只是性能优化同样的过滤必须可以仅靠 relabelling 达成不要发明新的目标过滤方式那是 relabelling 的职责只是把 SD 自身已有的功能透传出来。配置一律来自配置文件Prometheus 的通则所有配置来自配置文件。即使所用 SDK/库本身支持其他配置/认证途径典型如 EC2 依赖环境变量SD 实现也不应依赖它们——SD 实现不得读取环境变量或文件来获取配置。限流、多类型 SD、失败策略与敏感信息限流有些机制的速率限制使其难以实用。文档举例Amazon ECS 的服务发现就因限流过低而被拒绝合入除了小规模部署外无从使用。需要说明仓库中当前存在 discovery/aws/ecs.go 文件从源码结构看当前版本已包含 EC2/ECS/ElastiCache/Lightsail/MSK/RDS 等 AWS 相关实现具体可用性与限流表现以实际环境为准。一个系统提供多种 SD 时应通过配置选项如 role 字段选择用哪一种而不是做成超大 SD一次返回所有类型、再靠 relabelling 挑选。文档说目前只有 Kubernetes 出现过这种情况单个带选择器的 SD 与多个独立 SD 何者更优仍是开放问题。失败即中止与 SD 通信处理过程中发生失败时应当中止而不是返回部分数据——基于陈旧目标工作好过基于不完整或错误的工作。元数据非敏感从 SD 获取的信息在安全上不视为敏感数据因此不得在元数据中返回机密任何能访问 Prometheus 服务端的人都可见。编写一个 SD 机制文档的Writing an SD mechanism部分定义了实现一个新 SD 必须面对的接口。以下以 discovery/discovery.go 当前源码为准。Discoverer 接口与目标组流SD 机制要发现目标并交给 Prometheus相似的期望被组织为目标组target groupSD 以目标组列表形式下发。核心接口type Discoverer interface { Run(ctx context.Context, up chan- []*targetgroup.Group) }discovery/discovery.go 的注释补充了两个关键契约Run必须在 context 取消时返回且返回时不应关闭 update channelDiscoverer 应在初始时发送全量可发现的目标组。目标组的结构定义在 discovery/targetgroup/targetgroup.gotype Group struct { Targets []model.LabelSet // 组内目标列表每个目标由地址标签在组内唯一标识 Labels model.LabelSet // 全组公共标签 Source string // 组标识符同一 SD 实例内必须唯一 }Run() 之后的更新语义Prometheus 调用Run()初始化发现机制后机制把全部目标组发进 channel然后监听变更。此后每次更新可以发送全量也可以只发送变化与新增的目标组——Manager两种都能处理见下文Manager 如何消费。文档用一个例子说明假设某发现机制取回两个组Source分别为file1jobmysql两个目标10.11.150.1:7870/10.11.150.4:7870与file2jobpostgres两个目标10.11.122.11:6001/10.11.122.15:6001。分组方式是实现细节甚至可以一个目标一组但每个目标组的Source在该 SD 实例的所有目标组中必须唯一。更新的两种典型情形组内部分目标消失发送该组变化后的完整状态。如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组即{Targets: nil, Source: file2}。这个空组即删除的语义是 Manager 能正确回收目标的关键。Manager 如何消费按 Source 定位组discovery/manager.go 中的Manager持有targets map[poolKey]map[string]*targetgroup.Group状态注释明确写道// Some Discoverers(e.g. k8s) send only the updates for a given target group, // so we use map[tg.Source]*targetgroup.Group to know which group to update.也就是说Manager 以tg.Source为键决定是更新已有组还是新增组这正是上面部分更新 空组删除协议能成立的原因。Manager 还带有updatert默认 5 秒的批处理节奏把多个 provider 的更新合并后经syncCh下发给 scrape manager 等订阅方。Config 接口与注册name_sd_configs的由来发现机制写好之后还要让 Prometheus发现它自己实现discovery.Config接口并在包的init函数中调用discovery.RegisterConfig注册。文档给出的接口与 discovery/discovery.go 一致实际版本还要求实现NewDiscovererMetricstype Config interface { Name() string NewDiscoverer(DiscovererOptions) (Discoverer, error) NewDiscovererMetrics(prometheus.Registerer, RefreshMetricsInstantiator) DiscovererMetrics }Name()的返回值要求短、描述性强、小写且唯一。它有两处用途给传入的Logger打标签以及构成 YAML 配置键${NAME}_sd_configs——在scrape_config与alertmanager_config中出现的都是这个键。注册机制的实现细节值得展开discovery/registry.go 中RegisterConfig把config.Name()_sd_configs作为 YAML 键、AUTO_DISCOVERY_前缀的导出字段名登记进全局表再用reflect.StructOf动态拼装出一个包含所有已注册 SD 字段的临时结构体供scrape_config/alertmanager_config的 YAML 编解码使用。因此注册这一步同时决定了你的 SD 在 YAML 里长什么样、以及能否被正确解析。内置 SD 的聚合入口是 discovery/install/install.go——该包以副作用导入的方式引入 aws、azure、consul、dns、file、kubernetes 等全部内置机制由main导入以完成注册。此外当前仓库还带 plugins/ 目录如 plugins/plugin_consul.go 用//go:build !remove_all_sd || enable_consul_sd这类构建标签把 SD 做成可按构建开关裁剪的插件为不同发行形态提供了裁剪空间。新 SD 检查清单文档列出了新增服务发现时一些不显眼但必须核验的事项可 DeepEqual把发现配置加入 config/testdata/conf.good.yml 及关联测试验证配置可被DeepEqual这是热加载判断配置是否变化的前提含文件路径必须实现config.DirectorySetter若配置直接或间接含文件路径如TLSConfig、HTTPClientConfig字段就要实现它以便相对路径能正确拼接参考 discovery/discovery.go 中Configs.SetDirectory的遍历逻辑从 install 包导入prometheus/discovery/install会被main导入以注册全部内置 SD文档登记在 docs/configuration/configuration.md 的scrape_config与alertmanager_config两节都列出该 SD。文档最后还提到新 SD 的参考示例如 Eureka 的 PR #3369可能随时间过期但足以展示一个新 SD 会触及哪些区域——对照本仓库 discovery/eureka/ 目录eureka.go、client.go、metrics.go及其测试可以完整看到实现 客户端 指标 测试的标准形态。小结与延伸阅读回到文档开头的问题——什么是一个好的 SD答案是成熟、通用、无业务逻辑、全量倾倒、元数据充分暴露、失败即中止、配置只来自文件。实现层面则收敛为两个接口Discoverer.Run的 channel 协议 Config的注册外加一份四项检查清单。想继续深入建议按以下路径在仓库中阅读discovery/discovery.goDiscoverer、Config、Configs的完整定义与 YAML 编解码discovery/manager.go 与 discovery/manager_test.goManager 如何维护 provider、按Source合并更新并下发discovery/file/file.go 与 discovery/file/fixtures/file_sd的文件格式与目标组样例discovery/refresh/refresh.go许多轮询型 SD 共用的刷新框架及其限流/退避逻辑documentation/examples/custom-sd/不改动 Prometheus 本体、通过file_sd适配器接入自定义 SD 的官方示例。【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考