O job de scrape do Prometheus não consegue alcançar as métricas

Tenho uma instalação do Discourse em funcionamento (duas, na verdade, uma em staging e outra em produção, VMs diferentes e tudo mais). Estou testando no ambiente de staging. Instalação através do guia oficial.

Atualmente, há uma pilha Grafana/Prometheus/Node Exporter implantada via docker compose na mesma VM onde a instalação do Discourse já está implantada.

Aqui está o 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í o discourse especificando uma rede para que ele não fosse implantado em bridge e conectei o Prometheus na mesma rede.

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

Testei entrando no container do Prometheus e pingando o container do Discourse usando o alias da rede interna e ele conseguiu alcançá-lo.

Agora, ao configurar o job do Prometheus para que ele colete as métricas, usando um IP interno, só consigo ver server returned HTTP status 404 Not Found.

Esta é a configuração do 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 é uma substituição do nome real da VM.

De acordo com a documentação aqui, o acesso via IP interno deveria ser permitido:

Por padrão, permitimos a rota metrics para administradores e IPs privados.

Por favor, ajude-me a ver o que estou perdendo :stuck_out_tongue:

Apenas para investigar mais a fundo, tentei gerar uma chave de API do Discourse e acessá-la usando o nome de host interno, e a resposta não é um 301, o que está correto, pois todas as solicitações devem ser redirecionadas para https.

O problema, eu acho, é que as solicitações que chegam, mesmo de um IP interno, estão sendo tratadas como não autorizadas e acabam em um 404 por esse motivo.

Você tem o plugin prometheus instalado e habilitado? Ele deve permitir requisições de endereços privados, mas você poderia tentar definir a variável de ambiente para permitir o acesso do IP de onde você está puxando.

Sim, o Prometheus está na mesma VM e implantado como um contêiner Docker. Tudo funciona (tenho outros exportadores implantados também), mas por algum motivo o plugin Discourse Prometheus, mesmo estando claramente ativo e em execução, não está aceitando requisições.

Quando você diz a variável de ambiente, está falando sobre o ambiente no arquivo app.yaml do Discourse, certo?

Então, algo como isto:

env:
  DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX: 172.20.0.3

Sendo 172.20.0.3 o IP interno atual que o Prometheus terá na rede virtual Docker à qual o Discourse também está conectado.

Já tentei usar o IP externo que todos os contêineres compartilham de qualquer maneira (o IP estático da VM), mas como eles estão na mesma rede, quando um tenta acessar o outro, ele o faz através do IP interno.

Um ./launcher restart app deve ser suficiente para que as variáveis de ambiente sejam reconhecidas, certo?

Nesse caso, eu recebo:

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

vmi1187507-app é o nome da rede do contêiner em sua rede. O nome está correto, consigo pingá-lo do contêiner Prometheus em execução.
Não tenho ideia de onde vem esse 127.0.0.11:53 para ser honesto :thinking:

A mensagem é a mesma se eu comentar a variável de ambiente.

Eu pensaria que sim, mas não tenho certeza absoluta. Você pode testar de dentro do contêiner e ver se consegue curl a partir dali.

Executar um wget do contêiner prometheus retorna:

/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

Estou imaginando que seja o redirecionamento automático do contêiner nginx do Discourse?
O que acontece é que ele está encaminhando para o https do nome de domínio público, que é um IP interno do Cloudflare, e isso está, claro, dizendo a qualquer solicitação para voltar.

Agora, isso está fora de questão porque esse redirecionamento não deveria acontecer para o URL do caminho http://yourwebsite.com/metrics se vier de um IP interno, e eu esperava que o plugin cuidasse disso adicionando uma configuração nginx que adicionasse essa regra, o que aparentemente não está acontecendo?

Alguém dos desenvolvedores do Discourse pode opinar? Não quero marcar pessoas aleatoriamente e parece estranho que ninguém nunca tenha relatado esse problema antes.

Editar: Reconstruí especificando também um nome de host estático para a configuração de rede porque notei que a cada reconstrução um novo nome aleatório era atribuído ao contêiner.
Depois disso, também tentei definir o job do prometheus para acessar a versão https das métricas, mas o problema volta para o primeiro passo:

global:
  scrape_interval: 30s
  scrape_timeout: 10s

rule_files:

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

Neste ponto, isso parece um problema com o próprio plugin.

Parece certo. Você precisa acessá-lo usando o nome do host, não o nome do contêiner.

Estou usando o nome do host, escrevi muito e tarde, pode ter sido confuso, mas definitivamente usando o nome do host da rede interna.

Esta não é minha área de especialização, mas dei uma olhada nas postagens que o tópico timer comeu para ver se alguma poderia ser relevante, e possivelmente encontrei estas? (Peço desculpas se estiver muito longe :slight_smile: :pray: )

Obrigado @JammyDodger, mas infelizmente esses recursos não ajudaram.

Eles têm um problema semelhante, mas ligeiramente diferente a ponto de não se aplicarem neste caso.
Só para ter certeza, tentei o que um desses tópicos sugeriu (assim como @pfaffman) e brinquei com a variável de ambiente DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX.

Eu testei:

  • Comentando-a
  • Adicionada e com valor de IP interno
  • Adicionada e com valor de IP externo

