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: trueno cAdvisor — necessário para ler métricas de todos os containers -pid: hostno Node Exporter — necessário para ver processos reais do host - Grafana vinculado a172.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
- Adicione um novo Proxy Host:
- Domain Names:
monitor.ruiogawa.net - Scheme:
http - Forward Hostname/IP:
172.17.0.1 - Forward Port:
3002 -
✅ Websockets Support
-
Na aba SSL:
- SSL Certificate: Let's Encrypt
- ✅ Force SSL
-
✅ HTTP/2 Support
-
Na aba Advanced, adicione para evitar timeouts:
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
Por que
172.17.0.1e não127.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 IP172.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_NAMEdeve 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.netconsegue visualizar os dashboards, mas não pode editar, criar alertas ou acessar configurações. O papelVieweré 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:
- Administration → Default preferences
- Em Painel de controle inicial selecione o dashboard desejado (ex:
/Docker Containers) - 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