当前位置: 首页 > news >正文

Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:Discovery 动态服务发现机制

Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:Discovery 动态服务发现机制

https://github.com/prometheus/prometheus/tree/v3.4.2/discovery/ 

一、引言

    在监控系统中,对目标的有效管理至关重要。早期,Prometheus 在没有动态服务发现机制时,依赖静态配置来管理监控目标。

    用户需在配置文件中手动罗列所有待监控目标的详细信息,像 IP 地址、端口号等。然而,当监控目标发生增减、IP 或端口变更等情况时,就必须手动修改配置文件,并且重启 Prometheus 才能让新配置生效

    这种方式在小规模、稳定的环境下或许可行,但在大规模、动态变化的基础设施环境中,操作繁琐,容易出现监控遗漏或错误,几乎无法适用。 Prometheus 的动态服务发现机制,借助 Discovery 管理器,有效解决了这些问题,大大提升了监控的灵活性和可靠性。

二、动态服务发现的核心 ——Discovery 管理器

    Prometheus 的 Discovery 管理器是动态服务发现的核心组件,它能自动发现和识别监控目标。其工作架构涉及多个关键概念和组件,下面结合 Prometheus 3.4.2 源码进行详细分析。 

2.1、核心概念

2.1.1、Discovery 接口

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/discovery.go#L35

Discoverer 接口:定义了发现 TargetGroup 的基本行为。实现该接口的组件(如 Consul、DNS 等)负责发现目标组,并在检测到潜在变化时通过通道发送更新后的目标组。在 https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/discovery.go#L35 中,Discoverer 接口定义如下:

// Discoverer 接口定义了发现监控目标组的规范,它维护一组来源,从中可以获取TargetGroup信息
// 当发现提供者检测到潜在变更时,会通过通道发送TargetGroup
// 注意:Discoverer并不保证每次发送的TargetGroup都实际发生了变化
// 但确保只要发生变化,就会发送新的TargetGroup
// 并且Discoverer实现应该在启动时发送一次完整的可发现TargetGroup集合
type Discoverer interface {// Run方法用于启动发现过程,它接收一个context和一个用于发送更新的通道// 参数说明://   - ctx: 用于控制发现过程的生命周期,当ctx被取消时,Run方法应该返回//   - up: 发现者通过这个通道向消费者发送更新后的target group列表// 实现要求://   - 必须在ctx被取消时立即返回//   - 不得关闭up通道,由调用者负责关闭// 典型工作流程://   1. 初始化时发送一次全量的target groups//   2. 持续监听来源变化(如服务注册中心、文件系统等)//   3. 检测到变化时发送增量或全量更新Run(ctx context.Context, up chan<- []*targetgroup.Group)
}

关键说明:

        • 异步通信机制:通过 Go 的 channel 实现异步通信,避免阻塞
        • 上下文控制:使用 context.Context 实现优雅的取消机制
        • 单向数据流:up chan<- 表示只写通道,确保数据流向的一致性
        • 批量更新:一次可以发送多个目标组 []*targetgroup.Group

2.1.2、Config 接口

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/discovery.go#L85

Config 接口:每个服务发现机制都需要实现 Config 接口

// Config 接口定义了发现器的配置规范,负责提供配置信息和创建发现器实例
type Config interface {// Name 返回该发现机制的名称(如"consul"、"kubernetes"等)// 该名称用于配置文件识别和日志标识Name() string// NewDiscoverer 根据配置创建并返回一个Discoverer实例// 参数 opts 包含发现器运行所需的通用选项(如日志、客户端等)// 返回的Discoverer实例将负责实际的服务发现工作NewDiscoverer(opts DiscovererOptions) (Discoverer, error)// NewDiscovererMetrics 创建并返回该发现机制使用的指标收集器// 参数 registerer 用于注册指标,instantiator 用于创建刷新相关指标// 实现应返回一个DiscovererMetrics接口的实现,用于监控发现过程NewDiscovererMetrics(registerer prometheus.Registerer, instantiator RefreshMetricsInstantiator) DiscovererMetrics
}// Configs 是 Config 接口的切片类型,提供了自定义YAML编解码功能
// 它允许将多种不同类型的发现配置组合在一起,并序列化为YAML格式
// 反序列化时会根据配置类型自动映射到对应的Config实现
type Configs []Config 

