Prometheus Scrape Job kann Metriken nicht erreichen

Ich habe eine laufende Discourse-Installation (tatsächlich zwei, eine im Staging und eine in der Produktion, unterschiedliche VMs und alles). Ich teste in der Staging-Umgebung. Installation über den offiziellen Leitfaden.

Derzeit ist ein Grafana/Prometheus/Node Exporter-Stack über docker compose auf derselben VM bereitgestellt, auf der die Discourse-Installation bereits bereitgestellt ist.

Hier ist die 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

Ich habe den Discourse neu erstellt und dabei ein Netzwerk angegeben, damit es nicht auf bridge bereitgestellt wird, und Prometheus mit demselben Netzwerk verbunden.

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

Ich habe getestet, indem ich in den Prometheus-Container eingestiegen bin und den Discourse-Container über den internen Netzwerknamen (Alias) angepingt habe, und er konnte ihn erreichen.

Wenn ich nun den Prometheus-Job konfiguriere, damit er die Metriken abruft, und eine interne IP verwende, sehe ich nur server returned HTTP status 404 Not Found.

Dies ist die Prometheus-Konfiguration:

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 ist ein Ersatz für den tatsächlichen Namen der VM.

Gemäß der Dokumentation hier sollte der Zugriff über die interne IP erlaubt sein:

Out of the box we allow the metrics route to admins and private ips.

Bitte helft mir zu sehen, was ich übersehe :stuck_out_tongue:

Um das weiter zu untersuchen, habe ich versucht, einen API-Schlüssel von Discourse zu generieren und ihn über den internen Hostnamen zu erreichen. Die Antwort ist kein 301, was richtig ist, da jede Anfrage zu HTTPS umgeleitet werden soll.

Das Problem, denke ich, ist, dass Anfragen, selbst wenn sie von einer internen IP-Adresse kommen, als nicht autorisiert behandelt werden und aus diesem Grund in einem 404 enden.

Haben Sie das Prometheus-Plugin installiert und aktiviert? Es sollte Anfragen von privaten Adressen zulassen, aber Sie könnten versuchen, die Umgebungsvariable so einzustellen, dass der Zugriff von der IP-Adresse, von der Sie abrufen, zugelassen wird.

Ja, Prometheus befindet sich auf demselben VM und ist als Docker-Container bereitgestellt. Alles funktioniert (ich habe auch andere Exporter bereitgestellt), aber aus irgendeinem Grund akzeptiert das Discourse Prometheus Plugin, obwohl es eindeutig läuft, keine Anfragen.

Wenn Sie von der Umgebungsvariable sprechen, meinen Sie die Umgebung in der Datei app.yaml von Discourse, richtig?

Also, so etwas wie das:

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

172.20.0.3 ist die aktuelle interne IP, die Prometheus im Docker-Virtual-Netzwerk haben wird, an das auch Discourse angeschlossen ist.

Ich habe bereits versucht, die externe IP zu verwenden, die alle Container sowieso gemeinsam nutzen (die statische IP der VM), aber da sie sich im selben Netzwerk befinden, greift einer auf den anderen über die interne IP zu.

Ein ./launcher restart app sollte ausreichen, damit die Umgebungen übernommen werden, oder?

In diesem Fall erhalte ich:

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

vmi1187507-app ist der Container-Netzwerkname in seinem Netzwerk. Der Name ist korrekt, ich kann ihn vom Prometheus-Container aus anpingen.
Ich habe keine Ahnung, woher 127.0.0.11:53 kommt, um ehrlich zu sein :thinking:

Die Meldung ist dieselbe, wenn ich die Umgebungsvariable auskommentiere.

Ich denke schon, bin mir aber nicht ganz sicher. Du kannst es vom Container aus testen und sehen, ob du es von dort mit curl abrufen kannst.

Beim Ausführen eines wget vom Prometheus-Container wird Folgendes zurückgegeben:

/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

Ich vermute hier, dass es sich um die automatische Weiterleitung vom Discourse Nginx-Container handelt?
Was passiert, ist, dass es an das HTTPS der öffentlichen Domain weitergeleitet wird, was eine interne Cloudflare-IP ist, und das weist jede Anfrage zurück.

