dns_sd_configs在这篇博文中,我将演示如何通过在 Docker swarm 中引入一个中间 Prometheus 实例并结合几个 Prometheus 功能(主要是跨服务联合)来收集和获取所需的指标数据,从而很容易地做到这一点。

在 Docker swarm 集群中,应用程序作为服务运行。对于外部宿主机中(特指swarm 集群之外的所有东西),该服务看起来就像通过已发布端口访问的实例。但是在 swarm 内部,通常有多个运行该服务的实例(副本)。外部主动请求时,Docker 网络将到已发布服务端口路由到正在运行的副本之一。作为调用者,您无法感知被路由到服务具体实例。

如果你想拥有一个在 Docker swarm 之外运行的 Prometheus 服务器来抓取服务的指标,最简单的方法是让它主动拉取已发布服务的监控,如果服务在具有多个实例的复制模式下运行,将无法准确获得具体的实例。因为对服务的调用实际上最终在 Docker 网络负载均衡器中完成,它将抓取请求转发到一个正在运行的实例。因此,您获得的数据是其中一个服务实例的指标(您不知道是哪一个)。由于 Prometheus 会定期抓取服务指标,并且每个抓取请求都独立于之前的请求进行路由,因此下一个抓取请求可能会被路由到返回该实例指标的不同服务实例并由其响应,等等。因此,最坏的情况是 Prometheus 在每次抓取请求时都会获得一组不同的指标,不会为您提供连贯的数据。

如果 Prometheus 知道多个服务实例并可以单独抓取它们,它将为指标添加一个instance标签,并由此为每个指标和实例存储不同的时间序列。Docker swarm 在向 Prometheus 隐藏这些细节方面做得很好,至少在 swarm 之外是这样。因此,如果您将 Prometheus 本身作为 Docker swarm 中的服务运行,您可以将其dns_sd_configs功能与 Docker swarm DNS 服务发现一起使用,以单独抓取所有实例。结合 Prometheus 的跨服务联合功能,您可以从 swarm 之外的 Prometheus 服务器上抓取这些服务实例指标。

在这篇博文中,我将设置一个运行示例服务的本地 Docker swarm 集群来演示它的外观。

使用示例服务设置 Docker 群