关键说明: 

      • Config 接口:
        • 解耦了配置数据和发现器实现
        • 通过工厂模式(NewDiscoverer)创建具体发现器
        • 支持为每种发现机制定制监控指标
      • 关键方法作用:
        • Name(): 作为配置类型的唯一标识,用于配置文件解析,也是通过该方法标识不同的发现机制
        • NewDiscoverer(): 实现依赖注入,将通用选项注入到具体发现器
        • NewDiscovererMetrics(): 支持为不同发现机制定制监控指标
      • Configs 切片:
        • 支持混合多种发现机制配置(如同时使用 Consul 和 Kubernetes 发现)
        • 通过自定义 YAML 编解码实现灵活的配置格式
        • 典型用法:在 Prometheus 配置文件中定义多个发现源

例如,在 https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/nomad/nomad.go#L86 中,SDConfig 结构体实现了 Config 接口,通过 NewDiscoverer 方法创建了 Discovery 实例,该实例实现了 Discoverer 接口:

// SDConfig is the configuration for nomad based service discovery.
type SDConfig struct {AllowStale       bool                    `yaml:"allow_stale"`HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`Namespace        string                  `yaml:"namespace"`RefreshInterval  model.Duration          `yaml:"refresh_interval"`Region           string                  `yaml:"region"`Server           string                  `yaml:"server"`TagSeparator     string                  `yaml:"tag_separator,omitempty"`
}// NewDiscovererMetrics implements discovery.Config.
func (*SDConfig) NewDiscovererMetrics(reg prometheus.Registerer, rmi discovery.RefreshMetricsInstantiator) discovery.DiscovererMetrics {return newDiscovererMetrics(reg, rmi)
}// Name returns the name of the Config.
func (*SDConfig) Name() string { return "nomad" }// NewDiscoverer returns a Discoverer for the Config.
func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) {return NewDiscovery(c, opts.Logger, opts.Metrics)
}

例如,在 https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/consul/consul.go#L90 中,SDConfig 结构体实现了 Config 接口,通过 NewDiscoverer 方法创建了 Discovery 实例,该实例实现了 Discoverer 接口:

// SDConfig is the configuration for Consul service discovery.
type SDConfig struct {Server       string        `yaml:"server,omitempty"`PathPrefix   string        `yaml:"path_prefix,omitempty"`Token        config.Secret `yaml:"token,omitempty"`Datacenter   string        `yaml:"datacenter,omitempty"`Namespace    string        `yaml:"namespace,omitempty"`Partition    string        `yaml:"partition,omitempty"`TagSeparator string        `yaml:"tag_separator,omitempty"`Scheme       string        `yaml:"scheme,omitempty"`Username     string        `yaml:"username,omitempty"`Password     config.Secret `yaml:"password,omitempty"`// See https://www.consul.io/docs/internals/consensus.html#consistency-modes,// stale reads are a lot cheaper and are a necessity if you have >5k targets.AllowStale bool `yaml:"allow_stale"`// By default use blocking queries (https://www.consul.io/api/index.html#blocking-queries)// but allow users to throttle updates if necessary. This can be useful because of "bugs" like// https://github.com/hashicorp/consul/issues/3712 which cause an un-necessary// amount of requests on consul.RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`// See https://www.consul.io/api/catalog.html#list-services// The list of services for which targets are discovered.// Defaults to all services if empty.Services []string `yaml:"services,omitempty"`// A list of tags used to filter instances inside a service. Services must contain all tags in the list.ServiceTags []string `yaml:"tags,omitempty"`// Desired node metadata. As of Consul 1.14, consider `filter` instead.NodeMeta map[string]string `yaml:"node_meta,omitempty"`// Consul filter string// See https://www.consul.io/api-docs/catalog#filtering-1, for syntaxFilter string `yaml:"filter,omitempty"`HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
}// NewDiscovererMetrics implements discovery.Config.
func (*SDConfig) NewDiscovererMetrics(reg prometheus.Registerer, rmi discovery.RefreshMetricsInstantiator) discovery.DiscovererMetrics {return newDiscovererMetrics(reg, rmi)
}// Name returns the name of the Config.
func (*SDConfig) Name() string { return "consul" }// NewDiscoverer returns a Discoverer for the Config.
func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) {return NewDiscovery(c, opts.Logger, opts.Metrics)
}