Das ist jetzt nebensächlich, denn diese Weiterleitung sollte für die Pfad-URL http://yourwebsite.com/metrics nicht erfolgen, wenn sie von einer internen IP kommt, und ich erwartete, dass das Plugin dies durch Hinzufügen einer Nginx-Konfiguration, die diese Regel hinzufügt, erledigen würde, was offenbar nicht geschieht?

Kann sich jemand von den Discourse-Entwicklern dazu äußern? Ich möchte niemanden zufällig anpingen und es fühlt sich seltsam an, dass noch niemand dieses Problem gemeldet hat.

Bearbeiten: Ich habe neu kompiliert und auch einen statischen Hostnamen für die Netzwerkkonfiguration angegeben, da ich bemerkt habe, dass bei jedem Neubau ein neuer zufälliger Hostname dem Container zugewiesen wurde.
Danach habe ich auch versucht, den Prometheus-Job so einzustellen, dass er auf die HTTPS-Version der Metriken zugreift, aber das Problem kehrt zum ersten Schritt zurück:

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

Zu diesem Zeitpunkt scheint dies ein Problem mit dem Plugin selbst zu sein.

Das klingt richtig. Sie müssen darauf über den Hostnamen und nicht über den Containernamen zugreifen.

Ich verwende den Hostnamen, ich habe viel und spät geschrieben, es war vielleicht verwirrend, aber definitiv den internen Netzwerkhostnamen verwendet.

Das ist nicht mein Fachgebiet, aber ich habe die Beiträge, die der Thema-Timer verschluckt hat, durchforstet, um zu sehen, ob welche relevant sein könnten, und habe vielleicht diese gefunden? (Ich entschuldige mich, wenn ich völlig daneben liege :slight_smile: :pray: )

Danke @JammyDodger, aber leider haben diese Ressourcen nicht geholfen.

Sie haben ähnliche Probleme, aber leicht unterschiedlich, sodass sie in diesem Fall nicht zutreffen.
Um sicherzugehen, habe ich versucht, was eines dieser Themen vorschlug (sowie @pfaffman) und mit der Umgebungsvariable DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX herumgespielt.

Ich habe getestet:

  • Auskommentieren
  • Hinzugefügt und mit internem IP-Wert
  • Hinzugefügt und mit externem IP-Wert

Ich habe auch versucht, den Prometheus-Scrape-Job zu ändern, um die Discourse-Installation anzusprechen als:

  • direkte interne IP
  • interne Docker-Hostname
  • direkte externe IP
  • öffentlicher Domainname

In jedem Fall habe ich sowohl http als auch https ausprobiert.

In allen Fällen erhalte ich eine 404.
Ich würde erwarten, die tatsächliche Seitenantwort zu erhalten, da die Anfrage von einer internen IP kommt.

1 „Gefällt mir“

Was Jay hier meinte, ist, dass Sie den konfigurierten Hostnamen (DISCOURSE_HOSTNAME in Ihrer Container-.yml-Definition) verwenden müssen und nicht einen beliebigen Hostnamen, der auf die richtige IP-Adresse aufgelöst wird.

Dies ist beabsichtigt, damit Sie eine öffentliche Instanz nicht einfach von überall aus per Reverse-Proxy erreichen können und nur der konfigurierte Hostname akzeptiert wird:

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

# Das Folgende entspricht der Erstellung eines DNS-Eintrags unter
# try.somebogusreverseproxy.com, der auf dieselbe IP-Adresse wie try.discourse.org zeigt,
# und dann die Anforderung von 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

Umgekehrt, wenn Sie dies versuchen:

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

sollte es funktionieren, ist aber ein Hack. Es wird erwartet, dass Sie DNS nach Bedarf einrichten, damit Discourse über seinen konfigurierten Hostnamen transparent erreichbar ist:

curl -I https://YOUR_CONFIGURED_HOSTNAME/metrics

Wie Sie das tun, hängt stark von Ihren Anforderungen ab, aber die einfachste Option ist die Einrichtung eines Alias in /etc/hosts von dort, wo Ihre HTTP-Anfragen ausgehen.

3 „Gefällt mir“

Der Prometheus-Exporter läuft nicht auf Port 80 – er lauscht auf einem eigenen Port. Standardmäßig Port 9405.

5 „Gefällt mir“

Guter Fund, aber wenn ich versuche, auf diesen bestimmten Port zuzugreifen, erhalte ich die Meldung „Verbindung verweigert“.

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

