Prometheus のスクレイプジョブがメトリクスに到達できません

すでに Discourse のインストールが稼働しています(実際には 2 つあり、ステージングと本番で、それぞれ異なる VM です)。ステージング環境でテストしています。インストールは公式ガイドに従って行いました。

現在、Discourse が既にデプロイされているのと同じ VM に、docker compose を介して Grafana/Prometheus/Node Exporter スタックがデプロイされています。

以下は 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

bridge ではなくネットワークを指定して Discourse を再構築し、Prometheus を同じネットワークに接続しました。

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

Prometheus コンテナに入り、内部ネットワークエイリアスを使用して Discourse コンテナに ping を送信したところ、到達可能であることを確認しました。

現在、内部 IP を使用してメトリクスをスクレイピングするように Prometheus ジョブを設定していますが、「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 は実際の VM 名の置き換えです。

ドキュメントこちらによると、内部 IP 経由でのアクセスは許可されるはずです。

デフォルトで、metrics ルートは管理者とプライベート IP に許可されます。

何が不足しているか教えていただけますでしょうか :stuck_out_tongue:

さらに詳しく調べるために、DiscourseからAPIキーを生成し、内部ホスト名を使用してそれに到達しようとしましたが、応答は301ではありませんでした。これは、すべてのリクエストがhttpsにリダイレクトされることになっているため、正しいです。

問題は、内部IPからのリクエストであっても、承認されていないと見なされ、その結果404になることだと私は思います。

Prometheus プラグインがインストールされ、有効になっていることを確認しましたか?プライベートアドレスからのリクエストを許可するはずですが、取得元の IP からのアクセスを許可するように環境変数を設定することもできます。

はい、Prometheus は同じ VM 上にあり、Docker コンテナとしてデプロイされています。すべて正常に動作していますが(他のエクスポーターもデプロイされています)、なぜか Discourse Prometheus プラグインは、明らかに稼働しているにもかかわらず、リクエストを受け付けていません。

ENV 変数について話しているとき、Discourse の app.yaml ファイル内の環境について話していますか?

では、このような感じでしょうか?

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

172.20.0.3 は、Discourse も接続されている Docker 仮想ネットワーク上の Prometheus の現在の内部 IP です。

コンテナが互いにアクセスする場合、内部 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 からのリクエストの場合、パス URL http://yourwebsite.com/metrics に対してこのリダイレクトが発生するはずではなく、プラグインがこれを処理し、このルールを追加する nginx 設定を追加することを期待していましたが、そうはなっていないようです。

Discourse の開発者の方、どなたかコメントいただけますか?ランダムに人を ping したり、この問題を報告した人が誰もいないのが奇妙だと感じたりしたくありません。

編集:ネットワーク設定の静的ホスト名を指定して再構築しました。これは、再構築ごとにコンテナに新しいランダムなホスト名が割り当てられていることに気づいたためです。
その後、metrics の HTTPS バージョンにアクセスするように Prometheus ジョブを設定しようとしましたが、問題はステップ 1 に戻ります。

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 がここで言いたかったのは、正しい IP アドレスに解決される任意の名前ではなく、設定されたホスト名(コンテナの .yml 定義の DISCOURSE_HOSTNAME)を使用する必要があるということです。

これは意図的なものであり、どこからでもパブリックインスタンスを簡単にリバースプロキシできないようにするため、また設定されたホスト名のみが受け入れられるようにするためです。

$ 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
[... ]

# 次のものは、try.discourse.org と同じ IP アドレスを指す
# try.somebogusreverseproxy.com で DNS レコードを作成し、
# 次に 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

機能するはずですが、これはハックです。期待されるのは、Discourse が設定されたホスト名で透過的に到達できるように、必要に応じて DNS を設定することです。

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

後々のために、必要なものをすべて設定したリポジトリを用意しました。

フォワードプロキシの設定ファイルにあるウェブサイトのFQDNのようなハードコードされた値は、他の人が使用したい場合に変更する必要がありますが、他の誰かの役に立つかもしれません。

Docker ComposeからNginxの設定、リソースとダッシュボードの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 -ltp または netstat -ltp(それぞれ必要なパッケージは net-tools および iproute2)を使用して確認できます。たとえば、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

マイケル、素晴らしい内容をまとめてくれてありがとう。

今週は仕事で時間を使いすぎたので、週末にテストしてみます :stuck_out_tongue:

試している間に、Prometheusコンテナがメトリクスをリクエストする際の内部IPを DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX に追加しようとしましたが、うまくいきませんでした。

DISCOURSE_PROMETHEUS_WEBSERVER_BIND を提案されていますが、これはどこから情報を得たのでしょうか? app.yml ファイルに追加する別の環境変数だと仮定してよろしいでしょうか?

どのようにうまくいかなかったのですか?

接続に失敗した場合、allowlistの設定は関係ありません。なぜなら、それは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.