Il job di scrape di Prometheus non riesce a raggiungere le metriche

Ho un’installazione Discourse in esecuzione (due in realtà, una in staging e un’altra in produzione, VM diverse e tutto il resto). Sto testando sull’ambiente di staging. Installazione tramite la guida ufficiale.

Attualmente uno stack Grafana/Prometheus/Node Exporter è distribuito tramite docker compose sulla stessa VM in cui è già distribuita l’installazione di Discourse.

Ecco il 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

Ho ricostruito discourse specificando una rete in modo che non venga distribuito su bridge e ho collegato Prometheus sulla stessa rete.

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

Ho testato entrando nel container Prometheus e facendo il ping al container discourse usando l’alias della rete interna e ha potuto raggiungerlo.

Ora, quando configuro il job di Prometheus in modo che esegua lo scraping delle metriche, utilizzando un IP interno, vedo solo server returned HTTP status 404 Not Found.

Questa è la configurazione di 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 è una sostituzione del nome effettivo della VM.

Secondo la documentazione qui, l’accesso tramite IP interno dovrebbe essere consentito:

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

Aiutatemi a capire cosa mi sfugge :stuck_out_tongue:

Per approfondire ulteriormente, ho provato a generare una chiave API da Discourse e ad accedervi utilizzando l’hostname interno e la risposta non è un 301, il che è corretto perché ogni richiesta dovrebbe essere reindirizzata a https.

Il problema, penso, è che le richieste in arrivo, anche se provenienti da un IP interno, vengono trattate come non autorizzate e finiscono per questo motivo con un 404.

Hai il plugin Prometheus installato e abilitato? Dovrebbe consentire richieste da indirizzi privati, ma potresti provare a impostare la variabile d’ambiente per consentire l’accesso dall’IP da cui stai recuperando i dati.

Sì, Prometheus è sulla stessa VM e distribuito come container Docker. Tutto funziona (ho anche altri exporter distribuiti) ma per qualche motivo il plugin Discourse Prometheus, anche se chiaramente attivo e funzionante, non accetta richieste.

Quando dici la variabile ENV ti riferisci all’ambiente nel file app.yaml di Discourse, giusto?

Quindi, qualcosa del genere:

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

172.20.0.3 essendo l’attuale IP interno che Prometheus avrà sulla rete virtuale Docker a cui è collegato anche Discourse.

Ho già provato a usare l’IP esterno che tutti i container condividono comunque (l’IP statico della VM) ma dato che sono sulla stessa rete, quando uno cerca di accedere all’altro, lo fa tramite l’IP interno.

Un ./launcher restart app dovrebbe essere sufficiente affinché gli env vengano rilevati, giusto?

In quel caso ottengo:

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

vmi1187507-app è il nome della rete del container nella sua rete. Il nome è corretto, posso pingarlo dal container Prometheus in esecuzione.
Non ho idea da dove provenga quel 127.0.0.11:53 ad essere sincero :thinking:

Il messaggio è lo stesso se commento la variabile env.

Penserei di sì, ma non ne sono del tutto sicuro. Puoi testare dall’interno del container e vedere se riesci a fare curl da lì.

Eseguendo un wget dal container prometheus restituisce:

/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

Sto ipotizzando che sia il reindirizzamento automatico dal container nginx di Discourse?
Quello che succede è che inoltra all’https del nome di dominio pubblico che è un IP interno di Cloudflare e questo sta ovviamente dicendo a qualsiasi richiesta di tornare indietro.

Ora questo è secondario perché questo reindirizzamento non dovrebbe avvenire per l’URL del percorso http://yourwebsite.com/metrics se proviene da un IP interno e mi aspettavo che il plugin se ne occupasse aggiungendo una configurazione nginx che aggiungesse questa regola, cosa che apparentemente non sta succedendo?

Qualcuno degli sviluppatori di Discourse può intervenire? Non voglio infastidire persone a caso e mi sembra strano che nessuno abbia mai segnalato questo problema prima.

Modifica: Ho ricostruito specificando anche un hostname statico per la configurazione di rete perché ho notato che ogni ricostruzione ne veniva assegnato uno nuovo e casuale al container.
Dopo di che ho anche provato a impostare il job prometheus per accedere alla versione https delle metriche, ma il problema torna al primo passaggio:

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

scrape_configs:
# altri job
# [...]
  - 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

A questo punto, questo sembra un problema con il plugin stesso.

Sembra corretto. Devi accedervi utilizzando il nome host, non il nome del container.

Sto usando il nome host, ho scritto molto e tardi, potrebbe essere stato confusionario ma sicuramente sto usando il nome host della rete interna.

Questa non è la mia area di competenza, ma ho dato un’occhiata ai post che il timer dell’argomento ha mangiato per vedere se ce ne fossero di pertinenti, e forse ho trovato questi? (Mi scuso se sono completamente fuori strada :slight_smile: :pray: )

Grazie @JammyDodger ma sfortunatamente quelle risorse non hanno aiutato.

Hanno un problema simile ma leggermente diverso al punto che non si applicano in questo caso.
Giusto per essere sicuro, ho provato quello che uno di quegli argomenti suggeriva (così come @pfaffman) e ho giocato con la variabile d’ambiente DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX.

