El trabajo de scrapeo de Prometheus no puede alcanzar las métricas

Tengo una instalación de Discourse en funcionamiento (dos en realidad, una en staging y otra en producción, máquinas virtuales diferentes y todo). Estoy probando en el entorno de staging. Instalación a través de la guía oficial.

Actualmente, hay una pila de Grafana/Prometheus/Node Exporter desplegada a través de docker compose en la misma VM donde ya está desplegada la instalación de Discourse.

Aquí está el 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

Reconstruí el discourse especificando una red para que no se desplegara en bridge y conecté Prometheus a la misma red.

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

Probé entrando en el contenedor de Prometheus y haciendo ping al contenedor de Discourse usando el alias de red interno y pude alcanzarlo.

Ahora, al configurar el trabajo de Prometheus para que extraiga las métricas, usando una IP interna, solo veo server returned HTTP status 404 Not Found.

Esta es la configuración de 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 es un reemplazo del nombre real de la VM.

Según la documentación aquí, el acceso a través de IP interna debería estar permitido:

Por defecto permitimos la ruta metrics a administradores y a IPs privadas.

Por favor, ayúdenme a ver qué me estoy perdiendo :stuck_out_tongue:

Para investigar más, intenté generar una clave API de Discourse y acceder a ella utilizando el nombre de host interno, y la respuesta no es un 301, lo cual es correcto porque cada solicitud debe ser redirigida a https.

El problema, creo, es que las solicitudes entrantes, incluso si provienen de una IP interna, se tratan como no autorizadas y terminan en un 404 por esa razón.

¿Tienes instalado y habilitado el plugin de Prometheus? Debería permitir solicitudes de direcciones privadas, pero podrías intentar configurar la variable de entorno para permitir el acceso desde la IP desde la que estás extrayendo.

Sí, Prometheus está en la misma VM y se implementa como un contenedor de Docker. Todo funciona (tengo otros exportadores implementados también) pero por alguna razón, el plugin Discourse Prometheus, a pesar de estar claramente en funcionamiento, no acepta solicitudes.

Cuando dices la variable de entorno, ¿te refieres al entorno en el archivo app.yaml de Discourse?

Entonces, algo como esto:

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

siendo 172.20.0.3 la IP interna actual que tendrá Prometheus en la red virtual de Docker a la que también está conectado Discourse.

Ya intenté usar la IP externa que todos los contenedores comparten de todos modos (la IP estática de la VM), pero como están en la misma red, cuando uno intenta acceder al otro, lo hace a través de la IP interna.

Un ./launcher restart app debería ser suficiente para que las variables de entorno se capturen, ¿verdad?

En ese caso, obtengo:

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

vmi1187507-app es el nombre de la red del contenedor en su red. El nombre es correcto, puedo hacer ping desde el contenedor de Prometheus en ejecución.
No tengo idea de dónde viene ese 127.0.0.11:53 para ser honesto :thinking:

El mensaje es el mismo si comento la variable de entorno.

Creo que sí, pero no estoy completamente seguro. Puedes probar desde dentro del contenedor y ver si puedes hacer curl desde allí.

Ejecutar un wget desde el contenedor de prometheus devuelve:

/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

¿Supongo que es la redirección automática desde el contenedor nginx de Discourse?
Lo que sucede es que está reenviando a https del nombre de dominio público, que es una IP interna de Cloudflare y eso, por supuesto, le dice a cualquier solicitud que regrese.

Ahora, eso está fuera de lugar porque esta redirección no debería ocurrir para la URL de ruta http://yourwebsite.com/metrics si proviene de una IP interna y esperaba que el plugin se encargara de eso agregando una configuración de nginx que agregue esta regla, lo cual aparentemente no está sucediendo.

¿Puede intervenir algún desarrollador de Discourse? No quiero mencionar a la gente al azar y se siente raro que nadie haya informado este problema antes.

Editar: Volví a compilar especificando también un nombre de host estático para la configuración de red porque noté que cada reconstrucción asignaba uno nuevo y aleatorio al contenedor.
Después de eso, también intenté configurar el trabajo de prometheus para acceder a la versión https de las métricas, pero el problema vuelve al primer paso:

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

scrape_configs:
# otros trabajos
# [...]
  - 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

En este punto, esto parece un problema con el propio plugin.

Suena bien. Necesitas acceder a él usando el nombre de host, no el nombre del contenedor.

Estoy usando el nombre de host, escribí mucho y tarde, podría haber sido confuso, pero definitivamente estoy usando el nombre de host de la red interna.

Esta no es mi área de especialización, pero he buscado en las publicaciones que el temporizador del tema se comió para ver si alguna podría ser relevante, y ¿posiblemente encontré estas? (Mis disculpas si estoy muy equivocado :slight_smile: :pray: )

Gracias @JammyDodger, pero desafortunadamente esos recursos no ayudaron.

Tienen un problema similar pero ligeramente diferente al punto de que no se aplican en este caso.
Solo para estar seguro, probé lo que sugería uno de esos temas (así como @pfaffman) y jugué con la variable de entorno DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX.