2.1.3、目标组 (TargetGroup) 

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/targetgroup/targetgroup.go#L24 

目标组 (TargetGroup):目标组是服务发现的基本数据单元,其结构体定义如下: 

// Group 表示一组具有共同标签集合的目标(如生产环境、测试环境、预发环境等)。
type Group struct {// Targets 是由标签集合标识的目标列表。// 组内的每个目标通过其address标签唯一标识。Targets []model.LabelSet// Labels 是该组内所有目标共享的标签集合。Labels model.LabelSet// Source 是一个标识符,用于描述一组目标的来源。Source string
} 

2.1.3、供应者(Provider)

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L37

// Provider 管理一个 Discoverer 实例及其生命周期,包括配置、取消函数和订阅者。
type Provider struct {name   string         // 提供者的唯一名称d      Discoverer     // 负责服务发现的实例config interface{}    // 存储发现器的配置信息cancel context.CancelFunc // 取消函数,用于终止发现器的运行// done 在取消提供者并清理相关资源后调用done func()mu   sync.RWMutex      // 读写锁,保护并发访问subs map[string]struct{} // 当前订阅者集合(使用空结构体节省内存)// newSubs 用于临时存储配置重新加载完成后要使用的订阅者newSubs map[string]struct{}
}// Discoverer 返回提供者持有的服务发现实例
func (p *Provider) Discoverer() Discoverer {return p.d
}// IsStarted 判断服务发现实例是否已启动
// 通过检查 cancel 函数是否存在来确定
func (p *Provider) IsStarted() bool {return p.cancel != nil
}// Config 返回提供者的配置信息
func (p *Provider) Config() interface{} {return p.config
} 

2.2、架构设计模式

2.2.1、插件化架构

