实现自定义服务发现
Prometheus 包含对许多服务发现 (SD) 系统的内置集成,例如 Consul、Kubernetes 和 Azure 等公共云提供商。但是,我们无法为所有服务发现选项提供集成实现。Prometheus 团队已经竭尽全力支持当前的 SD 集成,因此为每个可能的 SD 选项维护一个集成是不切实际的。在许多情况下,当前的 SD 实现是由团队以外的人员贡献的,之后没有得到很好的维护或测试。我们希望承诺只为我们知道可以维护并且按预期工作的服务发现机制提供直接集成。因此,目前暂停新的 SD 集成。
但是,我们知道仍然希望能够与其他 SD 机制(如 Docker Swarm)集成。最近,在 Prometheus 存储库内的 documentation/examples/custom-sd 目录中,提交了一个小的代码更改和一个示例,用于实现自定义服务发现集成,而无需将其合并到 Prometheus 主二进制文件中。代码更改使我们能够利用内部的 Discovery Manager 代码来编写另一个可执行文件,该文件与新的 SD 机制交互并输出与 Prometheus 的 file_sd 兼容的文件。通过将 Prometheus 和我们的新可执行文件放在一起,我们可以配置 Prometheus 来读取可执行文件的 file_sd 兼容输出,从而从该服务发现机制中抓取目标。将来,这将使我们能够将 SD 集成移出 Prometheus 主二进制文件,并将使用适配器的稳定 SD 集成移入 Prometheus discovery 包。
使用 file_sd 的集成(例如使用适配器代码实现的集成)在此处 列出。
让我们看一下示例代码。
适配器
首先,我们有文件 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)
}
适配器使用 discovery.Manager
在 goroutine 中实际启动我们自定义 SD 提供程序的 Run 函数。Manager 有一个通道,我们的自定义 SD 将向其发送更新。这些更新包含 SD 目标。groups 字段包含我们的自定义 SD 可执行文件从我们的 SD 机制了解到的所有目标和标签。
type customSD struct {
Targets []string `json:"targets"`
Labels map[string]string `json:"labels"`
}
此 customSD
结构主要用于帮助我们将内部 Prometheus targetgroup.Group
结构转换为 file_sd 格式的 JSON。
运行时,适配器将监听来自我们的自定义 SD 实现的通道更新。收到更新后,它会将 targetgroup.Groups 解析为另一个 map[string]*customSD
,并将其与适配器 groups
字段中存储的内容进行比较。如果两者不同,我们将新组分配给适配器结构,并将它们作为 JSON 写入输出文件。请注意,此实现假定 SD 实现通过通道发送的每个更新都包含 SD 了解的所有目标组的完整列表。
自定义 SD 实现
现在我们实际上要使用适配器来实现我们自己的自定义 SD。一个完整的可运行示例在同一个 examples 目录中的 此处。
在这里,您可以看到我们正在导入适配器代码 "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)
。这是适配器代码中的管理器将在 goroutine 中调用的函数。Run 函数使用上下文来知道何时退出,并传递一个通道,用于发送目标组的更新。
查看提供的示例中的 Run 函数,我们可以看到在另一个 SD 的实现中我们需要做的几个关键事项。我们会定期调用,在本例中是 Consul(为了举例说明,假设还没有内置的 Consul SD 实现),并将响应转换为一组 targetgroup.Group
结构。由于 Consul 的工作方式,我们必须先调用以获取所有已知服务,然后每个服务再调用一次以获取有关所有支持实例的信息。
请注意在对 Consul 的每个服务进行调用的循环上面的注释
// Note that we treat errors when querying specific consul services as fatal for for this
// iteration of the time.Tick loop. It's better to have some stale targets than an incomplete
// list of targets simply because there may have been a timeout. If the service is actually
// gone as far as consul is concerned, that will be picked up during the next iteration of
// the outer loop.
有了这个,我们表明如果无法获取所有目标的信息,最好不要发送任何更新,而不是发送不完整的更新。我们宁愿在短时间内获得过时的目标列表,并防止由于诸如瞬时网络问题、进程重启或 HTTP 超时等原因导致的误报。如果我们确实从 Consul 获得了关于每个目标的响应,我们会将所有这些目标发送到通道上。还有一个辅助函数 parseServiceNodes
,它接受单个服务的 Consul 响应,并使用标签从支持节点创建目标组。
使用当前示例
在开始编写您自己的自定义 SD 实现之前,在查看代码之后运行当前示例可能是一个好主意。为了简单起见,在使用示例代码时,我通常通过 docker-compose 将 Consul 和 Prometheus 作为 Docker 容器运行。
docker-compose.yml
version: '2'
services:
consul:
image: consul:latest
container_name: consul
ports:
- 8300:8300
- 8500:8500
volumes:
- ${PWD}/consul.json:/consul/config/consul.json
prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- 9090:9090
consul.json
{
"service": {
"name": "prometheus",
"port": 9090,
"checks": [
{
"id": "metrics",
"name": "Prometheus Server Metrics",
"http": "http://prometheus:9090/metrics",
"interval": "10s"
}
]
}
}
如果我们通过 docker-compose 启动两个容器,然后运行示例 main.go,我们将查询 localhost:8500 上的 Consul HTTP API,并且与 file_sd 兼容的文件将作为 custom_sd.json 写入。我们可以配置 Prometheus 以通过 file_sd 配置来获取此文件
scrape_configs:
- job_name: "custom-sd"
scrape_interval: "15s"
file_sd_configs:
- files:
- /path/to/custom_sd.json