ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Prometheus 自定义服务发现:基于 file_sd 适配器实现官方发行版未内置的 SD 机制

Prometheus 自定义服务发现:基于 file_sd 适配器实现官方发行版未内置的 SD 机制 Prometheus 自定义服务发现基于 file_sd 适配器实现官方发行版未内置的 SD 机制【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus本篇指南基于 Prometheus 仓库中的 custom-sd 示例 展开讲解如何用 file_sd 适配器把任意“非官方”服务发现Service DiscoverySD实现接入 Prometheus你只需实现一个Discoverer接口由适配器将其产出的目标组TargetGroup落盘为 file_sd 兼容的 JSON 文件再由prometheus.yml中的file_sd_configs消费。读完本文你可以照着仓库示例接入 Consul 等任意注册中心并理解适配器从目标同步、变更判定到原子写文件的完整实现。一、为什么需要 file_sd 适配器Prometheus 官方发行版内置了大量 SD 机制EC2、Kubernetes、Consul、DNS 等源码位于 discovery/ 目录但企业内网常存在私有注册中心或自研配置系统这些机制无法直接写进发行版。custom SD 适配器的思路是“解耦”自定义 SD 逻辑运行在一个独立进程中实现 Prometheus 的discovery.Discoverer接口适配器adapter包负责驱动该 Discoverer把目标组序列化为 JSON 写入一个文件该文件通过prometheus.yml的file_sd机制交给 Prometheus 抓取无需使用静态配置static config即可动态传递目标。整体数据流如下自定义 Discoverer如 Consul 查询逻辑 │ []*targetgroup.Groupchan ▼ discovery.Managerdiscovery/manager.go │ 变更后的目标组 ▼ Adapter生成 JSON 并原子写入 custom_sd.json │ 文件变更fsnotify 磁盘监听 ▼ file_sddiscovery/file/file.go→ Prometheus 抓取目标从源码结构看这条链路复用了 Prometheus 官方的服务发现核心组件——适配器内部直接创建discovery.Manager来驱动 Discoverer因此自定义 SD 的行为目标增删、标签变化与内置 SD 机制在语义上完全一致只是输出端从“内存中的 scrape manager”换成了“磁盘文件 file_sd 监听”。二、示例目录结构示例位于 documentation/examples/custom-sd/包含三部分路径内容adapter/adapter.gofile_sd 适配器核心实现实现自定义 SD 时无需修改此文件adapter/adapter_test.go适配器的单元测试目标组生成、文件写出adapter-usage/main.go一个可直接运行的示例为 Consul 实现的Discoverer 适配器调用入口按原 README 的说明adapter-usage目录包含一个基础 Consul 服务发现的Discoverer实现它向 Consul 查询所有已知服务跳过 Consul 自身把服务的全部元数据以标签label形式随目标一起打包进TargetGroupadapter目录则是你需要导入并把自定义Discoverer传入的适配器代码。三、file_sd适配器与 Prometheus 的对接契约适配器产出的文件必须能被file_sd解析。根据官方配置文档 docs/configuration/configuration.md 中file_sd_config一节file_sd 的定义是File-based service discovery provides a more generic way to configure static targets and serves as an interface to plug in custom service discovery mechanisms.关键行为与参数原文档内容完整继承读取一组包含零个或多个static_config的文件文件格式支持JSON 或 YAML文件变更通过磁盘监听fsnotify检测并立即生效父目录也被隐式监听以高效处理原子重命名和新增的 glob 匹配文件若父目录文件过多监听开销会增大仅“结果良好well-formed的目标组变更”会被应用作为兜底文件会按refresh_interval周期性重读每个目标在 relabel 阶段带有元标签__meta_filepath值为其来源文件路径。# prometheus.yml 片段 scrape_configs: - job_name: custom_sd_consul file_sd_configs: - files: - /var/lib/prometheus/custom_sd.json refresh_interval: 5m # 周期性重读文件的兜底间隔默认 5m文件内容格式JSON / YAML 二选一[ { targets: [ host, ... ], labels: { labelname: labelvalue, ... } } ]- targets: [ - host ] labels: [ labelname: labelvalue ... ]文件名约束来自 discovery/file/file.go 中的校验逻辑路径必须以.json、.yml、.yaml大小写不敏感结尾最后一段路径可含一个*通配符如my/path/tg_*.json正则定义为^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$files至少需要一个条目。默认刷新间隔在 DefaultSDConfig 中定义为5 * time.Minute。这也解释了为什么适配器的输出文件默认命名为custom_sd.json——后缀天然符合 file_sd 的 glob 校验。四、Discoverer 接口自定义 SD 的唯一硬性要求从 discovery/discovery.go 可以看到接口定义实现自定义 SD 必须遵守的契约// Discoverer provides information about target groups. It maintains a set // of sources from which TargetGroups can originate. Whenever a discovery provider // detects a potential change, it sends the TargetGroup through its channel. // // Discoverer does not know if an actual change happened. // It does guarantee that it sends the new TargetGroup whenever a change happens. // // Discoverers should initially send a full set of all discoverable TargetGroups. type Discoverer interface { // Run hands a channel to the discovery provider (Consul, DNS, etc.) through which // it can send updated target groups. It must return when the context is canceled. // It should not close the update channel on returning. Run(ctx context.Context, up chan- []*targetgroup.Group) }接口契约可以归纳为四点初始全量首次应发送全部可发现目标组的完整集合只发“可能变了”的组Discoverer 不判断是否真的变化只保证变化发生时发送新TargetGroup不得关闭传入的 channel返回时不应关闭up响应 contextctx取消时Run必须返回。适配器正是通过 discovery/manager.go 的StartCustomProvider把这个 Discoverer 挂进discovery.Manager。该方法的源码注释值得注意“used for sdtool. Only use this if you know what youre doing”仅供 sdtool 类工具使用非标准路径说明这是面向工具侧的扩展入口而非prometheus.yml的常规配置路径。其内部逻辑是创建Provider、调用startProvider后者在两个 goroutine 中分别执行p.d.Run(ctx, updates)和m.updater(ctx, p, updates)完成“Discoverer 产出 → Manager 聚合 → 下游订阅”的闭环。五、适配器核心实现解析adapter/adapter.go5.1 数据结构适配器内部用 customSD 结构 描述单个目标组即 file_sd JSON 的最小形态type customSD struct { Targets []string json:targets Labels map[string]string json:labels }Adapter 结构 持有驱动所需的全部依赖// Adapter runs an unknown service discovery implementation and converts its target groups // to JSON and writes to a file for file_sd. type Adapter struct { ctx context.Context disc discovery.Discoverer // 你的自定义 SD 实现 groups map[string]*customSD // 当前已知的目标组快照 manager *discovery.Manager // 驱动 Discoverer 的官方 Manager output string // 输出文件路径 name string // 该 SD 机制的名称Provider 名 logger *slog.Logger }5.2 NewAdapter 参数说明构造入口见 NewAdapterfunc NewAdapter(ctx context.Context, file, name string, d discovery.Discoverer, logger *slog.Logger, sdMetrics *discovery.SDMetrics, registerer prometheus.Registerer) *Adapter参数含义ctx生命周期 context取消时 Manager 会停止 providerfile输出文件路径即prometheus.yml中file_sd_configs.files指向的文件name自定义 SD 机制名用作 Manager 中 Provider 的名字d你的discovery.Discoverer实现实例loggerslog.LoggersdMetrics*discovery.SDMetrics机制指标 refresh 指标示例中通过discovery.RegisterSDMetrics/NewRefreshMetrics注册registererprometheus.Registerer此处示例使用独立的prometheus.NewRegistry()NewAdapter内部通过discovery.NewManager(ctx, logger, registerer, sdMetrics)创建 Manager——即适配器直接复用 Prometheus 主进程同款的服务发现管理器无需自行实现目标聚合、去重与清理逻辑。5.3 运行主流程Run() 只有三行func (a *Adapter) Run() { //nolint:errcheck go a.manager.Run() a.manager.StartCustomProvider(a.ctx, a.name, a.disc) go a.runCustomSD(a.ctx) }a.manager.Run()在独立 goroutine 中启动 Manager 主循环StartCustomProvider把你的 Discoverer 注册为 Provider 并启动对应第四节discovery/manager.go中的实现runCustomSD订阅 Manager 的同步通道。runCustomSD 持续从a.manager.SyncCh()读取目标组全集并在ctx取消或通道关闭时退出func (a *Adapter) runCustomSD(ctx context.Context) { updates : a.manager.SyncCh() for { select { case -ctx.Done(): case allTargetGroups, ok : -updates: // Handle the case that a target provider exits and closes the channel // before the context is done. if !ok { return } a.refreshTargetGroups(allTargetGroups) } } }5.4 变更判定与原子写文件refreshTargetGroups 先由 generateTargetGroups 把map[string][]*targetgroup.Groupkey 为 SD 类型名压缩为map[string]*customSD再用reflect.DeepEqual与旧快照比对只有发生变化才落盘避免无意义的文件写入触发 file_sd 反复重载。目标组到文件的转换规则每个TargetGroup的Targets展平为字符串数组并排序sort.Strings保证输出稳定也便于 DeepEqual 判定组级Labels原样复制映射 key 为fmt.Sprintf(%s:%s:%s, k, group.Source, groupFingerprint.String())其中 fingerprint 是“所有目标地址指纹 XOR 组标签指纹”——引入指纹是为了防止sd_type与group.Source都不唯一时 key 冲突。写文件采用临时文件 原子重命名见 writeOutput// Writes JSON formatted targets to output file. func (a *Adapter) writeOutput() error { arr : mapToArray(a.groups) b, _ : json.MarshalIndent(arr, , ) dir, _ : filepath.Split(a.output) tmpfile, err : os.CreateTemp(dir, sd-adapter) // ... // Close the file immediately for platforms (eg. Windows) that cannot move // a file while a process is holding a file handle. tmpfile.Close() err os.Rename(tmpfile.Name(), a.output) // ... }两个细节与 file_sd 的实现特性直接对应临时文件与目标文件同目录filepath.Split取目录保证os.Rename是同一文件系统内的原子操作——而 file_sd 恰好隐式监听父目录来“efficiently handle atomic renaming”见 discovery/file/file.go 的 fsnotify 引入与 configuration.md 的说明重命名前先tmpfile.Close()源码注释说明这是为了兼容 Windows 等平台“持有句柄时无法移动文件”的限制。六、完整示例走读Consul 版 Discovereradapter-usage/main.goadapter-usage/main.go 是一个可编译运行的完整程序package main演示了 README 中“Usage”一节要求的全部动作替换示例 SD 配置、实现Discoverer、把实例传给NewAdapter。源码中对应的Note:/NOTE:注释即原文档指代的改造点。6.1 命令行参数程序基于 kingpin 提供两个标志main.go#L41-L43标志默认值说明--output.filecustom_sd.jsonfile_sd 兼容的输出文件路径--listen.addresslocalhost:8500Consul HTTP API 监听地址6.2 自定义 SD 配置替换点一按注释 “Note: create a config struct for your custom SD type here” 定义自己的配置结构main.go#L80-L85// Note: create a config struct for your custom SD type here. type sdConfig struct { Address string TagSeparator string RefreshInterval int }注意这与官方内置 Consul SD 不同官方机制的配置走prometheus.yml的consul_sd_configs并由discovery.RegisterConfig注册而这里因为运行在独立进程中配置由程序自身的命令行/硬编码给出在main()中组装main.go#L259-L264// NOTE: create an instance of your new SD implementation here. cfg : sdConfig{ TagSeparator: ,, Address: *listenAddress, RefreshInterval: 30, // 每 30 秒轮询一次 Consul }6.3 实现 Discoverer 接口替换点二按注释 “Note: This is the struct with your implementation of the Discoverer interface (see Run function)” 定义结构main.go#L87-L95核心是必须实现的Run函数。示例的 Run 方法 逻辑按refreshInterval定时轮询GET http://address/v1/catalog/services获取全部服务名跳过consul服务本身对应 README“except Consul itself”的描述对每个服务调用GET /v1/catalog/service/name由 parseServiceNodes 解析为*targetgroup.Group每轮结束后发送ch - tgs然后等待下一 tick 或ctx.Done()。几个值得学习的实现细节服务地址选择若服务注册了ServiceAddress可能来自远端节点注册则用它拼接端口否则回退到节点Addressvar addr string if node.ServiceAddress ! { addr net.JoinHostPort(node.ServiceAddress, strconv.Itoa(node.ServicePort)) } else { addr net.JoinHostPort(node.Address, strconv.Itoa(node.ServicePort)) }元标签集示例把 Consul 的全部服务数据以__meta_consul_*标签形式带上main.go#L46-L57 定义元标签内容__meta_consul_address节点地址__meta_consul_node节点名__meta_consul_tags服务标签前后包裹分隔符使 relabel 正则无需考虑位置__meta_consul_service_address可选的服务地址__meta_consul_service_port服务端口__meta_consul_service_id服务 ID节点元数据NodeMeta还会经strutil.SanitizeLabelName清洗后逐个附加为__meta_key标签。这些__meta_*标签只存在于 relabel 阶段你可以在relabel_configs中把它们映射为正式标签或据此过滤目标。消失目标的处理discovery结构维护oldSourceList每轮记录本次出现的服务若某服务从 Consul 目录中消失则补发一个只有Source、无目标的空TargetGroupmain.go#L215-L222确保下游能及时清理旧目标。错误容忍策略注释明确说明对“单个服务查询失败”视为本轮致命break跳出本轮宁可保留部分陈旧目标也不因一次超时就提交不完整的目标列表若服务真的消失下一轮外层循环会处理main.go#L190-L194。6.4 组装并启动适配器替换点三main()的最后几步main.go#L276-L291展示了把 Discoverer 交给适配器的标准写法reg : prometheus.NewRegistry() refreshMetrics : prom_discovery.NewRefreshMetrics(reg) mechanismMetrics, err : prom_discovery.RegisterSDMetrics(reg, refreshMetrics) if err ! nil { logger.Error(failed to register service discovery metrics, err, err) os.Exit(1) } sdMetrics : prom_discovery.SDMetrics{ MechanismMetrics: mechanismMetrics, RefreshManager: refreshMetrics, } sdAdapter : adapter.NewAdapter(ctx, *outputFile, exampleSD, disc, logger, sdMetrics, reg) sdAdapter.Run() -ctx.Done()说明RegisterSDMetrics会注册scrape_sd_discovered_targets、scrape_sd_running_duration_seconds、scrape_sd_discovery_refresh_success、scrape_sd_refresh_duration_seconds等机制指标对应 discovery/metrics.go 中的定义注册到独立 registry不会污染 Prometheus 主进程第三个参数exampleSD即name在 Manager 中作为 Provider 名出现由于ctx是context.Background()-ctx.Done()永不返回进程将持续运行——这正是适配器作为常驻 sidecar 进程的形态。七、测试依据适配器的正确性由 adapter_test.go 覆盖可作为实现自定义 SD 时的验收参照TestGenerateTargetGroups 用四组表驱动用例验证目标组转换空组key 为customSD:Consul:0000000000000000即零指纹、多目标组如customSD:Azure:282a007a18fadbbb、空/非空混合、以及乱序 IP 的排序稳定性输入192.168.1.55, 192.168.1.44输出排序后的192.168.1.44, 192.168.1.55——最后一个用例专门验证“地址乱序不导致无谓的重复写文件”TestWriteOutput 验证writeOutput能真实地把 JSON 落盘。八、落地清单与注意事项改造三步即原 README 的 Usage 要求在adapter-usage/main.go中替换示例sdConfig、实现自己的discovery.Discoverer重点是Run函数、把其实例传给adapter.NewAdapter所有改造点均有Note:/NOTE:注释标记adapter/adapter.go无需改动其头部注释明确写明 “you do not need to edit this file when implementing a custom sd”。输出文件名必须匹配 file_sd 的 glob以.json/.yml/.yaml结尾否则 file_sd 配置校验 会拒绝该配置。适配器是独立进程它与 Prometheus 主进程解耦需要自行保证常驻systemd / 容器等它崩溃后文件停止更新Prometheus 会继续使用文件中的最后目标集直到超时清理这一点从源码结构看与官方各 SD 机制的单点行为一致。轮询间隔由你的 Discoverer 决定Consul 示例是 30 秒定时轮询RefreshInterval: 30若你的机制支持 watch/事件推送如 Consul 的 block queries可在Run中用事件驱动代替 ticker降低目标变更延迟file_sd 侧的refresh_interval默认 5m只是磁盘监听的兜底。变更判定基于 DeepEqualgenerateTargetGroups已对目标排序以保证输出稳定自定义Run中若每轮都发送内容相同的目标组适配器不会重复写文件可放心发送全量。运行方式示例属于主模块导入路径为github.com/prometheus/prometheus/documentation/examples/custom-sd/adapter可在仓库根目录直接构建运行例如go run ./documentation/examples/custom-sd/adapter-usage \ --output.file/var/lib/prometheus/custom_sd.json \ --listen.addresslocalhost:8500前提是目标注册中心示例为 Consul默认localhost:8500可用。九、小结custom-sd 示例展示了 Prometheus 服务发现体系的标准扩展姿势自定义侧只需要一个满足 Discoverer 契约 的Run实现官方侧由 Adapter 负责驱动 discovery.Manager、做变更判定与原子写文件Prometheus 侧则零改动地用file_sd_configs消费。相比把第三方 SD 直接编译进发行版这种“进程级适配 文件接口”的方案边界清晰、可独立升级与测试见 adapter_test.go是把私有注册中心、自研配置系统接入 Prometheus 抓取目标的最通用路径。【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表