Le job de scraping Prometheus ne peut pas atteindre les métriques

J’ai une installation Discourse en cours d’exécution (deux en fait, une en pré-production et une en production, des VM différentes et tout). Je teste sur l’environnement de pré-production. Installation via le guide officiel.

Actuellement, une pile Grafana/Prometheus/Node Exporter est déployée via docker compose sur la même VM où l’installation Discourse est déjà déployée.

Voici le 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: [OMIS]
            GF_SECURITY_ADMIN_PASSWORD: [OMIS]
            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

J’ai reconstruit le discourse en spécifiant un réseau afin qu’il ne se déploie pas sur bridge et j’ai connecté Prometheus sur le même réseau.

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

J’ai testé en entrant dans le conteneur Prometheus et en pingant le conteneur discourse en utilisant l’alias du réseau interne et il a pu l’atteindre.

Maintenant, lors de la configuration du job Prometheus pour qu’il scrape les métriques, en utilisant une adresse IP interne, je ne vois que server returned HTTP status 404 Not Found.

Voici la configuration 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 est un remplacement du nom réel de la VM.

Selon la documentation ici, l’accès via une adresse IP interne devrait être autorisé :

Par défaut, nous autorisons la route metrics aux administrateurs et aux adresses IP privées.

Aidez-moi à voir ce qui me manque :stuck_out_tongue:

Juste pour approfondir un peu, j’ai essayé de générer une clé API à partir de Discourse et d’y accéder en utilisant le nom d’hôte interne, et la réponse n’est pas un 301, ce qui est correct car chaque requête est censée être redirigée vers https.

Le problème, je pense, est que les requêtes entrantes, même provenant d’une IP interne, sont traitées comme non autorisées et aboutissent à un 404 pour cette raison.

Avez-vous bien le plugin prometheus installé et activé ? Il devrait autoriser les requêtes provenant d’adresses privées, mais vous pourriez essayer de définir la variable d’environnement pour autoriser l’accès depuis l’IP que vous utilisez.

Oui, Prometheus est sur la même VM et déployé comme un conteneur Docker. Tout fonctionne (j’ai d’autres exportateurs déployés aussi) mais pour une raison quelconque, le plugin Discourse Prometheus, même s’il est clairement opérationnel, n’accepte pas les requêtes.

Quand vous parlez de la variable ENV, vous parlez de l’environnement dans le fichier app.yaml de Discourse, n’est-ce pas ?

Donc, quelque chose comme ceci :

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

172.20.0.3 étant l’adresse IP interne actuelle que Prometheus aura sur le réseau virtuel Docker auquel Discourse est également attaché.

J’ai déjà essayé d’utiliser l’adresse IP externe que tous les conteneurs partagent de toute façon (l’adresse IP statique de la VM) mais comme ils sont sur le même réseau, quand l’un essaie d’accéder à l’autre, il le fait via l’adresse IP interne.

Un ./launcher restart app devrait suffire pour que les variables d’environnement soient prises en compte, n’est-ce pas ?

Dans ce cas, j’obtiens :

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

vmi1187507-app est le nom du réseau du conteneur dans son réseau. Le nom est correct, je peux le pinger depuis le conteneur Prometheus en cours d’exécution.
Je n’ai aucune idée d’où vient ce 127.0.0.11:53 pour être honnête :thinking:

Le message est le même si je commente la variable d’environnement.

Je pense que oui, mais je ne suis pas entièrement sûr. Vous pouvez tester depuis l’intérieur du conteneur et voir si vous pouvez y faire un curl.

L’exécution d’un wget depuis le conteneur prometheus renvoie :

/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

Je suppose qu’il s’agit de la redirection automatique du conteneur nginx de Discourse ?
Ce qui se passe, c’est qu’il redirige vers le https du nom de domaine public qui est une adresse IP interne de Cloudflare et qui renvoie toute requête.