Ho testato:

  • Commentandola
  • Aggiunta e con valore IP interno
  • Aggiunta e con valore IP esterno

Ho provato anche a cambiare il job di scrape di Prometheus per indirizzare l’installazione di Discourse come:

  • IP interno diretto
  • hostname interno docker
  • IP esterno diretto
  • nome di dominio pubblico

In ogni caso, ho provato sia http che https

In tutti i casi, ricevo un 404.
Mi aspetterei la risposta effettiva della pagina poiché la richiesta proviene da un IP interno.

1 Mi Piace

Ciò che Jay intendeva qui è che devi usare il nome host configurato (DISCOURSE_HOSTNAME nella definizione del tuo file .yml del container) anziché qualsiasi nome host che per caso risolva all’IP corretto.

Questo è deliberato, in modo che tu non possa facilmente fare il reverse proxy di un’istanza pubblica da qualsiasi luogo, e in modo che venga accettato solo il nome host configurato:

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

# quanto segue è equivalente alla creazione di un record DNS su
# try.somebogusreverseproxy.com che punta allo stesso indirizzo IP di try.discourse.org,
# e quindi alla richiesta di 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

Al contrario, se provi questo:

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

dovrebbe funzionare, ma è un hack. L’aspettativa è che tu imposti il DNS come richiesto in modo che Discourse possa essere raggiunto al suo nome host configurato in modo trasparente:

curl -I https://YOUR_CONFIGURED_HOSTNAME/metrics

Come farlo dipende molto dai tuoi requisiti, ma l’opzione più semplice è impostare un alias in /etc/hosts da dove provengono le tue richieste HTTP.

3 Mi Piace

Il prometheus exporter non è in esecuzione sulla porta 80 - ascolta sulla propria porta. Per impostazione predefinita porta 9405.

5 Mi Piace

Buona scoperta, ma se provo a puntare a quella porta specifica ricevo un messaggio “connection refused”.

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

Ho anche testato con wget dall’interno del container prometheus per essere sicuro.

/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

Sì, ho testato con wget (il container prometheus è un busybox minimale) ma sono comunque riuscito ad accedere alle metriche.

Quindi, quello che stai dicendo è che dovrei trovare un modo per far sì che il container che esegue prometheus abbia una voce in /etc/hosts che risolva… ti ho perso, scusa :slight_smile:

Quello che ho fatto è aggiungere un altro docker con semplicemente un nginx al suo interno e fornire una configurazione di forward proxy che aggiunge l’header Host alle richieste che riceve. Non espone alcuna porta, quindi può essere accessibile solo dalla rete virtuale interna.

Quindi, come cambiano le cose?

Prometheus Job:

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

docker-compose.yaml (solo la parte con il 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

Nella directory in cui si trova il tuo docker-compose.yaml, crea ./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;
    }
}

Ecco fatto:

1 Mi Piace

Per completezza, ho un repository in cui ho impostato tutto il necessario.
Ci sono alcuni valori hardcoded (come il fqdn del nostro sito web nel file di configurazione del forward proxy) che dovranno essere modificati nel caso in cui qualcun altro voglia utilizzarlo, ma forse può essere utile a qualcun altro là fuori.

Include tutto, dal docker compose alla configurazione nginx e al provisioning di grafana per risorse e dashboard.

Ciò è dovuto alla riga successiva:

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

Il binding a localhost significa che può essere connesso solo sull’IP di localhost, motivo per cui la connessione a 172.20.0.2 fallisce. Questa è una misura di sicurezza per garantire che non venga accidentalmente esposto a un pubblico molto più ampio del previsto.

Se imposti nel file di definizione del container:

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

Ascolterà su tutti gli indirizzi IP e potrai connetterti da un altro container.

Il motivo per cui questo ha funzionato:

server {
    listen 8080;

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

è che questo container nginx ora parla con prometheus tramite l’IP localhost.

Se non sei sicuro degli IP o delle porte su cui i servizi sono in ascolto, puoi usare ss -ltp o netstat -ltp (all’interno del container! i pacchetti necessari sono rispettivamente net-tools e iproute2) per esaminarli. Ad esempio, ho appena ricompilato un container con il plugin prometheus e vedo:

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

Quello è il nameserver che rifiuta la richiesta di lookup IP per vmi1187507-app. La porta 53 è DNS.

2 Mi Piace

Questo è ottimo materiale Michael, grazie per aver dedicato del tempo a scriverlo.

Lo testerò durante il fine settimana dato che ho già passato troppo tempo durante i miei giorni lavorativi questa settimana :stuck_out_tongue:

Durante i miei tentativi ho provato ad aggiungere l’IP interno da cui il container con prometheus apparirebbe come richiedente le metriche a DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX ma non ha funzionato.

Tu suggerisci DISCOURSE_PROMETHEUS_WEBSERVER_BIND. Posso chiederti da dove l’hai preso? Presumo che sia un’altra variabile d’ambiente da aggiungere al file app.yml, giusto?

Come non ha funzionato?

Se non è riuscito a connettersi, allora l’impostazione della whitelist non ha importanza poiché opera dopo la connessione L4.

C’è magia :magic_wand: nella codebase di Discourse dove se imposti DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME nell’ENV, lo sovrascriverà.

Quindi impostarlo sovrascriverà:

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

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