服务发现在Wikipedia的描述是:Service discovery is the automatic detection of devices and services offered by these devices on a computer network.
换句话说,它允许应用程序动态发现服务,而不是在应用程序配置中静态定义服务。
对于Prometheus,可以使用多种方法进行服务发现,包括云提供商API(例如AWS,Azure,GCE,Openstack),基于DNS的发现(使用SRV记录)以及查询Kubernetes API中正在运行的服务。
可想而知,在目前云原生环境下,应用具备高度弹性,通过静态配置监控目标的行为是多么的低效。
Hotreload
当然Prometheus 提供了Hotreload机制,在配置文件变更的时候,可以通知Prometheus进行reload。而且在reload的过程中,服务不会停机
热更新的加载方法有两种:
- kill -hub pid
- curl -X POST http://localhost:9090/-/reload
当然新版本的Prometheus 的热加载功能默认是关闭的,你需要在Prometheus的启动参数中,添加如下参数:
--web.enable-lifecycle
但是这种方式,并不是最优雅的,你需要维护整个配置文件。
服务发现
除静态配置之外,Prometheus 已经支持了如下的服务发现方式:
# List of Azure service discovery configurations.
azure_sd_configs:
[ - ... ]
# List of Consul service discovery configurations.
consul_sd_configs:
[ - ... ]
# List of DNS service discovery configurations.
dns_sd_configs:
[ - ... ]
# List of EC2 service discovery configurations.
ec2_sd_configs:
[ - ... ]
# List of OpenStack service discovery configurations.
openstack_sd_configs:
[ - ... ]
# List of file service discovery configurations.
file_sd_configs:
[ - ... ]
# List of GCE service discovery configurations.
gce_sd_configs:
[ - ... ]
# List of Kubernetes service discovery configurations.
kubernetes_sd_configs:
[ - ... ]
# List of Marathon service discovery configurations.
marathon_sd_configs:
[ - ... ]
# List of AirBnB's Nerve service discovery configurations.
nerve_sd_configs:
[ - ... ]
# List of Zookeeper Serverset service discovery configurations.
serverset_sd_configs:
[ - ... ]
# List of Triton service discovery configurations.
triton_sd_configs:
[ - ... ]
下图是一个Prometheus + consul sd 的架构。对于线上环境我们可能会划分为:dev, stage, prod不同的集群。每一个集群运行多个主机节点,每个服务器节点上运行一个Node Exporter实例。Node Exporter实例会自动注册到Consul中,而Prometheus则根据Consul返回的Node Exporter实例信息动态的维护Target列表,从而向这些Target轮询监控数据。
当然目前官方对于增加新的服务发现方式比较慎重,与Alertmanager 通知类型情况类似,官方不希望新的不稳定服务发现方式会影响Prometheus自身的稳定性。
能够与其他SD机制(例如Docker Swarm)集成是源源不断的需求。为了解决这个问题,最近,Prometheus存储库中的文档目录进行了一些小的代码更改,并提供了一个示例,以实现自定义服务发现集成,而无需将其合并到Prometheus主分支中。
例如,我在实际落地Prometheus的工程中,增加了下面两种服务发现方式:
- 基于负载均衡器服务自动发现:根据负载均衡器的地址,获取后端服务器组。在Cloud上,后端服务器组大多要配置HPA,决定了需要动态的发现。
- 基于自动伸缩组的服务自动发现:有些任务型工作负载,根据工作负载来扩缩worker节点数,比如根据消息队列的消息堆积数,来扩缩消费者的数量。此时根据自动伸缩组,获取实际运行的Worker节点。
如何实现自定义服务发现?
官方推荐的方式:按照官方的接口约定,实现新的服务发现方式,将发现的目标和标签写到文件中,然后结合prometheus本身支持的file_sd方式。Prometheus会定期到指定文件中获取最新的目标。
如下所示:
scrape_configs:
- job_name: "custom-sd"
scrape_interval: "15s"
file_sd_configs:
- files:
- /path/to/custom_sd.json
Adapter
首先了解一下adapter.go文件,您可以复制此文件以实现自定义SD实现。
// 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
groups map[string]*customSD
manager *discovery.Manager
output string
name string
logger log.Logger
}
// Run starts a Discovery Manager and the custom service discovery implementation.
func (a *Adapter) Run() {
go a.manager.Run()
a.manager.StartCustomProvider(a.ctx, a.name, a.disc)
go a.runCustomSD(a.ctx)
}
Adapter利用Discovery.Manager
在goroutine中启动自定义SD提供程序的Run函数。Manager有一个通道,自定义SD将向其发送更新。这些更新包含SD目标。groups字段包含所有目标和标签。
type customSD struct {
Targets []string `json:"targets"`
Labels map[string]string `json:"labels"`
}
存在这个customSD
结构主要是为了帮助我们将内部Prometheus targetgroup.Group
结构转换为JSON以用于file_sd
格式。
运行时,Adapter将在通道上监听来自我们的自定义SD实现的更新,接收到更新后,它将解析targetgroup.Groups
到另一个映射[string]* customSD
中,并将其存储在Adapter,如果两者不同,我们将新组分配给Adapter结构,并将它们作为JSON写入输出文件中。
自定义SD实现
现在我们要实际使用Adapter来实现我们自己的自定义SD。完整的工作示例位于此处的examples目录中。
在这里,您可以看到我们导入Adapter代码“ github.com/prometheus/prometheus/documentation/examples/custom-sd/adapter”以及其他一些Prometheus库。为了编写自定义SD,我们需要一个实现Discoverer接口:
// 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.
// Must returns if the context gets canceled. It should not close the update
// channel on returning.
Run(ctx context.Context, up chan<- []*targetgroup.Group)
}
我们实际上只需要实现一个函数 Run(ctx context.Context,up chan <-[] * targetgroup.Group)
。这是Adapter代码中的管理器将在goroutine中调用的函数。Run函数包含了知道何时退出的上下文,并传递了用于发送目标组更新的通道。
查看提供的示例中的Run函数,我们可以看到在另一个SD的实现中需要做的一些关键事情。
总结
如果实际场景中,公司已经有统一的服务注册中心或是配置中心,那么完全可以自定义SD,这样的好处是,利用了现有的基础设施,实现无缝对接。
另外一点是,Prometheus 中kubernetes SD的方式,对于容器化部署的业务,更加简单。