Tentei também alterar o job de scrape do Prometheus para endereçar a instalação do Discourse como:

  • IP interno direto
  • Nome de host interno do docker
  • IP externo direto
  • Nome de domínio público

Em todos os casos, tentei tanto http quanto https.

Em todos os casos, estou recebendo um 404.
O que eu esperaria é a resposta real da página, já que a solicitação está vindo de um IP interno.

1 curtida

O que Jay quis dizer aqui é que você precisa usar o nome do host configurado (DISCOURSE_HOSTNAME na sua definição de contêiner .yml) em oposição a qualquer nome de host que por acaso resolva para o IP correto.

Isso é deliberado, para que você não possa facilmente fazer proxy reverso de uma instância pública de qualquer lugar, e para que apenas o nome do host configurado seja aceito:

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

# o seguinte é equivalente a criar um registro DNS em
# try.somebogusreverseproxy.com apontando para o mesmo endereço IP de try.discourse.org,
# e então 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

Inversamente, se você tentar isto:

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

deve funcionar, mas é um hack. A expectativa é que você configure o DNS conforme necessário para que o Discourse possa ser alcançado em seu nome de host configurado de forma transparente:

curl -I https://YOUR_CONFIGURED_HOSTNAME/metrics

Como fazer isso depende muito de seus requisitos, mas a opção mais simples é configurar um alias em /etc/hosts de onde suas requisições HTTP se originam.

3 curtidas

O exportador Prometheus não é executado na porta 80 - ele escuta em sua própria porta. Por padrão, a porta 9405.

5 curtidas

Boa descoberta, mas se eu tentar direcionar para essa porta específica, recebo uma mensagem de “conexão recusada”.

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

Testei com um wget de dentro do contêiner do prometheus também, apenas para ter certeza.

/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

Sim, testei com wget em vez disso (o contêiner do prometheus é um busybox básico), mas consegui chegar às métricas de qualquer maneira.

Então, o que você está dizendo é que devo encontrar uma maneira de fazer com que o contêiner que executa o prometheus tenha uma entrada em /etc/hosts que resolva… Perdi você aí, desculpe :slight_smile:

O que eu fiz foi adicionar mais um docker com simplesmente um nginx nele e fornecer uma configuração de proxy reverso que adiciona o cabeçalho Host às requisições que ele recebe. Ele não expõe nenhuma porta, então só pode ser acessado pela rede virtual interna de qualquer maneira.

Então, como as coisas mudam?

Prometheus Job:

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

docker-compose.yaml (apenas a parte com o 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

No diretório em que seu docker-compose.yaml está, tenha ./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;
    }
}

Aí está:

1 curtida

Apenas para registro, tenho um repositório em que configurei tudo o que é necessário.
Existem alguns valores codificados (como o FQDN do nosso site no arquivo de configuração do proxy forward) que precisarão ser alterados caso alguém mais queira usá-lo, mas talvez possa ser útil para outra pessoa.

Ele inclui tudo, desde o docker compose até a configuração do nginx e o provisionamento do grafana para recursos e dashboards.

Isso se deve à próxima linha:

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

Vincular ao localhost significa que ele só pode ser acessado no IP do localhost, razão pela qual a conexão com 172.20.0.2 falha. Esta é uma medida de segurança para garantir que ele não seja acidentalmente exposto a um público muito maior do que o pretendido.

Se você definir no arquivo de definição do contêiner:

  DISCOURSE_PROMETHEUS_WEBSERVER_BIND: '*'

Ele escutará em todos os endereços IP e você poderá se conectar a ele de outro contêiner.

A razão pela qual isso fez funcionar:

server {
    listen 8080;

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

é porque este contêiner nginx agora está falando com o prometheus sobre o IP localhost.

Se você não tiver certeza dos IPs ou portas em que os serviços estão escutando, você pode usar ss -ltp ou netstat -ltp (dentro do contêiner! os pacotes necessários são net-tools e iproute2, respectivamente) para examiná-los. Por exemplo, acabei de reconstruir um contêiner com o plugin prometheus e vejo:

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

Esse é o servidor de nomes rejeitando a solicitação de consulta de IP para vmi1187507-app. A porta 53 é DNS.

2 curtidas

Isso é ótimo, Michael, obrigado por reservar um tempo para escrever isso.

Vou testar no fim de semana, pois já gastei muito tempo durante meus dias de trabalho esta semana :stuck_out_tongue:

Durante minhas tentativas, tentei adicionar o IP interno de onde o contêiner com o prometheus apareceria como solicitando as métricas a DISCOURSE_PROMETHEUS_TRUSTED_IP_ALLOWLIST_REGEX, mas não funcionou.

Você está sugerindo DISCOURSE_PROMETHEUS_WEBSERVER_BIND. Posso perguntar de onde você o obteve? Estou assumindo que é outra variável de ambiente para adicionar ao arquivo app.yml, certo?

Como não funcionou?

Se falhou ao conectar, então a configuração da lista de permissões não importa, pois ela opera após a conexão L4.

Existe mágica :magic_wand: na base de código do Discourse onde, se você definir DISCOURSE_SITE_OR_GLOBAL_SETTING_NAME no ENV, ele substituirá.

Portanto, definir isso substituirá:

GlobalSetting.add_default :prometheus_webserver_bind, "localhost"

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