Prometheus 的服务发现采用了插件化架构(https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/discovery.go#L139)

// A StaticConfig is a Config that provides a static list of targets.
// StaticConfig 是一个实现了 Config 接口的结构体,用于提供静态的目标列表。
type StaticConfig []*targetgroup.Group// Name returns the name of the service discovery mechanism.
// Name 方法返回服务发现机制的名称,该名称用于在配置文件和日志标记等场景中唯一标识此发现机制。
// 这里返回 "static",表示这是静态服务发现机制。
func (StaticConfig) Name() string { return "static" }// NewDiscoverer returns a Discoverer for the Config.
// NewDiscoverer 方法根据提供的 StaticConfig 和 DiscovererOptions 创建并返回一个 Discoverer 实例。
// 参数 DiscovererOptions 包含了创建 Discoverer 所需的一些选项,如日志记录器、指标注册器等。
// 这里将 StaticConfig 转换为 staticDiscoverer 类型并返回,不返回错误。
func (c StaticConfig) NewDiscoverer(DiscovererOptions) (Discoverer, error) {return staticDiscoverer(c), nil
}// NewDiscovererMetrics returns NoopDiscovererMetrics because no metrics are
// needed for this service discovery mechanism.
// NewDiscovererMetrics 方法返回用于服务发现机制的指标对象。
// 由于静态服务发现机制不需要额外的指标(如失败计数、持续时间等),所以返回 NoopDiscovererMetrics 实例。
// NoopDiscovererMetrics 是一个空操作的指标实现,不会记录任何实际的指标数据。
// 参数 prometheus.Registerer 是用于注册指标的注册器,RefreshMetricsInstantiator 是用于实例化刷新指标的实例化器。
func (c StaticConfig) NewDiscovererMetrics(prometheus.Registerer, RefreshMetricsInstantiator) DiscovererMetrics {return &NoopDiscovererMetrics{}
} 

关键说明:

        • 统一接口:所有发现机制都实现相同的接口
        • 配置驱动:通过配置文件指定使用哪种发现机制
        • 热插拔:可以在运行时动态添加或移除发现机制

2.2.1、管理器模式

Manager 类负责协调多个 Discoverer(https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L143)

// Manager 维护一组服务发现提供者,并将每次更新发送到映射通道。
// 目标按目标集名称分组管理。
type Manager struct {logger   *slog.Logger         // 日志记录器name     string               // 管理器名称httpOpts []config.HTTPClientOption // HTTP客户端配置选项mtx      sync.RWMutex         // 保护管理器状态的读写锁ctx      context.Context      // 管理生命周期的上下文// 部分发现器(如k8s)仅发送目标组的增量更新,// 使用 map[tg.Source]*targetgroup.Group 跟踪需更新的组targets    map[poolKey]map[string]*targetgroup.GrouptargetsMtx sync.Mutex         // 保护targets的并发访问// providers 维护所有注册的服务发现提供者providers []*Provider// syncCh 作为同步通道,将更新以映射形式发送,键为抓取配置中的job值syncCh chan map[string][]*targetgroup.Group// updatert 控制发送更新到通道前的等待时间,仅在单元测试中修改updatert time.Duration// triggerSend 通道用于触发更新发送,当接收到提供者的新更新时发出信号triggerSend chan struct{}// lastProvider 记录管理器生命周期内注册的提供者总数lastProvider uint// registerer 用于注册所有服务发现指标registerer prometheus.Registerermetrics   *Metrics             // 管理器自身指标sdMetrics map[string]DiscovererMetrics // 各发现器的指标
}// Providers 返回当前配置的所有服务发现提供者
func (m *Manager) Providers() []*Provider {return m.providers
}// UnregisterMetrics 注销管理器指标。注意:
// 不注销服务发现或刷新指标,这些指标生命周期独立管理
func (m *Manager) UnregisterMetrics() {m.metrics.Unregister(m.registerer)
}// Run 启动管理器的后台处理逻辑:
// 1. 启动sender协程处理更新发送
// 2. 等待上下文结束信号
// 3. 取消所有发现器的运行
// 4. 返回上下文错误(通常是取消原因)
func (m *Manager) Run() error {go m.sender()       // 启动更新发送协程<-m.ctx.Done()      // 等待上下文结束m.cancelDiscoverers() // 取消所有发现器return m.ctx.Err()  // 返回取消原因
}
关键注释说明:
    • 结构体字段注释详细解释了各组件的用途和设计意图
    • 对复杂数据结构如 targets 进行了特别说明,解释了两级映射的使用逻辑
    • 方法注释明确了功能、实现逻辑和注意事项
    • 对并发控制机制和生命周期管理关键点进行了说明
    • 解释了指标管理的特殊处理方式(为何部分指标不注销)
    • 对 Run 方法的执行流程进行了步骤分解说明

三、总体工作流程

3.1、创建 Discovery 管理器

使用 NewManager(https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L84) 函数创建一个 Discovery 管理器实例。该函数接受上下文、日志记录器、注册器、服务发现指标等参数,并初始化管理器的各种属性。

// NewManager 是 Discovery Manager 的构造函数,用于创建和初始化服务发现管理器。
// 参数说明:
// - ctx: 控制管理器生命周期的上下文
// - logger: 日志记录器,若为nil则使用空实现
// - registerer: Prometheus指标注册器
// - sdMetrics: 服务发现指标集合
// - options: 可选配置函数,用于自定义管理器行为
func NewManager(ctx context.Context, logger *slog.Logger, registerer prometheus.Registerer, sdMetrics map[string]DiscovererMetrics, options ...func(*Manager)) *Manager {// 确保日志记录器不为空,避免空指针异常if logger == nil {logger = promslog.NewNopLogger()}// 初始化管理器基础配置mgr := &Manager{logger:      logger,                  // 设置日志记录器syncCh:      make(chan map[string][]*targetgroup.Group), // 创建同步通道targets:     make(map[poolKey]map[string]*targetgroup.Group), // 初始化目标组映射ctx:         ctx,                     // 设置上下文updatert:    5 * time.Second,         // 默认更新间隔为5秒triggerSend: make(chan struct{}, 1),  // 创建带缓冲的触发通道registerer:  registerer,              // 设置指标注册器sdMetrics:   sdMetrics,               // 设置服务发现指标}// 应用所有可选配置函数,允许自定义管理器行为for _, option := range options {option(mgr)}// 注册管理器指标(必须在所有选项设置完成后执行,确保管理器名称已设置)metrics, err := NewManagerMetrics(registerer, mgr.name)if err != nil {logger.Error("Failed to create discovery manager metrics", "manager", mgr.name, "err", err)return nil}mgr.metrics = metrics  // 保存指标收集器return mgr  // 返回初始化完成的管理器实例
} 

3.2、ApplyConfig 

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L204

使用 ApplyConfig 函数应用新的配置。该函数会检查现有的发现提供者,并根据新的配置启动或停止相应的提供者。

// ApplyConfig 检查是否已有配置的发现提供者正在运行,保留它们不变。
// 剩余提供者将被停止,并使用提供的配置启动新的所需提供者。
func (m *Manager) ApplyConfig(cfg map[string]Configs) error {m.mtx.Lock()defer m.mtx.Unlock()var failedCount int// 遍历配置中的每个名称及其对应的配置集合for name, scfg := range cfg {// 注册提供者并累加失败计数failedCount += m.registerProviders(scfg, name)}// 设置失败配置的指标值m.metrics.FailedConfigs.Set(float64(failedCount))var (wg           sync.WaitGroup    // 用于等待提供者停止的等待组newProviders []*Provider       // 存储需要保留的提供者)// 遍历当前所有提供者,处理保留、停止和更新逻辑for _, prov := range m.providers {// 取消过时的提供者(没有新订阅者的)if len(prov.newSubs) == 0 {wg.Add(1)prov.done = func() {wg.Done()}prov.cancel()  // 触发提供者的取消逻辑continue}// 保留需要的提供者newProviders = append(newProviders, prov)// 用于引用目标的映射,为新订阅者提供初始目标状态var refTargets map[string]*targetgroup.Groupprov.mu.Lock()  // 锁定提供者以修改其状态m.targetsMtx.Lock()  // 锁定目标映射以进行并发安全修改// 处理旧订阅者的目标for s := range prov.subs {refTargets = m.targets[poolKey{s, prov.name}]// 移除过时订阅者的目标if _, ok := prov.newSubs[s]; !ok {delete(m.targets, poolKey{s, prov.name})m.metrics.DiscoveredTargets.DeleteLabelValues(m.name, s)}}// 为新订阅者设置指标和目标for s := range prov.newSubs {if _, ok := prov.subs[s]; !ok {m.metrics.DiscoveredTargets.WithLabelValues(s).Set(0)}// 复制引用目标到新订阅者if l := len(refTargets); l > 0 {m.targets[poolKey{s, prov.name}] = make(map[string]*targetgroup.Group, l)for k, v := range refTargets {m.targets[poolKey{s, prov.name}][k] = v}}}m.targetsMtx.Unlock()// 更新提供者的订阅者集合prov.subs = prov.newSubsprov.newSubs = map[string]struct{}{}  // 清空临时新订阅者集合prov.mu.Unlock()// 如果提供者尚未启动,则启动它if !prov.IsStarted() {m.startProvider(m.ctx, prov)}}// 立即触发发送更新,确保下游管理器尽快获取最新状态if len(m.providers) > 0 {select {case m.triggerSend <- struct{}{}:  // 发送触发信号default:                           // 通道已满时跳过,避免阻塞}}// 更新提供者列表为保留的提供者m.providers = newProviderswg.Wait()  // 等待所有被取消的提供者完成清理return nil
}
关键注释说明:
  • 函数整体逻辑概述:配置应用、提供者管理和状态更新
  • 并发控制:使用互斥锁保护管理器状态
  • 提供者生命周期管理:注册、启动、停止和清理
  • 订阅者处理:新旧订阅者的识别、目标状态迁移
  • 指标管理:失败配置计数、发现目标计数更新
  • 状态同步优化:立即触发更新发送,确保下游管理器及时获取最新状态
  • 资源清理:等待所有被取消的提供者完成清理工作
  • 代码中涉及的特殊处理逻辑:目标状态复制、并发通道操作

在 Prometheus 的服务发现(discovery)模块中,ApplyConfig 函数是实现动态配置更新和服务发现提供者生命周期管理的核心逻辑,其作用和必要性可以从 Prometheus 的核心需求和服务发现的动态特性两方面来理解。

3.2.1、核心作用:动态协调配置与服务发现提供者的关系

ApplyConfig 的 核心功能 是将新的服务发现配置应用到运行中的系统,通过对比新旧配置,实现“保留有用的、清理过时的、启动新增的”服务发现提供者(Provider),同时维护目标(target)的状态一致性。具体可拆解为以下几个关键作用:

3.2.1.1、维护配置与运行时的一致性

Prometheus 的服务发现配置(如 Kubernetes 服务发现规则、Consul 地址、文件路径等)可能通过热更新(如 reload 信号)动态修改。ApplyConfig 需要确保:

    • 已有的 Provider 中,配置未变化的继续运行(避免重复创建销毁,减少资源消耗);
    • 新配置中新增的 Provider 被启动(如新增了一个 Consul 服务发现源);
    • 新配置中移除的 Provider 被安全停止并清理资源(如关闭连接、释放句柄)。
    • 通过这种 “增量更新” 逻辑,避免了全量重启 Provider 导致的服务中断。
3.2.1.2、管理目标(target)状态的平滑过渡

服务发现的最终目的是输出 “待监控的目标列表”(targets),这些目标会被下游的抓取模块(scraper)使用。ApplyConfig 需要在配置变更时确保目标状态的一致性:

    • 对于新添加的订阅者(subs,可理解为 “使用该服务发现结果的模块”),复制现有目标状态作为初始值,避免下游模块突然丢失目标;
    • 对于移除的订阅者,清理其关联的目标数据,避免无效目标占用内存或导致抓取错误;
    • 通过 triggerSend 立即触发目标更新,确保下游模块(如抓取器)能及时获取新配置下的目标列表。
3.2.1.3、保障系统稳定性与可观测性
    • 资源安全清理:通过 sync.WaitGroup 等待过时 Provider 完成资源释放(如关闭 HTTP 连接、Kubernetes API 客户端),避免内存泄漏或资源句柄泄露;
    • 指标跟踪:更新 FailedConfigs(配置失败计数)、DiscoveredTargets(发现的目标数)等指标,帮助用户监控服务发现的健康状态(如通过 Grafana 面板查看配置是否生效、目标是否正常发现);
    • 并发安全:通过 sync.RWMutex 和 sync.Mutex 保护配置更新、Provider 状态修改、目标数据读写等操作,避免多线程环境下的竞态条件(如配置更新时同时修改目标列表导致的数据错乱)。

3.2.2、为什么需要这一步?

Prometheus 作为动态监控系统,其服务发现模块必须满足两个核心需求动态适配环境变化无中断运行。ApplyConfig 正是为了实现这两个需求而存在:

3.2.2.1、应对动态变化的监控目标与配置 

在云原生、容器化环境中,监控目标(如 Pod、容器、服务)是动态创建 / 销毁的(例如 Kubernetes 扩缩容);同时,用户的服务发现配置也可能频繁变更(如新增一个 Consul 集群作为发现源、调整 Kubernetes 的标签选择器)。如果没有 ApplyConfig,每次配置变更都需要重启 Prometheus 才能生效,这会导致:

          • 监控中断(重启期间无法抓取指标);
          • 目标数据丢失(重启后需要重新全量发现目标,可能有延迟);
          • 生产环境不可接受的停机时间。

ApplyConfig 实现了配置的 “热更新”,让 Prometheus 无需重启即可适配新配置。

3.2.2.2、确保服务发现的连续性与一致性

服务发现是 Prometheus 监控的“数据源入口”,其输出的目标列表直接决定了抓取模块的工作内容。如果配置更新时处理不当(如突然销毁所有 Provider 再重建),会导致:

          • 下游抓取模块短暂丢失所有目标,出现 “无数据” 告警;
          • 重复创建 Provider 导致资源浪费(如重复建立 Kubernetes API 连接);
          • 旧配置的 Provider 未清理,继续产生无效的目标更新,干扰正常监控。

3.3、启动发现提供者

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L296

使用 startProvider函数启动一个发现提供者。该函数会为提供者创建一个上下文和更新通道,并启动提供者的 Run 方法和更新处理函数。

// startProvider 启动指定的服务发现提供者,并设置其生命周期管理
// 参数:
// - ctx: 继承的上下文,用于控制提供者的生命周期
// - p: 需要启动的提供者实例
func (m *Manager) startProvider(ctx context.Context, p *Provider) {// 记录调试日志,包含提供者名称和订阅者信息m.logger.Debug("Starting provider", "provider", p.name, "subs", fmt.Sprintf("%v", p.subs))// 创建可取消的上下文,用于控制提供者的生命周期ctx, cancel := context.WithCancel(ctx)// 创建更新通道,用于接收提供者发现的目标组更新updates := make(chan []*targetgroup.Group)// 设置提供者的取消函数,以便后续可以终止该提供者p.cancel = cancel// 启动提供者的发现逻辑:// - 在独立协程中运行提供者的Run方法// - Run方法负责持续发现目标并将更新发送到updates通道go p.d.Run(ctx, updates)// 启动更新处理器:// - 在独立协程中运行updater方法// - updater负责处理从updates通道接收到的目标更新go m.updater(ctx, p, updates)
}

    在 Prometheus 的服务发现(discovery)机制中,startProvider 是连接配置与实际目标发现逻辑的核心函数,其作用是将配置中定义的 "发现提供者"(如 Kubernetes、Consul、文件等不同来源的发现组件)实例化并启动,使其能够执行具体的目标探测工作。它的存在是为了确保服务发现过程能够动态、安全、有序地运行,并与 Prometheus 的整体生命周期管理协同工作。

3.3.1、核心作用

startProvider 的核心作用可以概括为:为一个 "发现提供者"(Provider)创建运行环境、启动其发现逻辑,并建立与管理器(Manager)的通信管道,使其能够持续发现目标并将结果反馈给 Prometheus 核心组件。具体可拆解为以下三点:

3.3.1.1、初始化运行环境

为发现提供者创建专用的上下文(context)和取消函数(cancel),用于精确控制提供者的生命周期(启动、运行、停止)。通过 context.WithCancel 生成的上下文,既能继承上层管理器的生命周期(如整个 Prometheus 进程的退出信号),又能独立取消(如配置更新时停止旧提供者),避免资源泄漏。

3.3.1.2、启动目标发现逻辑

启动提供者的核心发现逻辑(p.d.Run),该方法由具体的发现实现(如 kubernetes.SD、consul.SD 等)提供,负责从数据源(如 k8s API、Consul 服务端)持续探测目标(如 Pod、服务实例),并将发现的目标组(targetgroup.Group)通过 updates 通道发送出去。

3.3.2.3、建立目标更新通道与处理流程

创建 updates 通道作为发现结果的传递媒介,并启动 updater 协程处理通道中的目标更新。updater 会将新发现的目标同步到管理器的全局目标集合中,同时更新监控指标(如 DiscoveredTargets),最终让 Prometheus 知道需要抓取哪些目标的指标。  

3.3.2、为什么需要这一步?

startProvider 是服务发现机制从"配置定义" 到 "实际运行" 的关键桥梁,其必要性体现在以下几个方面:

3.3.2.1、隔离与并发控制

不同的发现提供者(如同时使用 Kubernetes 和 Consul 发现)需要独立运行,避免相互干扰。startProvider 通过为每个提供者创建独立的协程(p.d.Run 和 updater)和上下文,实现了并发隔离,确保某个提供者的故障或退出不会影响其他提供者。

3.3.2.2、生命周期管理的统一性

服务发现的目标是动态的(如容器重启、服务扩缩容),且配置可能随时更新(如新增一个 Consul 发现源)。startProvider 通过 context 和 cancel 函数,将所有提供者的启动、停止逻辑标准化,使得管理器(Manager)能通过 ApplyConfig 动态控制提供者的生命周期(启动新提供者、停止旧提供者),无需关心具体的发现实现细节。

3.3.2.3、数据流转的安全性

目标发现的结果(targetgroup.Group)需要从提供者传递到管理器的全局目标集合中,而 updates 通道的创建和 updater 协程的启动,确保了这一过程的线程安全(通过通道的序列化特性避免并发读写冲突)。同时,updater 还会处理新旧目标的衔接(如为新订阅者初始化目标状态),保证 Prometheus 抓取逻辑能获取到最新的目标列表。

3.3.2.4、适配动态目标场景

没有 startProvider,发现提供者无法被激活,Prometheus 就无法感知动态变化的目标(如 k8s 中新建的 Pod)。它是将 "静态配置" 转化为 "动态发现能力" 的关键步骤,让 Prometheus 能适应云原生环境中目标频繁变更的场景。

3.4、处理更新

https://github.com/prometheus/prometheus/blob/v3.4.2/discovery/manager.go#L321

使用 updater 函数处理发现提供者发送的更新。该函数会在上下文取消时清理资源,并将更新后的目标组存储到管理器中。

 

 

 

 
http://www.sczhlp.com/news/1027/

相关文章:

  • 在运维工作中,Docker的运行状态有哪些?
  • BZOJ 4641 题解
  • APP UI自动化元素定位高频问题
  • 通义灵码保姆级教程:从数据读取、清洗、结合大模型分析、可视化、生成报告全链路
  • 在运维工作中,docker file 用什么构建容器的?
  • 一维光栅结构严格耦合波分析(RCWA)求解器
  • rust学习笔记之基础:类型系统和类型转换
  • 在运维工作中,Docker的基本命令有哪些?
  • 云原生周刊:2025年的服务网格
  • 故障处理:troubleshooting Cluster Time Synchronization Service
  • 在运维工作中,镜像启动一个容器的命令的什么?
  • python命令行解析模块argparse
  • 学习笔记:一次RMAN还原慢的分析
  • 抖音Next-User Retrieva:生成式冷启动召回
  • 求两个自然数a和b的最大公约数(递归算法)
  • nginx压缩字体ttf的有关配置
  • 如何选择工业电脑?
  • 教你创业SUS
  • 使用 nacos-sdk-csharp 服务订阅机制动态更新Yarp配置的简易Demo
  • Three.js 的第一个工程-创建一个场景
  • nginx配置文件生产环境优化
  • 贪心随笔
  • ubuntu系统ufw开放端口教程
  • 基础算法随笔
  • 技术跃迁!DVP AirCAMERA _1020摄像头小板赋能开发者构建顶级视觉系统
  • 小工具
  • Ubuntu20.04 安装gcc11 g++11, Ubuntu18.04
  • Forward prop in tensorflow
  • aws 上传自定义证书
  • 空间智能赋能城市低空数字底座及智能网联系统建设