Ich habe es auch mit wget aus dem Prometheus-Container heraus getestet, um sicherzugehen.

/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

Ja, ich habe es stattdessen mit wget getestet (der Prometheus-Container ist ein einfaches Busybox), aber ich habe es trotzdem zu den Metriken geschafft.

Sie sagen also, ich sollte einen Weg finden, den Prometheus-Container mit einem Eintrag in /etc/hosts zum Laufen zu bringen, der auflöst… Ich habe Sie da verloren, Entschuldigung :slight_smile:

Was ich getan habe, ist, einen weiteren Docker mit nur einem Nginx darin hinzuzufügen und eine Forward-Proxy-Konfiguration bereitzustellen, die den Header Host zu den Anfragen hinzufügt, die er empfängt. Er gibt keinen Port frei, sodass er nur über das interne virtuelle Netzwerk zugänglich ist.

Wie ändern sich die Dinge also?

Prometheus Job:

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

docker-compose.yaml (nur der Teil mit dem Proxy)

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

Im Verzeichnis, in dem sich Ihre docker-compose.yaml befindet, haben Sie ./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;
    }
}

Da haben Sie es:

1 „Gefällt mir“

Nur für die Nachwelt, ich habe ein Repository, in dem ich alles Notwendige eingerichtet habe.
Es gibt einige hartcodierte Werte (wie die FQDN unserer Website in der Forward-Proxy-Konfigurationsdatei), die geändert werden müssen, falls jemand anderes es verwenden möchte, aber vielleicht ist es für jemand anderen da draußen nützlich.

Es enthält alles, vom Docker Compose über die Nginx-Konfiguration bis hin zur Grafana-Bereitstellung für Ressourcen und Dashboards.

Das liegt an der nächsten Zeile:

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

Die Bindung an localhost bedeutet, dass nur über die localhost-IP eine Verbindung hergestellt werden kann, weshalb die Verbindung zu 172.20.0.2 fehlschlägt. Dies ist eine Sicherheitsmaßnahme, um sicherzustellen, dass es nicht versehentlich einem viel größeren Publikum als beabsichtigt ausgesetzt wird.

Wenn Sie in der Containerdefinitionsdatei festlegen:

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

wird auf allen IP-Adressen gelauscht und Sie können von einem anderen Container aus eine Verbindung herstellen.

Der Grund, warum dies funktioniert hat:

server {
    listen 8080;

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

ist, dass dieser Nginx-Container jetzt über die localhost-IP mit Prometheus spricht.

Wenn Sie sich nicht sicher sind, über welche IPs oder Ports Dienste lauschen, können Sie ss -ltp oder netstat -ltp (innerhalb des Containers! Notwendige Pakete sind net-tools bzw. iproute2) verwenden, um sie anzuzeigen. Ich habe zum Beispiel gerade einen Container mit dem Prometheus-Plugin neu kompiliert und sehe:

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
...

Das ist der Nameserver, der die IP-Lookup-Anfrage für vmi1187507-app ablehnt. Port 53 ist DNS.

2 „Gefällt mir“

Das sind tolle Sachen, Michael, danke, dass du dir die Zeit genommen hast, sie aufzuschreiben.

Ich werde es am Wochenende testen, da ich diese Woche schon zu viel Zeit während meiner Arbeitstage dafür aufgewendet habe :stuck_out_tongue:

Bei meinen Versuchen habe ich versucht, die interne IP, von der aus der Container mit Prometheus die Metriken anfordern würde, zu DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX hinzuzufügen, aber es hat nicht funktioniert.

Du schlägst DISCOURSE_PROMETHEUS_WEBSERVER_BIND vor. Darf ich fragen, woher du das hast? Ich gehe davon aus, dass es sich um eine weitere Umgebungsvariable handelt, die zur app.yml-Datei hinzugefügt werden muss, richtig?

Wie hat es nicht funktioniert?

Wenn die Verbindung fehlgeschlagen ist, spielt die Einstellung der Allowlist keine Rolle, da diese nach der L4-Verbindung operiert.

Es gibt Magie :magic_wand: in der Discourse-Codebasis, wo, wenn Sie DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME in der ENV setzen, diese überschrieben wird.

Das Setzen davon überschreibt also:

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

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