Maintenant, ce n’est pas le problème, car cette redirection ne devrait pas se produire pour l’URL du chemin http://yourwebsite.com/metrics si elle provient d’une adresse IP interne et je m’attendais à ce que le plugin s’en charge en ajoutant une configuration nginx qui ajoute cette règle, ce qui ne se produit apparemment pas ?

Quelqu’un des développeurs de Discourse peut-il intervenir ? Je ne veux pas pinguer des personnes au hasard et il est étrange que personne n’ait jamais signalé ce problème auparavant.

Edit : J’ai reconstruit en spécifiant également un nom d’hôte statique pour la configuration réseau car j’ai remarqué qu’à chaque reconstruction, un nouveau nom aléatoire était attribué au conteneur.
Après cela, j’ai également essayé de configurer le job prometheus pour accéder à la version https des métriques, mais le problème revient à la première étape :

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

scrape_configs:
# autres 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

À ce stade, cela ressemble à un problème avec le plugin lui-même.

Ça semble juste. Vous devez y accéder en utilisant le nom d’hôte, pas le nom du conteneur.

J’utilise le nom d’hôte, j’ai beaucoup écrit et tard, cela a peut-être été confus mais j’utilise définitivement le nom d’hôte du réseau interne.

Ce n’est pas mon domaine d’expertise, mais j’ai fouillé dans les messages que le sujet du minuteur a mangés pour voir si certains pouvaient être pertinents, et j’en ai peut-être trouvé ceci ? (Mes excuses si je suis complètement à côté de la plaque :slight_smile: :pray: )

Merci @JammyDodger, mais malheureusement ces ressources n’ont pas aidé.

Ils ont un problème similaire mais légèrement différent au point qu’il ne s’applique pas dans ce cas.
Pour être sûr, j’ai essayé ce qu’un de ces sujets suggérait (ainsi que @pfaffman) et j’ai joué avec la variable d’environnement DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX.

J’ai testé :

  • En la commentant
  • Ajoutée et avec une valeur d’IP interne
  • Ajoutée et avec une valeur d’IP externe

J’ai aussi essayé de changer le job de scraping Prometheus pour adresser l’installation Discourse comme :

  • IP interne directe
  • Nom d’hôte interne Docker
  • IP externe directe
  • Nom de domaine public

Dans tous les cas, j’ai essayé http et https.

Dans tous les cas, j’obtiens une 404.
Ce à quoi je m’attendrais, c’est la réponse de la page réelle car la requête provient d’une IP interne.

1 « J'aime »

Ce que Jay voulait dire ici, c’est que vous devez utiliser le nom d’hôte configuré (DISCOURSE_HOSTNAME dans la définition de votre conteneur .yml) par opposition à tout nom d’hôte qui se résoudrait à la bonne adresse IP.

Ceci est délibéré, afin que vous ne puissiez pas facilement faire de proxy inverse d’une instance publique à partir de n’importe où, et que seul le nom d’hôte configuré soit accepté :

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

# ce qui suit est équivalent à la création d'un enregistrement DNS à
# try.somebogusreverseproxy.com pointant vers la même adresse IP que try.discourse.org,
# puis à la demande de 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

Inversement, si vous essayez ceci :

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

cela devrait fonctionner, mais c’est une solution de contournement. L’attente est que vous configuriez le DNS comme requis afin que Discourse puisse être atteint à son nom d’hôte configuré de manière transparente :

curl -I https://VOTRE_NOM_HOTE_CONFIGURE/metrics

Comment faire cela dépend grandement de vos besoins, mais l’option la plus simple est de configurer un alias dans /etc/hosts à partir d’où vos requêtes HTTP proviennent.

3 « J'aime »

L’exportateur Prometheus ne s’exécute pas sur le port 80 - il écoute sur son propre port. Par défaut port 9405.

5 « J'aime »

Bonne trouvaille, mais si j’essaie de cibler ce port spécifique, j’obtiens un message « connexion refusée ».

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

