Prometheus抓取作业无法到达指标

我有一个正在运行的 Discourse 安装(实际上有两个,一个在暂存环境,一个在生产环境,分别在不同的虚拟机上)。我正在暂存环境中进行测试。安装是通过官方指南完成的。

目前,Grafana/Prometheus/Node Exporter 堆栈是通过 docker compose 部署在与 Discourse 安装相同的虚拟机上。

这是 docker-compose.yaml:

version: "3"

services:
    cadvisor:
        image: gcr.io/cadvisor/cadvisor:latest
        container_name: cadvisor
        restart: unless-stopped
        volumes:
            - /:/rootfs:ro
            - /var/run:/var/run:ro
            - /sys:/sys:ro
            - /var/lib/docker/:/var/lib/docker:ro
            - /dev/disk/:/dev/disk:ro
        networks:
            - prometheus-cadvisor

    node_exporter:
        image: quay.io/prometheus/node-exporter:latest
        container_name: node_exporter
        command:
            - '--path.rootfs=/host'
        pid: host
        restart: unless-stopped
        volumes:
            - '/:/host:ro,rslave'
        networks:
            - prometheus-node_exporter

    prometheus:
        image: prom/prometheus:latest
        restart: unless-stopped
        container_name: prometheus
        ports:
            - "9090:9090"
        volumes:
            - ./prometheus:/app.cfg
        networks:
            - world
            - prometheus-cadvisor
            - prometheus-node_exporter
            - discourse
            - grafana-prometheus
        command: >-
            --config.file=/app.cfg/prometheus.yaml
            --storage.tsdb.path=/prometheus
            --web.console.libraries=/usr/share/prometheus/console_libraries
            --web.console.templates=/usr/share/prometheus/consoles

    grafana:
        image: grafana/grafana:latest
        container_name: grafana
        restart: unless-stopped
        ports:
            - "3000:3000"
        environment:
            GF_SECURITY_ADMIN_USER: [OMITTED]
            GF_SECURITY_ADMIN_PASSWORD: [OMITTED]
            GF_PATHS_PROVISIONING: '/app.cfg/provisioning'
        volumes:
            - ./grafana:/app.cfg
            - ./grafana/provisioning:/etc/grafana/provisioning
        networks:
            - world
            - grafana-prometheus

networks:
    world:
    grafana-prometheus:
        internal: true
    prometheus-cadvisor:
        internal: true
    prometheus-node_exporter:
        internal: true
    discourse:
        external: true

我重建了 Discourse,指定了一个网络,使其不部署在 bridge 上,并将 Prometheus 连接到同一个网络。

docker network create -d bridge discourse
/var/discourse/launcher rebuild app --docker-args '--network discourse'

我通过进入 Prometheus 容器并使用内部网络别名 ping Discourse 容器进行了测试,它能够到达。

现在,在配置 Prometheus 作业以抓取指标时,使用内部 IP,我只能看到 server returned HTTP status 404 Not Found

这是 Prometheus 的配置:

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

scrape_configs:
  - job_name: prometheus
    metrics_path: /metrics
    static_configs:
      - targets:
        - 'prometheus:9090'
  - job_name: node_exporter
    static_configs:
      - targets:
        - 'node_exporter:9100'
  - job_name: discourse_exporter
    static_configs:
      - targets:
        - 'vmuniqueID-app:80'

vmuniqueID 是实际虚拟机名称的替换。

根据文档此处,通过内部 IP 访问应该是允许的:

开箱即用,我们允许 metrics 路由访问管理员和私有 IP。

请帮我看看我遗漏了什么 :stuck_out_tongue:

为了进一步探究,我尝试从 Discourse 生成一个 API 密钥,并使用内部主机名访问它,响应不是 301,这是正确的,因为每个请求都应该重定向到 https。

我认为问题在于,即使是来自内部 IP 的请求,也被视为未经授权,并因此导致 404。

您是否已安装并启用了 Prometheus 插件?它应该允许来自私有地址的请求,但您可以尝试设置环境变量以允许来自您正在拉取的 IP 的访问。

