計(jì)準(zhǔn)則與從零編寫機(jī)制完整指南)
深入解析 Prometheus Service DiscoverySD 設(shè)計(jì)準(zhǔn)則與從零編寫機(jī)制完整指南【免費(fèi)下載鏈接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo本文以 Tempo 倉庫中 vendored 的 Prometheus Service Discovery 設(shè)計(jì)文檔 為骨架結(jié)合其配套源碼discovery.go、manager.go、registry.go、targetgroup/targetgroup.go系統(tǒng)講解服務(wù)發(fā)現(xiàn)SD機(jī)制的設(shè)計(jì)取舍、元數(shù)據(jù)映射規(guī)則、Discoverer/Config雙接口實(shí)現(xiàn)規(guī)范以及新 SD 合入前的完整檢查清單。讀完你既能判斷某個(gè)機(jī)制是否值得做成 SD也能照著接口契約獨(dú)立實(shí)現(xiàn)一個(gè)可被 Prometheus 自動加載的服務(wù)發(fā)現(xiàn)插件。一、背景什么是 Prometheus Service Discoveryvendor/github.com/prometheus/prometheus/discovery/目錄承載了 Prometheus 服務(wù)發(fā)現(xiàn)Service Discovery簡稱 SD的核心框架代碼從接口定義、配置注冊到管理器Manager編排而不包含各具體機(jī)制的實(shí)現(xiàn)。Prometheus 通過 SD 在抓取scrape之前自動發(fā)現(xiàn)目標(biāo)機(jī)器與服務(wù)配合 relabel 規(guī)則對元數(shù)據(jù)進(jìn)行篩選和改寫。Tempo 項(xiàng)目本身并不直接使用 SD 機(jī)制該目錄隨 Prometheus 依賴一起被 vendored 進(jìn)倉庫作為 Prometheus 生態(tài)公共框架的一部分。理解這套框架對維護(hù) Tempo 的 metrics-generator 中 Prometheus 相關(guān)配置、以及閱讀任何 Prometheus 派生組件如 Alertmanager、Grafana Agent的源碼都至關(guān)重要。從目錄結(jié)構(gòu)看本倉庫 vendored 的 discovery 包只包含框架本身vendor/github.com/prometheus/prometheus/discovery/ ├── README.md # 本文主題文檔 ├── discovery.go # Discoverer / Config / StaticConfig 接口與實(shí)現(xiàn) ├── manager.go # Manager 編排器維護(hù) provider、分發(fā)目標(biāo)組 ├── registry.go # RegisterConfig 注冊機(jī)制與 YAML 內(nèi)聯(lián)解析 ├── metrics.go / metrics_refresh.go / metrics_k8s_client.go / discoverer_metrics_noop.go └── targetgroup/ └── targetgroup.go # targetgroup.Group 數(shù)據(jù)結(jié)構(gòu)二、設(shè)計(jì)決策一個(gè)功能該不該做成 SD文檔開篇強(qiáng)調(diào)不是所有發(fā)現(xiàn)需求都適合做成原生 SD。判斷標(biāo)準(zhǔn)分為幾層。2.1 基本門檻機(jī)制必須成熟且被廣泛使用至少要有多個(gè)組織實(shí)際在用而非某個(gè)公司內(nèi)部的新發(fā)明。若是一個(gè)已有機(jī)制的變體同樣不應(yīng)當(dāng)重新發(fā)明輪子——目標(biāo)是把 Prometheus 接入基礎(chǔ)設(shè)施里已有的SD而不是創(chuàng)造更多新的發(fā)現(xiàn)方式。必須能發(fā)現(xiàn)機(jī)器和/或服務(wù)僅用于發(fā)現(xiàn)同款軟件其他實(shí)例的機(jī)制例如連上某個(gè) Kafka 或 Cassandra 節(jié)點(diǎn)以找到其余節(jié)點(diǎn)不算服務(wù)發(fā)現(xiàn)。這種場景應(yīng)交給決定哪臺機(jī)器成為 Kafka 節(jié)點(diǎn)的上游系統(tǒng)通常是機(jī)器數(shù)據(jù)庫或配置管理系統(tǒng)來處理。維護(hù)承諾解除新 SD 實(shí)現(xiàn)凍結(jié)moratorium后新增的要求是新實(shí)現(xiàn)必須有一位擁有推送權(quán)限on-team的專職維護(hù)者。2.2 file_sd應(yīng)對無限變化的通用出口對于特別定制、形態(tài)各異的場景文檔給出了明確答案用file_sd接入而不是在原生代碼里支持每一種變化。這與 Prometheus 的一貫哲學(xué)一脈相承——每個(gè)方向只提供一種通用機(jī)制例如alertmanager webhook告警投遞remote read / remote write遠(yuǎn)程存儲node exporter textfile collector文本收集器任何涉及關(guān)系型數(shù)據(jù)庫的發(fā)現(xiàn)需求都應(yīng)當(dāng)走file_sd。對于 Chef 這類配置管理工具雖然其數(shù)據(jù)庫/API 理論上可做發(fā)現(xiàn)但慣用做法是利用 Chef 的模板能力把目標(biāo)列表渲染成文件供file_sd讀取。2.3 與 SDK 實(shí)現(xiàn)強(qiáng)相關(guān)的硬性約束文檔還明確了三個(gè)容易踩坑的約束均可從源碼得到印證配置只能來自配置文件SD 實(shí)現(xiàn)不得讀取環(huán)境變量或額外文件來獲取配置EC2 的DescribeInstances通過環(huán)境變量傳參是反面典型。所有配置必須經(jīng)配置文件流入這也是后面Config接口存在的理由。速率限制是硬傷曾因速率限制過低而拒絕 Amazon ECS 服務(wù)發(fā)現(xiàn)——低到僅適合小規(guī)模部署大集群根本無法使用。多類型機(jī)制要分開如果一個(gè)系統(tǒng)提供多種不同類型的 SD如 Kubernetes 的 node/pod/service/endpoints 等應(yīng)通過配置選項(xiàng)選擇具體類型而不是做一個(gè)巨型 SD再靠 relabel 挑選。失敗即中止與 SD 通信失敗時(shí)應(yīng)中止而非返回部分?jǐn)?shù)據(jù)。寧可基于過期的目標(biāo)工作也不能基于部分/錯(cuò)誤的元數(shù)據(jù)工作。對應(yīng)源碼中Manager對 provider 錯(cuò)誤的處理與Discoverer的必須隨 context 取消而返回約定。元數(shù)據(jù)不含敏感信息服務(wù)發(fā)現(xiàn)的信息在安全層面不被視為敏感——不要在元數(shù)據(jù)里返回 secrets任何能訪問 Prometheus 服務(wù)器的人都能看到它們。三、從 SD 到 Prometheus 的元數(shù)據(jù)映射規(guī)則SD 的核心哲學(xué)盡可能提取 SD 返回的所有潛在有用信息具體用哪些交給用戶通過 relabel 決定。這些信息統(tǒng)稱為元數(shù)據(jù)metadata以每個(gè)目標(biāo)一組鍵值對標(biāo)簽的形式暴露。3.1 標(biāo)簽命名約定鍵以__meta_sdname_key為前綴例如 Consul 的__meta_consul_tags。必須提供__address__標(biāo)簽值為目標(biāo)的host:port優(yōu)先用 IP避免 DNS 查詢開銷。除此之外不得暴露其他標(biāo)簽名。3.2 數(shù)組、映射與多端口的處理數(shù)據(jù)類型處理方式示例/依據(jù)數(shù)組如 tags 列表合并為單個(gè)標(biāo)簽值用逗號連接且首尾也加逗號[a,b,c]→,a,b,c,因?yàn)?relabel 正則默認(rèn)全量錨定.*,a,.*無論a在列表何處都能匹配典范是__meta_consul_tags映射/哈希key/value全部加前綴后逐個(gè)暴露為標(biāo)簽EC2 標(biāo)簽Description→__meta_ec2_tag_Descriptionmydescription標(biāo)簽名只能含[_a-zA-Z0-9]非法字符替換為下劃線多端口目標(biāo)三種方案a) 暴露為列表b) 有名字則暴露為映射c)每個(gè)端口單獨(dú)作為目標(biāo)Kubernetes SD 采用此方案a) 和 b) 可組合機(jī)器類 SD 多網(wǎng)卡報(bào)告第一個(gè)/主網(wǎng)卡即可OpenStack、EC2、部分 Kubernetes 場景3.3 自定義應(yīng)交給 relabel而非寫死在 SD 里新 SD 的早期 PR 最常見的問題就是硬編碼了作者自己環(huán)境的假設(shè)。SD 應(yīng)當(dāng)是通用的除為了適配元數(shù)據(jù)模型所必需的轉(zhuǎn)換外不允許有業(yè)務(wù)邏輯、過濾或數(shù)據(jù)變換——任何定制化都應(yīng)當(dāng)通過 relabel 配置完成。唯一例外是性能優(yōu)化當(dāng) SD 返回全量目標(biāo)而用戶只關(guān)心一小部分時(shí)如 EC2 整 region 的實(shí)例可以使用 SD 自身暴露的過濾能力如DescribeInstances的Filter參數(shù)但必須保證僅靠 relabel 也能實(shí)現(xiàn)同樣的過濾效果。四、編寫一個(gè) SD 機(jī)制Discoverer 接口4.1 目標(biāo)組的形態(tài)targetgroup.GroupSD 發(fā)現(xiàn)的相似目標(biāo)會被分組為targetgroup.Group其結(jié)構(gòu)定義在 targetgroup.go// Group is a set of targets with a common label set(production , test, staging etc.). type Group struct { Targets []model.LabelSet // 一組由標(biāo)簽集標(biāo)識的目標(biāo)每個(gè)目標(biāo)以地址標(biāo)簽唯一標(biāo)識 Labels model.LabelSet // 該組所有目標(biāo)共享的公共標(biāo)簽 Source string // 描述該目標(biāo)組的標(biāo)識符 }該結(jié)構(gòu)還實(shí)現(xiàn)了完整的 YAML/JSON 序列化UnmarshalYAML會把 YAML 中targets: [host:port, ...]的字符串列表自動展開為帶__address__標(biāo)簽的LabelSet見 targetgroup.go。這也正是static_configs在配置文件中寫targets列表即可工作的底層原因。4.2 Discoverer 接口契約SD 機(jī)制必須實(shí)現(xiàn) discovery.go 中的Discoverer接口type Discoverer interface { Run(ctx context.Context, up chan- []*targetgroup.Group) }接口文檔明確約定了三點(diǎn)行為源碼注釋見 discovery.goRun()由 Prometheus 調(diào)用以啟動發(fā)現(xiàn)機(jī)制機(jī)制將全部目標(biāo)組送入 channel之后持續(xù)監(jiān)聽變化每次更新可發(fā)送全部目標(biāo)組也可只發(fā)送變化或新增的目標(biāo)組——Manager兩種情況都能處理Run()必須隨 context 取消而返回返回時(shí)不應(yīng)關(guān)閉更新 channel。4.3 完整示例全量推送與增量推送文檔給出了一個(gè)完整的雙目標(biāo)組示例——file1mysql 組與file2postgres 組。首次調(diào)用Run()時(shí)兩組都推送下去[]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, }, }關(guān)鍵規(guī)則一個(gè) SD 實(shí)例發(fā)送的所有目標(biāo)組其Source必須在該實(shí)例范圍內(nèi)唯一。分組粒度由實(shí)現(xiàn)決定甚至可以一組一個(gè)目標(biāo)但Source唯一性是不可妥協(xié)的——因?yàn)镸anager正是用map[tg.Source]*targetgroup.Group來定位哪個(gè)組需要更新見 manager.go 的注釋與字段定義。增量更新當(dāng)demo-postgres-2消失時(shí)只需把整個(gè)變化了的目標(biāo)組重新送下去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, }組內(nèi)目標(biāo)全部消失發(fā)送Targets為空的同Source目標(biāo)組targetgroup.Group{ Targets: nil, Source: file2, }這與Manager中targets字段以poolKey{setName, provider}Source為鍵的組織方式完全對應(yīng)空Targets表示該組清空管理器據(jù)此從 map 中移除或下發(fā)空組。五、讓 Prometheus 發(fā)現(xiàn)你的 SDConfig 接口與注冊實(shí)現(xiàn)好發(fā)現(xiàn)邏輯只是第一步還必須實(shí)現(xiàn)discovery.Config接口并在包的init函數(shù)中調(diào)用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攜帶運(yùn)行期依賴見 discovery.gotype DiscovererOptions struct { Logger *slog.Logger Metrics DiscovererMetrics // Extra HTTP client options to expose to Discoverers. 實(shí)現(xiàn)可選擇性讀取 HTTPClientOptions []config.HTTPClientOption // SetName identifies this discoverer set. SetName string }Name()的約定短小、描述性強(qiáng)、全小寫、唯一。它有兩個(gè)用途——給 Logger 打標(biāo)簽以及構(gòu)成 SD 在scrape_config/alertmanager_config中 YAML 鍵名的一部分即${NAME}_sd_configs。例如file對應(yīng)file_sd_configs、kubernetes對應(yīng)kubernetes_sd_configs。注冊機(jī)制的底層實(shí)現(xiàn)在 registry.goRegisterConfig把Name()_sd_configs作為 YAML 字段名通過reflect動態(tài)構(gòu)造結(jié)構(gòu)體字段并按字段名排序插入。默認(rèn)只注冊static_configs一種對應(yīng)StaticConfig其余全部由各實(shí)現(xiàn)包在init中自行注冊。Configs類型的UnmarshalYAML/MarshalYAML依賴這套反射機(jī)制實(shí)現(xiàn)按類型分組的 YAML 解析UnmarshalYAMLWithInlineConfigs則讓包含內(nèi)聯(lián)Configs字段的復(fù)合配置結(jié)構(gòu)也能正確反序列化。若同名 Config 重復(fù)注冊會直接panic。六、靜態(tài)配置static_configs 的參考實(shí)現(xiàn)StaticConfig是框架內(nèi)置的唯一 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: } }注意三處細(xì)節(jié)Name()返回static對應(yīng) YAML 鍵static_configsNewDiscoverer不做任何工作直接返回自身Run()一次性把整個(gè)目標(biāo)組列表送入 channel然后阻塞直到 context 取消。注釋中甚至保留了一個(gè)歷史遺留矛盾staticDiscoverer關(guān)閉了 channel而接口文檔明確禁止關(guān)閉恰好印證了接口約定的演進(jìn)過程。七、Manager消費(fèi)目標(biāo)組的編排層SD 機(jī)制負(fù)責(zé)產(chǎn)出manager.go 中的Manager負(fù)責(zé)消費(fèi)與編排以poolKey{setName, provider}Source為兩級鍵維護(hù)targets map[poolKey]map[string]*targetgroup.Group精確跟蹤每個(gè) provider 的每個(gè)目標(biāo)組通過syncCh chan map[string][]*targetgroup.Group向抓取層發(fā)送按 job 分組的目標(biāo)更新內(nèi)部有updatert默認(rèn) 5 秒批處理窗口和triggerSend信號通道將 provider 的高頻更新合并后統(tǒng)一下發(fā)提供Name、Updatert、HTTPClientOptions、FeatureRegistry等函數(shù)式選項(xiàng)func(*Manager)供構(gòu)造時(shí)定制NewManager還會把RegisteredConfigNames()返回的全部 SD provider 注冊進(jìn)特性開關(guān)feature registry支持按開關(guān)啟用。八、新增 SD 的合入檢查清單文檔最后給出了一份新手容易遺漏的驗(yàn)證清單是實(shí)際提交 PR 前的必查項(xiàng)DeepEqual 驗(yàn)證把新配置加入config/testdata/conf.good.yml及相關(guān)測試確保 discovery config 可以被深比較。目錄路徑處理若配置直接或間接包含文件路徑如帶TLSConfig或HTTPClientConfig字段必須實(shí)現(xiàn)config.DirectorySetter。對應(yīng)框架側(cè)的支持見 discovery.go 中Configs.SetDirectory對DirectorySetter的遍歷調(diào)用。統(tǒng)一導(dǎo)入注冊從prometheus/discovery/install導(dǎo)入你的 SD 包——main包導(dǎo)入 install 包來注冊全部內(nèi)置 SD 機(jī)制即觸發(fā)各包的init注冊。文檔同步在docs/configuration/configuration.md的scrape_config和alertmanager_config兩處分別列出新 SD。文中還提示參考已合入的 Eureka SD PRprometheus/prometheus#3369可以快速了解一個(gè)完整 SD 涉及的所有文件面。九、總結(jié)Prometheus 的 Service Discovery 框架是一個(gè)高度接口化 注冊化的設(shè)計(jì)targetgroup.Group定義數(shù)據(jù)的統(tǒng)一形態(tài)Discoverer契約約束增量/全量推送行為Config接口配合反射注冊把每個(gè)機(jī)制無縫接入 YAML 配置體系Manager負(fù)責(zé)按 job 聚合分發(fā)。新增機(jī)制的核心原則可以濃縮為一句話盡可能多地提取原始信息、以標(biāo)準(zhǔn)__meta_*標(biāo)簽建模、零業(yè)務(wù)邏輯把一切定制交給 relabel。對于 Tempo 這類深度集成 Prometheus 生態(tài)的組件理解本目錄的框架代碼有助于在排查抓取配置、分析 SD 相關(guān)指標(biāo)sd_*系列以及閱讀依賴代碼時(shí)快速定位問題。深入閱讀本倉庫的 discovery.go、registry.go 與 manager.go即可獲得第一手接口契約的完整細(xì)節(jié)?!久赓M(fèi)下載鏈接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考