Probé:

  • Comentándola
  • Añadida y con valor de IP interna
  • Añadida y con valor de IP externa

También intenté cambiar el trabajo de scraping de Prometheus para abordar la instalación de Discourse como:

  • IP interna directa
  • Nombre de host interno de docker
  • IP externa directa
  • Nombre de dominio público

En cada caso, probé tanto http como https.

En todos los casos, obtengo un 404.
Lo que esperaría es la respuesta real de la página, ya que la solicitud proviene de una IP interna.

1 me gusta

Lo que Jay quiso decir aquí es que necesitas usar el nombre de host configurado (DISCOURSE_HOSTNAME en la definición de tu archivo .yml del contenedor) en lugar de cualquier nombre de host que resuelva a la IP correcta.

Esto es deliberado, para que no puedas hacer un proxy inverso trivial de una instancia pública desde cualquier lugar, y para que solo se acepte el nombre de host configurado:

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

# lo siguiente es equivalente a crear un registro DNS en
# try.somebogusreverseproxy.com apuntando a la misma dirección IP que try.discourse.org,
# y luego solicitar 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

Por el contrario, si intentas esto:

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

Debería funcionar, pero es una solución temporal. La expectativa es que configures el DNS según sea necesario para que Discourse sea accesible en su nombre de host configurado de forma transparente:

curl -I https://YOUR_CONFIGURED_HOSTNAME/metrics

Cómo hacer eso depende en gran medida de tus requisitos, pero la opción más simple es configurar un alias en /etc/hosts desde donde se originan tus solicitudes HTTP.

3 Me gusta

El exportador de Prometheus no se ejecuta en el puerto 80; escucha en su propio puerto. Por defecto, el puerto 9405.

5 Me gusta

Buen hallazgo, pero si intento apuntar a ese puerto específico, obtengo un mensaje de “conexión rechazada”.

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

También probé con wget desde dentro del contenedor de prometheus para estar seguro.

/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í, lo probé con wget (el contenedor de prometheus es un busybox básico) pero de todos modos llegué a las métricas.

Entonces, ¿lo que dices es que debería encontrar una manera de que el contenedor que ejecuta prometheus tenga una entrada en /etc/hosts que resuelva… me he perdido ahí, lo siento :slight_smile:

Lo que hice fue añadir otro docker con simplemente un nginx y proporcionar una configuración de proxy inverso que añade la cabecera Host a las solicitudes que recibe. No expone ningún puerto, por lo que solo se puede acceder a él a través de la red virtual interna de todos modos.

Entonces, ¿cómo cambian las cosas?

Prometheus Job:

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

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

En el directorio en el que se encuentra tu docker-compose.yaml, ten ./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;
    }
}

Ahí lo tienes:

1 me gusta

Solo por si acaso, tengo un repositorio en el que he configurado todo lo necesario.
Hay algunos valores codificados (como el fqdn de nuestro sitio web en el archivo de configuración del proxy forward) que deberán cambiarse en caso de que alguien más quiera usarlo, pero tal vez pueda ser útil para alguien más.

Incluye todo, desde el docker compose hasta la configuración de nginx y el aprovisionamiento de grafana para recursos y paneles.

Eso se debe a la siguiente línea:

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

Vincular a localhost significa que solo se puede conectar en la IP de localhost, por eso la conexión a 172.20.0.2 falla. Esta es una medida de seguridad para garantizar que no se exponga accidentalmente a una audiencia mucho más amplia de la prevista.

Si configuras en el archivo de definición del contenedor:

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

Escuchará en todas las direcciones IP y podrás conectarte desde otro contenedor.
La razón por la que esto funcionó:

server {
    listen 8080;

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

es porque este contenedor nginx ahora está hablando con prometheus a través de la IP localhost.

Si no estás seguro de las IP o puertos en los que los servicios están escuchando, puedes usar ss -ltp o netstat -ltp (dentro del contenedor. Los paquetes necesarios son net-tools e iproute2 respectivamente) para verlos. Por ejemplo, acabo de reconstruir un contenedor con el plugin de prometheus y veo:

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

Ese es el servidor de nombres rechazando la solicitud de búsqueda de IP para vmi1187507-app. El puerto 53 es DNS.

2 Me gusta

Esto es genial Michael, gracias por tomarte el tiempo de escribirlo.

Lo probaré durante el fin de semana, ya que ya he pasado demasiado tiempo durante mis días de trabajo esta semana :stuck_out_tongue:

Durante mis intentos, probé a añadir la IP interna desde la que el contenedor con prometheus aparecería solicitando las métricas a DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX, pero no funcionó.

Estás sugiriendo DISCOURSE_PROMETHEUS_WEBSERVER_BIND. ¿Puedo preguntar de dónde lo sacaste? Supongo que es otra variable de entorno que añadir al archivo app.yml, ¿verdad?

¿Cómo que no funcionó?

Si falló al conectarse, entonces la configuración de la lista de permitidos no importa ya que opera después de la conexión L4.

Hay magia :magic_wand: en la base de código de Discourse donde si estableces DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME en el ENV, lo anulará.

Por lo tanto, establecer eso anulará:

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

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