J’ai également testé avec un wget depuis l’intérieur du conteneur prometheus pour être sûr.

/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

Oui, j’ai testé avec wget à la place (le conteneur prometheus est un busybox très basique) mais j’ai quand même réussi à accéder aux métriques.

Alors, ce que vous dites, c’est que je devrais trouver un moyen d’avoir le conteneur exécutant prometheus avec une entrée dans /etc/hosts qui résout… Je vous ai perdu, désolé :slight_smile:

Ce que j’ai fait, c’est ajouter un autre docker avec simplement un nginx dedans et fournir une configuration de proxy forward qui ajoute l’en-tête Host aux requêtes qu’il reçoit. Il n’expose aucun port, il ne peut donc être accédé que par le réseau virtuel interne de toute façon.

Alors, comment les choses changent-elles ?

Job Prometheus :

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

docker-compose.yaml (juste la partie avec le 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

Dans le répertoire où se trouve votre docker-compose.yaml, créez ./discourse_forward_proxy/discourse_forward_proxy.conf

server {
    listen 8080;

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

Voilà :

1 « J'aime »

Pour mémoire, j’ai un dépôt dans lequel j’ai mis en place tout le nécessaire.
Il y a certaines valeurs codées en dur (comme le FQDN de notre site Web dans le fichier de configuration du proxy forward) qui devront être modifiées si quelqu’un d’autre souhaite l’utiliser, mais peut-être que cela peut être utile à quelqu’un d’autre.

Il comprend tout, du docker compose à la configuration nginx et au provisionnement grafana pour les ressources et les tableaux de bord.

C’est dû à la ligne suivante :

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

Lier à localhost signifie qu’il ne peut être connecté que sur l’IP localhost, c’est pourquoi la connexion à 172.20.0.2 échoue. C’est une mesure de sécurité pour s’assurer qu’il n’est pas accidentellement exposé à un public beaucoup plus large que prévu.

Si vous définissez dans le fichier de définition du conteneur :

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

Il écoutera sur toutes les adresses IP et vous pourrez vous y connecter depuis un autre conteneur.

La raison pour laquelle cela a fonctionné :

server {
    listen 8080;

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

est que ce conteneur nginx communique maintenant avec prometheus sur l’IP localhost.

Si vous n’êtes pas sûr des adresses IP ou des ports sur lesquels les services écoutent, vous pouvez utiliser ss -ltp ou netstat -ltp (à l’intérieur du conteneur ! les paquets nécessaires sont respectivement net-tools et iproute2) pour les examiner. Par exemple, je viens de reconstruire un conteneur avec le plugin prometheus et je vois :

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

C’est le serveur de noms qui rejette la requête de recherche d’IP pour vmi1187507-app. Le port 53 est le DNS.

2 « J'aime »

C’est excellent Michael, merci d’avoir pris le temps de l’écrire.

Je vais tester ça ce week-end car j’ai déjà passé trop de temps pendant mes journées de travail cette semaine :stuck_out_tongue:

Lors de mes tentatives, j’ai essayé d’ajouter l’IP interne depuis laquelle le conteneur avec prometheus apparaîtrait comme demandant les métriques à DIS="../../DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX " mais cela n’a pas fonctionné.

Vous suggérez DIS="../../DISCOURSE_PROMETHEUS_WEBSERVER_BIND". Puis-je demander d’où vous l’avez obtenu ? Je suppose que c’est une autre variable d’environnement à ajouter au fichier app.yml, n’est-ce pas ?

Comment ça n’a pas fonctionné ?

Si la connexion a échoué, alors le paramètre de la liste d’autorisation n’a pas d’importance puisqu’il s’exécute après la connexion L4.

Il y a de la magie :magic_wand: dans la base de code de Discourse où si vous définissez DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME dans l’ENV, cela le remplacera.

Donc, le définir remplacera :

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

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