Skip to content

Monitoramento de Containers com Prometheus + Grafana

Servidor: Oracle Cloud VPS — Ubuntu Server, 4 vCPU, 24 GB RAM
Domínio: ruiogawa.net
Dashboard: https://monitor.ruiogawa.net
Stack: cAdvisor + Node Exporter + Prometheus + Grafana
Proxy: Nginx Proxy Manager (NPM)


Arquitetura

Internet
    │
    ▼
Nginx Proxy Manager (80/443)
    │
    ▼ https://monitor.ruiogawa.net
Grafana (172.17.0.1:3002)
    │
    ▼ http://prometheus:9090
Prometheus
    ├── cAdvisor:8080   → métricas por container (CPU, RAM, rede, I/O)
    └── node-exporter:9100 → métricas do host (disco, sistema)

Todos os serviços se comunicam via rede interna Docker monitoring. Apenas o Grafana expõe porta para o host, vinculada ao IP da bridge Docker (172.17.0.1) para que o NPM consiga alcançá-lo.


Estrutura de Arquivos

/opt/monitoring/
├── docker-compose.yml
├── prometheus/
│   └── prometheus.yml
└── grafana/
    ├── provisioning/
    │   ├── datasources/
    │   │   └── prometheus.yml
    │   └── dashboards/
    │       └── dashboards.yml
    └── dashboards/
        ├── node-exporter.json      # Docker Host
        ├── cadvisor.json           # cAdvisor Docker Insights
        └── docker_containers.json  # Docker Containers (dockprom)

Instalação

1. Criar estrutura de diretórios

sudo mkdir -p /opt/monitoring/prometheus
sudo mkdir -p /opt/monitoring/grafana/provisioning/datasources
sudo mkdir -p /opt/monitoring/grafana/provisioning/dashboards
sudo mkdir -p /opt/monitoring/grafana/dashboards

2. Configurar o Prometheus

sudo nano /opt/monitoring/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'cadvisor'
    scrape_interval: 5s
    static_configs:
      - targets: ['cadvisor:8080']

3. Configurar o provisionamento do Grafana

Datasource:

sudo nano /opt/monitoring/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false

Dashboards:

sudo nano /opt/monitoring/grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1

providers:
  - name: 'default'
    orgId: 1
    folder: ''
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    options:
      path: /var/lib/grafana/dashboards

4. Baixar os dashboards

# Node Exporter Full (métricas do host)
sudo curl -s https://grafana.com/api/dashboards/1860/revisions/latest/download \
  -o /opt/monitoring/grafana/dashboards/node-exporter.json

# Docker Containers (dockprom - métricas por container)
sudo curl -s https://raw.githubusercontent.com/stefanprodan/dockprom/master/grafana/provisioning/dashboards/docker_containers.json \
  -o /opt/monitoring/grafana/dashboards/docker_containers.json

Corrigir datasource no dashboard de containers:

Obtenha o UID do datasource após subir o Grafana:

curl -s -u admin:SENHA http://172.17.0.1:3002/api/datasources | python3 -m json.tool | grep -E '"uid"|"name"'

Substitua no JSON:

sudo sed -i 's/"datasource": "Prometheus"/"datasource": {"type": "prometheus", "uid": "SEU_UID"}/g' \
  /opt/monitoring/grafana/dashboards/docker_containers.json

5. Docker Compose

sudo nano /opt/monitoring/docker-compose.yml
services:

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    restart: unless-stopped
    privileged: true
    devices:
      - /dev/kmsg
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    networks:
      - monitoring

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - /opt/monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    user: "root"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=SENHA_AQUI
      - GF_SERVER_ROOT_URL=https://monitor.ruiogawa.net
      - GF_SERVER_DOMAIN=monitor.ruiogawa.net
      - GF_SMTP_ENABLED=true
      - GF_SMTP_HOST=smtp-relay.brevo.com:587
      - GF_SMTP_USER=USUARIO_BREVO
      - GF_SMTP_PASSWORD=API_KEY_BREVO
      - GF_SMTP_FROM_ADDRESS=monitor@ruiogawa.net
      - GF_SMTP_FROM_NAME=Grafana Monitor
    volumes:
      - grafana_data:/var/lib/grafana
      - /opt/monitoring/grafana/provisioning:/etc/grafana/provisioning
      - /opt/monitoring/grafana/dashboards:/var/lib/grafana/dashboards
    ports:
      - "172.17.0.1:3002:3000"
    networks:
      - monitoring

networks:
  monitoring:
    driver: bridge

volumes:
  prometheus_data:
  grafana_data:

Observações importantes: - user: "root" — necessário para evitar erro de permissão no plugin elasticsearch - privileged: true no cAdvisor — necessário para ler métricas de todos os containers - pid: host no Node Exporter — necessário para ver processos reais do host - Grafana vinculado a 172.17.0.1:3002 — acessível pelo NPM mas não pela internet diretamente - Retenção de 30 dias no Prometheus

6. Subir a stack

cd /opt/monitoring
sudo docker compose up -d
sudo docker compose ps

Configuração do Nginx Proxy Manager

  1. Adicione um novo Proxy Host:
  2. Domain Names: monitor.ruiogawa.net
  3. Scheme: http
  4. Forward Hostname/IP: 172.17.0.1
  5. Forward Port: 3002
  6. Websockets Support

  7. Na aba SSL:

  8. SSL Certificate: Let's Encrypt
  9. ✅ Force SSL
  10. ✅ HTTP/2 Support

  11. Na aba Advanced, adicione para evitar timeouts:

proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

Por que 172.17.0.1 e não 127.0.0.1?
O NPM roda dentro de um container. Para ele, 127.0.0.1 é o próprio container, não o host. O IP 172.17.0.1 é o gateway da bridge Docker, acessível a todos os containers.


Portas

Serviço Porta Binding Exposto externamente
Grafana 3002 172.17.0.1 ❌ — só via NPM
Prometheus 9090 rede Docker interna
cAdvisor 8080 rede Docker interna
Node Exporter 9100 rede Docker interna
NPM 80/443 0.0.0.0

Nenhuma porta nova precisa ser aberta no painel da Oracle.


Dashboards

Os dashboards são provisionados automaticamente via arquivos JSON ao iniciar o Grafana. Não é necessário importá-los manualmente pela UI.

Dashboard Fonte Métricas
Docker Host dockprom (stefanprodan) CPU, RAM, disco, rede, processos do host
Docker Containers dockprom (stefanprodan) CPU, RAM, I/O e rede por container

Para adicionar novos dashboards: baixe o JSON em /opt/monitoring/grafana/dashboards/ e reinicie o Grafana:

cd /opt/monitoring
sudo docker compose restart grafana

Alertas por E-mail

Configuração SMTP (Brevo)

As variáveis de ambiente no docker-compose.yml já configuram o SMTP. Para atualizar a API key:

sudo nano /opt/monitoring/docker-compose.yml
# Atualize GF_SMTP_PASSWORD
cd /opt/monitoring
sudo docker compose up -d --force-recreate grafana

Contact Point

Configurado em Alerting → Notification configuration → Contact points: - Name: email-ruiogawa - Integration: Email - Definido como padrão em Notification policies → Default policy

Regras de Alerta

Todas em Alerting → Alert rules → Infrastructure/sistema, avaliadas a cada 1 minuto:

Alerta Query Condição Pending
RAM alta (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 80% 1m
Container down count(container_memory_usage_bytes{image!=""}) < 1 None
Disco cheio (1 - (node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"})) * 100 > 85% 5m
CPU alta 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85% 5m
Rede alta sum(rate(node_network_receive_bytes_total{device!="lo"}[5m]) + rate(node_network_transmit_bytes_total{device!="lo"}[5m])) / 1024 / 1024 > 100 MB/s 5m

Acesso Anônimo (sem login)

Para permitir visualização dos dashboards sem autenticação, adicione as variáveis de ambiente no docker-compose.yml:

sudo nano /opt/monitoring/docker-compose.yml
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_NAME=ruiogawa.net
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer

Atenção: O valor de GF_AUTH_ANONYMOUS_ORG_NAME deve ser exatamente igual ao nome da organização configurado no Grafana em Administration → Default preferences → Nome da organização.

Reinicie o Grafana:

cd /opt/monitoring
sudo docker compose up -d --force-recreate grafana

Observação: Com o acesso anônimo habilitado, qualquer pessoa com acesso a monitor.ruiogawa.net consegue visualizar os dashboards, mas não pode editar, criar alertas ou acessar configurações. O papel Viewer é somente leitura.

Dashboard padrão para acesso anônimo

Para definir qual dashboard carrega ao acessar monitor.ruiogawa.net sem login, configure pela UI com o usuário admin:

  1. Administration → Default preferences
  2. Em Painel de controle inicial selecione o dashboard desejado (ex: /Docker Containers)
  3. Clique em Salvar preferências

A partir daí o dashboard selecionado carrega automaticamente para visitantes não autenticados.


Manutenção

Reiniciar a stack

cd /opt/monitoring
sudo docker compose restart

Ver logs

cd /opt/monitoring
sudo docker compose logs grafana --tail=50
sudo docker compose logs prometheus --tail=50

Verificar targets do Prometheus

docker exec prometheus wget -qO- http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -E '"health"|"job"|"instance"'

Atualizar imagens

cd /opt/monitoring
sudo docker compose pull
sudo docker compose up -d

Corrigir permissões do volume Grafana

Se o Grafana apresentar erros de permissão:

cd /opt/monitoring
sudo docker compose down
sudo docker run --rm -v monitoring_grafana_data:/var/lib/grafana busybox chown -R 472:472 /var/lib/grafana
sudo docker compose up -d

Problemas Conhecidos

Porta 3001 travada após restart

Um processo rootlesskit pode travar a porta 3001. Por isso a stack usa a porta 3002. Se necessário, identifique e mate o processo:

sudo ss -tlnp | grep 3001
sudo kill -9 PID

502 Bad Gateway no NPM

Causas comuns: 1. Grafana ainda inicializando — aguarde 30 segundos 2. Sessão expirada — abra aba anônima e faça login novamente 3. Timeout — verifique as configurações de timeout no NPM (Advanced)

Plugin elasticsearch com erro de permissão

O Grafana tenta atualizar o plugin elasticsearch e falha sem permissão. Solução: user: "root" no docker-compose.yml (já aplicado).

Storage Load exibindo N/A

O dashboard do dockprom filtra filesystem por aufs, que não existe em sistemas modernos com overlay2. O servidor usa ext4. Corrija com:

sudo sed -i 's/fstype=\\"aufs\\"/fstype=\\"ext4\\"/g' /opt/monitoring/grafana/dashboards/docker_containers.json

cd /opt/monitoring
sudo docker compose restart grafana