是的,Prometheus 也在同一个虚拟机上,并作为 Docker 容器部署。一切正常(我还有其他导出器已部署),但不知何故,Discourse Prometheus 插件即使明显已启动并运行,也无法接受请求。

您所说的环境变量是指 Discourse 的 app.yaml 文件中的环境吗?

那么,类似这样:

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

172.20.0.3 是 Prometheus 在与 Discourse 连接的 Docker 虚拟网络上的当前内部 IP。

我已经尝试使用所有容器共享的外部 IP(VM 的静态 IP),但由于它们在同一个网络上,当一个容器尝试访问另一个容器时,它会通过内部 IP 进行访问。

./launcher restart app 应该足以让环境变量生效,对吧?

在这种情况下,我得到:

Get "http://vmi1187507-app:80/metrics": dial tcp: lookup vmi1187507-app on 127.0.0.11:53: server misbehaving

vmi1187507-app 是其网络中的容器网络名称。名称是正确的,我可以在运行的 Prometheus 容器中 ping 通它。
说实话,我不知道 127.0.0.11:53 是从哪里来的 :thinking:

如果我注释掉环境变量,消息也是一样的。

认为是的,但也不完全确定。你可以从容器内部进行测试,看看是否能从那里 curl 它。

从 prometheus 容器运行 wget 返回:

/prometheus # wget http://vmi1229594-app:80/metrics
Connecting to vmi1229594-app:80 (172.20.0.2:80)
Connecting to [public URL] (172.67.69.84:443)
wget: note: TLS certificate validation not implemented
wget: server returned error: HTTP/1.1 404 Not Found

我猜这是 Discourse nginx 容器的自动重定向?
发生的情况是,它会转发到公共域名的 https,这是一个内部 Cloudflare IP,当然会告诉任何请求返回。

现在这无关紧要,因为如果来自内部 IP 的请求路径是 http://yourwebsite.com/metrics,则不应发生此重定向,我期望插件通过添加一个添加此规则的 nginx conf 来处理此问题,但显然没有发生?

Discourse 开发者能否介入?我不想随意 ping 人,而且感觉很奇怪,以前没有人报告过这个问题。

编辑:我重建时还指定了网络配置的静态主机名,因为我注意到每次重建时都会为容器分配一个新的随机主机名。
之后,我还尝试将 prometheus 作业设置为访问 https 版本的指标,但问题又回到了第一步:

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

scrape_configs:
# other jobs
# [...]
  - job_name: discourse_exporter
    scheme: https
    tls_config:
      insecure_skip_verify: true
    static_configs:
      - targets:
        - 'discourse_app'
/prometheus # wget https://discourse_app/metrics
Connecting to discourse_app (172.20.0.2:443)
wget: note: TLS certificate validation not implemented
Connecting to [public URL] (104.26.4.193:443)
wget: server returned error: HTTP/1.1 404 Not Found

此时,这似乎是插件本身的问题。

听起来没错。你需要使用主机名访问它,而不是容器名。

我正在使用主机名,我写了很多并且很晚,可能有些令人困惑,但绝对使用的是内部网络主机名。