首先,我为本地 Docker 实例初始化 swarm 模式(可以使用 再次停用docker swarm leave --force

docker swarm init

我正在为 Mac 运行 Docker Desktop,所以这里不需要任何其他选项。有关如何在其他环境中设置本地 swarm 的详细信息,请参阅docker swarm 教程。

一个重要的细节(不幸的是,Docker swarm 文档中似乎没有描述)是,Docker swarm DNS 服务发现不适用于默认的入口覆盖网络(我花了很长时间才弄清楚这一点,直到我发现这个在 Docker 论坛中回答)。所以我将首先创建一个自定义覆盖网络。

docker network create \
    --driver overlay \
    --attachable \
    custom-overlay-network

作为示例服务,我使用了一个 Docker 映像,其中包含一个非常基本的 Spring Boot 应用程序,并启用了 Actuator 和 Micrometer Prometheus 插件。

docker service create \
    --name sample-service \
    --replicas 3 \
    --network custom-overlay-network \
    -p 8080:8080 \
    sample-app:latest

列出在我的 swarm 中运行的所有 Docker 服务,我可以看到我sample-service正在运行三个实例。

docker service ls

    ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
    kgjaw3vx1tnh        sample-service      replicated          3/3                 sample-app:latest   *:8080->8080/tcp

我的 Spring Boot 应用程序的 8080 端口已发布,因此我还可以访问执行器指标端点

curl localhost:8080/actuator/prometheus

    # HELP jvm_gc_live_data_size_bytes Size of old generation memory pool after a full GC
    # TYPE jvm_gc_live_data_size_bytes gauge
    jvm_gc_live_data_size_bytes 0.0
    # HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine
    # TYPE jvm_classes_loaded_classes gauge
    jvm_classes_loaded_classes 7469.0
    ...

由于我的 Docker swarm 只包含一个管理器节点(我的本地机器),我可以看到所有三个副本正在运行的 Docker 容器

docker ps

    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
    bc26b66080f7        sample-app:latest   "java -Djava.securit…"   6 minutes ago       Up 6 minutes                            sample-service.3.hp0xkndw8mx9yoph24rhh60pl
    b4cb0a313b82        sample-app:latest   "java -Djava.securit…"   6 minutes ago       Up 6 minutes                            sample-service.2.iwbagkwjpx4m6exm4w7bsj5pd
    7621dd38374a        sample-app:latest   "java -Djava.securit…"   6 minutes ago       Up 6 minutes                            sample-service.1.1a208aiqnu5lttkg93j4dptbe

为了查看运行中的 DNS 服务发现,我连接到在 Docker swarm 中运行的容器之一。我必须安装dnsutils软件包才能使用nslookup.

docker exec -ti bc26b66080f7 /bin/sh

apt-get update && apt-get install dnsutils -y

查找服务名称本身,我得到一个虚拟 IP 地址

nslookup sample-service

    Server:   127.0.0.11
    Address:  127.0.0.11#53

    Non-authoritative answer:
    Name:     sample-service
    Address:  10.0.1.2

要解析在我的 Docker 群中运行的所有服务副本的虚拟 IP 地址,我必须查找tasks.<service name>域名(请参阅Docker 覆盖网络文档

nslookup tasks.sample-service

    Server:   127.0.0.11
    Address:  127.0.0.11#53

    Non-authoritative answer:
    Name:     tasks.sample-service
    Address:  10.0.1.4
    Name:     tasks.sample-service
    Address:  10.0.1.3
    Name:     tasks.sample-service
    Address:  10.0.1.5

这个 DNS 服务发现功能正是运行在 Docker 群中的 Prometheus 实例可以用来抓取所有这些服务实例的功能(我将swarm-prometheus在其余文本中提到这个实例)。

在 swarm 中抓取服务实例

为了设置swarm-prometheus服务,我基于最新的官方 Prometheus 镜像构建了一个 Docker 镜像,并添加了我自己的配置文件。

FROM prom/prometheus:latest
ADD prometheus.yml /etc/prometheus/

配置文件中有趣的部分是swarm-service我添加的抓取作业。我使用 a dns_sd_config(有关详细信息,请参阅文档)通过执行 DNS 查询来查找抓取目标。我需要执行 A 类 DNS 查询,因为查询只返回服务实例的 IP 地址,所以我必须告诉 Prometheus 实例正在侦听的端口以及指标端点的路径。

scrape_configs:
  ...
  - job_name: 'swarm-service'
    dns_sd_configs:
      - names:
          - 'tasks.sample-service'
        type: 'A'
        port: 8080
    metrics_path: '/actuator/prometheus'

构建图像后,我创建了swarm-prometheus服务

docker build -t swarm-prometheus .

docker service create \
    --replicas 1 \
    --name swarm-prometheus \
    --network custom-overlay-network \
    -p 9090:9090 \
    swarm-prometheus:latest

当我打开 Prometheus Web UI 并导航到“Status -> Targets”时,我可以看到我的配置按预期工作

图1图 1 – swarm-prometheus Web UI 中配置的抓取作业的状态

对示例应用程序编写的指标之一执行基本查询,我得到三个结果时间序列,一个用于我的每个实例。由 prometheus 抓取作业添加的instance标签包含相应服务实例的 IP 和端口。

图 2图 2 - 具有三个结果时间序列的基本 Prometheus 查询

此时,我的所有服务实例的指标都收集在swarm-prometheus. 作为下一步,我想让它们进入在 swarm 之外运行的 Prometheus 服务器(我将host-prometheus在此处引用它)。

使用 federate 从另一个 Prometheus 抓取指标

Prometheus 提供了一个/federate端点,可用于从另一个 Prometheus 实例中抓取选定的时间序列集(有关详细信息,请参阅文档)。端点需要一个或多个即时向量选择器来指定请求的时间序列。

我想为我的抓取作业收集的所有时间序列调用和查询的/federate端点(我使用with和选项以便能够使用未编码的参数值)swarm-prometheus``swarm-service``curl``-G``--data-urlencode

curl -G "http://localhost:9090/federate" --data-urlencode 'match[]={job="swarm-service"}'

    # TYPE jvm_buffer_count_buffers untyped
    jvm_buffer_count_buffers{id="direct",instance="10.0.1.3:8080",job="swarm-service"} 10 1586866971856
    jvm_buffer_count_buffers{id="direct",instance="10.0.1.4:8080",job="swarm-service"} 10 1586866975100
    jvm_buffer_count_buffers{id="direct",instance="10.0.1.5:8080",job="swarm-service"} 10 1586866976176
    jvm_buffer_count_buffers{id="mapped",instance="10.0.1.3:8080",job="swarm-service"} 0 1586866971856
    jvm_buffer_count_buffers{id="mapped",instance="10.0.1.5:8080",job="swarm-service"} 0 1586866976176
    jvm_buffer_count_buffers{id="mapped",instance="10.0.1.4:8080",job="swarm-service"} 0 1586866975100
    ...

我唯一需要做的host-prometheus就是添加一个适当的抓取作业来请求该/federate端点。

scrape_configs:
  ...
  - job_name: 'swarm-prometheus'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="swarm-service"}'
    static_configs:
      - targets:
        - 'swarm-prometheus:9090'

因为我将host-prometheus在 Docker 中运行,连接到与我的 swarm 相同的网络,所以我可以使用swarm-prometheus服务名称作为主机名。在现实世界的环境中,我可能不得不找到另一种访问swarm-prometheus服务的方法,例如使用一个 docker swarm 节点的 IP 地址和发布的端口。

激活honor_labels标志确保 Prometheus 保留已包含在已抓取指标中的jobinstance标签,并且不会用自己的值覆盖它们(有关详细信息,请参阅scrape_config文档)。

构建并运行后,host-prometheus我可以再次检查目标状态页面以查看抓取作业是否成功运行

docker build -t host-prometheus .

docker run -d \
    --network custom-overlay-network \
    -p 9999:9090 \
    host-prometheus:latest

图 3图 3 – host-prometheus Web UI 中配置的抓取作业的状态

host-prometheus现在我可以在我的Web UI中执行与以前相同的 Prometheus 查询,并获得三个结果时间序列。

所以,已经是这样了。只需在 docker swarm 中设置一个中间 Prometheus 实例并结合几个现有功能,就可以很容易地将所有 swarm 服务实例的指标获取到 Prometheus 服务器中,即使它必须在 swarm 之外运行。

优化

在我当前的项目中实施上述设置后,我提出了一些我认为值得分享的改进。

如果您在 docker swarm 中运行多个不同的 Spring Boot 服务,所有服务都在默认端口 8080 上进行侦听,那么为每个服务设置一个专用的swarm-prometheus抓取作业是非常多余的。每个服务唯一需要更改的是请求的域名 ( tasks.<service name>)。而且,您可能已经注意到,可以在dns_sd_configs. 所以我们可以配置一个覆盖所有现有服务的抓取作业

scrape_configs:
  ...
  - job_name: 'swarm-services'
    metrics_path: '/actuator/prometheus'
    dns_sd_configs:
      - names:
          - 'tasks.sample-service-1'
          - 'tasks.sample-service-2'
          - ...
        type: 'A'
        port: 8080

但是,这样做我们可能会遇到另一个问题。使用旧配置,每个服务有一个抓取作业,我们能够相应地命名抓取作业并使用job标签来识别/过滤不同服务的指标。现在,有了一个通用的抓取工作,我们必须为此找到另一种解决方案。

幸运的是,我们在 Spring Boot 应用程序中用于提供 Prometheus 指标端点的库 Micrometer 可以轻松配置为向所有书面指标添加自定义标签。通过将以下行添加到application.properties我们每个 Spring Boot 服务的配置文件(例如)中,一个以service包含服务名称(此处)的静态值命名的标签sample-service-1被添加到我们的服务写入的所有指标中。

management.metrics.tags.service=sample-service-1

最后,如果您在 Prometheus 之上使用Grafanainstance,则包含服务实例的 IP 地址和端口(例如10.0.1.3:8080)的标签值将被证明是有问题的。如果您想将它们用作仪表板变量(例如,为所有实例重复面板或为一个具体实例过滤数据)这将不起作用,因为值中的点和冒号(这些值将中断对底层 Prometheus 的数据请求因为它们不是由 Grafana 编码的 URL)。我们必须将它们转换为问题较少的格式才能以这种方式使用它们。我们可以通过在抓取作业配置中添加一个metric_relabel_configs来做到这一点swarm-prometheus

scrape_configs:
  ...
  - job_name: 'swarm-services'
    metrics_path: '/actuator/prometheus'
    dns_sd_configs:
      - names:
          - 'tasks.sample-service-1'
          - 'tasks.sample-service-2'
          - ...
        type: 'A'
        port: 8080
    metric_relabel_configs:
      - source_labels: [ instance ]
        regex: '^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\:([0-9]+)$'
        replacement: '${1}_${2}_${3}_${4}'
        target_label: instance

此配置采用source_labels(here instance) 的所有值,将给定值应用于每个regex值,用给定表达式replacement替换值(使用由覆盖原始值)到指标中。因此,旧值将转换为对 Grafana 而言问题较小的值。${1}``${2}``regex``target_label``instance``10.0.1.3:8080``10_0_1_3


更新:从 Prometheus 2.20 开始,还有一个可用的Docker Swarm 服务发现可以用来代替本文中描述的 DNS 服务发现。感谢Julien Pivoto向我介绍了新功能。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