这不是我的专业领域,但我翻阅了主题计时器吞噬的帖子,看看是否有任何可能相关,并且可能找到了这些?(如果我离题万里,请原谅我 :slight_smile: :pray:

感谢 @JammyDodger,但不幸的是,这些资源并没有帮助。

它们遇到了类似但略有不同的问题,以至于不适用于此情况。
为确保万无一失,我尝试了其中一个主题(以及 @pfaffman)提出的建议,并调整了 DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX 环境变量。

我测试了:

  • 注释掉它
  • 添加并使用内部 IP 值
  • 添加并使用外部 IP 值

还尝试更改 Prometheus 抓取作业,将 Discourse 安装地址设置为:

  • 直接内部 IP
  • Docker 内部主机名
  • 直接外部 IP
  • 公共域名

在所有情况下,我都尝试了 httphttps

在所有情况下,我都收到 404 错误。
我期望的是实际的页面响应,因为请求来自内部 IP。

1 个赞

Jay 的意思是,你需要使用配置的主机名(在你的容器 .yml 定义中为 DISCOURSE_HOSTNAME),而不是任何碰巧解析到同一 IP 地址的主机名。

这是故意的,这样你就不能轻易地从任何地方反向代理一个公共实例,并且只接受配置的主机名:

$ curl -I https://try.discourse.org/about.json
HTTP/2 200
server: nginx
date: Mon, 15 May 2023 16:25:05 GMT
content-type: application/json; charset=utf-8
[...]

# The following is equivalent to creating a DNS record at
# try.somebogusreverseproxy.com pointing to the same IP address as try.discourse.org,
# and then requesting https://try.somebogusreverseproxy.com/about.json
$ curl -H 'Host: try.somebogusreverseproxy.com' -I https://try.discourse.org/about.json
HTTP/2 404
cache-control: no-cache
content-length: 1427
content-type: text/html
cdck-proxy-id: app-router-tiehunter02.sea1
cdck-proxy-id: app-balancer-tieinterceptor1b.sea1

反之,如果你尝试这样做:

curl -H 'Host: YOUR_CONFIGURED_HOSTNAME' -I https://discourse_app/metrics

它应该可以工作,但这是一种 hack。预期是你将根据需要设置 DNS,以便可以透明地通过配置的主机名访问 Discourse:

curl -I https://YOUR_CONFIGURED_HOSTNAME/metrics

如何做到这一点很大程度上取决于你的需求,但最简单的选择是在 HTTP 请求发起的源头在 /etc/hosts 中设置一个别名。

3 个赞

Prometheus exporter 不在 80 端口运行 - 它侦听自己的端口。默认情况下是 端口 9405

5 个赞

但是,如果我尝试定位该特定端口,我会收到“连接被拒绝”消息。

Get "http://discourse_app:9405/metrics": dial tcp 172.20.0.2:9405: connect: connection refused

为了确保万无一失,我还从 Prometheus 容器内部使用 wget 进行了测试。

/prometheus # ping discourse_app
PING discourse_app (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.223 ms
64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.270 ms
^C
--- discourse_app ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.223/0.246/0.270 ms
/prometheus # wget discourse_app:9405/metrics
Connecting to discourse_app:9405 (172.20.0.2:9405)
wget: can't connect to remote host (172.20.0.2): Connection refused

是的,我改用 wget 测试了(Prometheus 容器是一个精简的 busybox),但最终还是能够访问 metrics。

那么您的意思是,我应该找到一种方法让运行 Prometheus 的容器在 /etc/hosts 中有一个解析条目……抱歉,我没听懂 :slight_smile:

我所做的是添加另一个只有 nginx 的 docker,并提供一个正向代理配置,该配置将 Host 标头添加到它接收到的请求中。它不公开任何端口,因此只能通过内部虚拟网络访问。

那么情况如何变化?

Prometheus Job:

  - job_name: discourse_exporter_proxy
    scheme: http
    static_configs:
      - targets:
        - 'discourse_forward_proxy:8080'

docker-compose.yaml(仅代理部分)

version: "3"

services:
# [...]
    discourse_forward_proxy:
        image: nginx:latest
        container_name: discourse_forward_proxy
        restart: unless-stopped
        volumes:
            - ./discourse_forward_proxy/:/etc/nginx/conf.d
        networks:
            - prometheus-discourse_forward_proxy
            - discourse
# [...]

networks:
    prometheus-discourse_forward_proxy:
        internal: true
    discourse:
        external: true

在您的 docker-compose.yaml 所在的目录中,有一个 ./discourse_forward_proxy/discourse_forward_proxy.conf

server {
    listen 8080;

    location /metrics {
      proxy_set_header Host "YOUR_DOMAIN_HERE.COM";
      proxy_pass https://discourse_app/metrics;
    }
}

看,就这样了:

1 个赞

为了方便以后查阅,我有一个仓库,里面设置了所有必需的东西。
其中包含一些硬编码的值(例如我们网站在正向代理配置文件中的完全限定域名),如果其他人想使用它,则需要进行更改,但也许它对其他有需要的人会有所帮助。

它包含了所有内容,从 docker compose 到 nginx conf 以及资源的 grafana 配置和仪表板。

这是由于下一行:

GlobalSetting.add_default :prometheus_collector_port, 9405
GlobalSetting.add_default :prometheus_webserver_bind, "localhost"
GlobalSetting.add_default :prometheus_trusted_ip_allowlist_regex, ""

绑定到 localhost 意味着它只能通过 localhost IP 连接,这就是为什么连接到 172.20.0.2 会失败。这是一种安全措施,可确保它不会意外地暴露给比预期更广泛的受众。

如果在容器定义文件中设置:

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

它将监听所有 IP 地址,您将能够从另一个容器连接到它。

这使其起作用的原因:

server {
    listen 8080;

    location /metrics {
      proxy_set_header Host "YOUR_DOMAIN_HERE.COM";
      proxy_pass https://discourse_app/metrics;
    }
}

是因为这个 nginx 容器现在通过 localhost IP 与 prometheus 通信。

如果您不确定服务正在监听的 IP 或端口,您可以使用 ss -ltpnetstat -ltp容器内部!所需的包分别是 net-toolsiproute2)来查看它们。例如,我刚刚使用 prometheus 插件重建了一个容器,看到:

root@discourse-docker-app:/# ss -ltp
State      Recv-Q     Send-Q           Local Address:Port                 Peer Address:Port     Process
LISTEN     0          128                  127.0.0.1:3000                      0.0.0.0:*
LISTEN     0          128                    0.0.0.0:postgresql                0.0.0.0:*
LISTEN     0          128                    0.0.0.0:https                     0.0.0.0:*         users:(("nginx",pid=555,fd=7))
LISTEN     0          128                  127.0.0.1:9405                      0.0.0.0:*
LISTEN     0          128                    0.0.0.0:redis                     0.0.0.0:*
LISTEN     0          128                    0.0.0.0:http                      0.0.0.0:*         users:(("nginx",pid=555,fd=6))
LISTEN     0          128                       [::]:postgresql                   [::]:*
LISTEN     0          128                       [::]:https                        [::]:*         users:(("nginx",pid=555,fd=8))
LISTEN     0          128                       [::]:redis                        [::]:*

root@discourse-docker-app:/# curl http://172.17.0.2:9405/metrics
curl: (7) Failed to connect to 172.17.0.2 port 9405: Connection refused

root@discourse-docker-app:/# curl http://localhost:9405/metrics
# HELP discourse_collector_working Is the master process collector able to collect metrics
# TYPE discourse_collector_working gauge
discourse_collector_working 1


# HELP discourse_collector_rss total memory used by collector process
# TYPE discourse_collector_rss gauge
discourse_collector_rss 38178816
…

这是名称服务器拒绝为 vmi1187507-app 进行 IP 查找请求。端口 53 是 DNS。

2 个赞

Michael,这太棒了,感谢你花时间写下来。

我将在周末进行测试,因为这周的工作日我已经花了太多时间了 :stuck_out_tongue:

在我尝试的过程中,我曾尝试将 Prometheus 容器请求指标的内部 IP 添加到 DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX,但没有成功。

你建议的是 DISCOURSE_PROMETHEUS_WEBSERVER_BIND。我能问一下你从哪里得到它的吗?我猜它是在 app.yml 文件中要添加的另一个环境变量,对吗?

怎么没起作用?

如果连接失败,那么允许列表的设置就没有意义了,因为它是在 L4 连接之后操作的。

Discourse 代码库中有魔法 :magic_wand:,如果你在 ENV 中设置了 DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME,它就会覆盖。

所以设置它将覆盖:

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.