Monitoramento de Servidores Linux com Prometheus e Grafana: passo a passo para ambientes DevOps

Montar uma stack de observabilidade útil em Linux não começa no painel bonito; começa na coleta consistente, na modelagem correta das métricas e na automação da configuração. Prometheus e Grafana formam um par muito forte para esse cenário porque encaixam bem em ambientes com systemd, containers, VMs, bare metal e até clusters híbridos. O ponto central não é “ver gráficos”, e sim transformar sinais do host em alertas acionáveis, com baixo acoplamento e rastreabilidade suficiente para operar produção sem depender de login manual em servidor.

O fluxo básico é direto: o node_exporter expõe métricas do host Linux em /metrics; o Prometheus faz o scrape, armazena séries temporais e avalia regras; o Grafana consulta o Prometheus e renderiza painéis; e, para alertas, o Alertmanager recebe as notificações e aplica roteamento. Quando essa cadeia está bem montada, você consegue enxergar CPU, memória, I/O de disco, filesystem, rede, load average, processos e eventos do kernel com granularidade suficiente para correlacionar incidentes com deploys, mudanças de configuração e picos de tráfego.

Arquitetura que faz sentido para Linux de verdade

Evite tratar monitoramento como um serviço isolado. Em Linux e DevOps, o desenho precisa acompanhar a topologia real: servidores físicos, instâncias em cloud, nodes de Kubernetes, VMs de virtualização e serviços rodando com systemd. O arranjo mais simples e funcional usa um servidor central para Prometheus, Grafana e Alertmanager, e agentes leves nos hosts: node_exporter, process_exporter quando o foco é processo, e eventualmente blackbox_exporter para checagens sintéticas.

Uma arquitetura mínima fica assim:

Linux hosts --- node_exporter ---> Prometheus ---> Grafana
                  |                    |
                  |                    +--> Alert rules --> Alertmanager
                  +------------------------------ scrape ------------------->

Em ambientes maiores, vale separar responsabilidades. Prometheus pode rodar com armazenamento local em SSD e retenção controlada; Grafana pode ser colocado atrás de um reverse proxy; Alertmanager deve ser configurado com rotas por severidade e por time; e o node_exporter nunca precisa de acesso de escrita no host. Isso reduz superfície de ataque e facilita atualização sem impacto no workload monitorado.

Preparando o host Linux com o mínimo de ruído

Antes de instalar qualquer coisa, confirme versão do kernel, distribuição, systemd e portas em uso. Em RHEL, Rocky, Alma, Debian e Ubuntu, o fluxo é parecido. O node_exporter escuta na porta 9100/TCP por padrão, então é bom validar firewall e segmentação de rede antes de expor isso para o Prometheus.

uname -r
cat /etc/os-release
systemctl --version
ss -lntp | grep 9100 || true

Se o host usa firewalld, libere apenas a sub-rede do servidor Prometheus. Em nftables, faça o mesmo com regra explícita. Não deixe a porta aberta para qualquer origem, porque métricas também contam muita coisa sobre o sistema e podem ser usadas para enumerar serviços, discos e nomes de interfaces.

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.10.0.0/24" port protocol="tcp" port="9100" accept'
sudo firewall-cmd --reload

Node Exporter via systemd: instalação sem improviso

O caminho mais limpo em servidores Linux é usar binário estático e unit file do systemd. Isso evita dependências desnecessárias e simplifica rollback. Abaixo está um exemplo para arquitetura amd64; adapte a versão conforme a release disponível no momento do deploy. O importante é manter a instalação reproduzível.

USER=node_exporter
VERSION=1.8.1
cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/node_exporter-${VERSION}.linux-amd64.tar.gz
curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/sha256sums.txt
grep "node_exporter-${VERSION}.linux-amd64.tar.gz" sha256sums.txt | sha256sum -c -

tar -xzf node_exporter-${VERSION}.linux-amd64.tar.gz
sudo install -m 0755 node_exporter-${VERSION}.linux-amd64/node_exporter /usr/local/bin/node_exporter
sudo useradd --no-create-home --shell /usr/sbin/nologin ${USER} || true

Crie o unit file com as flags adequadas. Em hosts com múltiplos mounts ou pontos de montagem efêmeros, vale excluir sistemas de arquivos que poluem a observabilidade. Exemplos comuns: /snap, /var/lib/kubelet em nodes com containers, e pseudo-filesystems que não interessam para o SRE no dia a dia.

# /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter 
  --web.listen-address=:9100 
  --web.disable-exporter-metrics 
  --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+|var/lib/containerd/.+)($|/) 
  --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Ative e valide a unidade. A checagem precisa ir além de “o serviço está ativo”. Consulte o endpoint HTTP e confirme se o Prometheus conseguirá ler as métricas sem erro.

sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter --no-pager
curl -s http://127.0.0.1:9100/metrics | head

Se você administra muitos hosts, empacote isso em Ansible, Puppet ou Salt. Um playbook simples com Ansible já tira a operação do modo manual e deixa o estado rastreável no Git.

---
- name: Install node_exporter on Linux hosts
  hosts: linux_servers
  become: true
  tasks:
    - name: Create exporter user
      user:
        name: node_exporter
        shell: /usr/sbin/nologin
        system: true
        create_home: false

    - name: Copy binary
      copy:
        src: files/node_exporter
        dest: /usr/local/bin/node_exporter
        mode: '0755'

    - name: Install systemd unit
      copy:
        src: files/node_exporter.service
        dest: /etc/systemd/system/node_exporter.service
        mode: '0644'
      notify: restart node_exporter

    - name: Enable service
      systemd:
        name: node_exporter
        enabled: true
        state: started
        daemon_reload: true

  handlers:
    - name: restart node_exporter
      systemd:
        name: node_exporter
        state: restarted
        daemon_reload: true

Prometheus: scrape, retenção e disciplina operacional

Prometheus não é um banco genérico; ele foi desenhado para séries temporais com coleta por pull. No contexto Linux, essa escolha é excelente porque elimina dependência de push em cada host, preserva controle central e permite que o servidor de monitoramento descubra o estado dos alvos. O arquivo prometheus.yml é o coração da configuração. Nele você define jobs, intervalos, relabeling e regras de alertas.

Uma configuração base para hosts Linux pode ficar assim:

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  scrape_timeout: 10s

rule_files:
  - /etc/prometheus/rules/*.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

scrape_configs:
  - job_name: 'linux-node'
    static_configs:
      - targets:
          - 'server01:9100'
          - 'server02:9100'
          - 'server03:9100'
        labels:
          environment: 'production'
          role: 'linux-host'

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

Em escala real, use descoberta de serviços. Em cloud e em plataformas com automação, o ideal é gerar targets a partir de inventory, Consul, file_sd ou discovery integrado com a infraestrutura. O file-based service discovery é particularmente útil quando o inventário já vem do pipeline de provisionamento.

[
  {
    "targets": ["10.0.1.10:9100", "10.0.1.11:9100"],
    "labels": {
      "job": "linux-node",
      "environment": "staging",
      "team": "platform"
    }
  }
]

Com isso, basta apontar o Prometheus para o arquivo:

scrape_configs:
  - job_name: 'linux-node'
    file_sd_configs:
      - files:
          - /etc/prometheus/targets/linux_nodes.json
        refresh_interval: 30s

Na operação, retenção e armazenamento importam mais do que muita gente admite. Se o host Prometheus usa disco pequeno, a retenção precisa ser ajustada para evitar pressão no TSDB. O binário aceita flags como --storage.tsdb.retention.time. Em instâncias com volume dedicado, a organização do mount point faz diferença no desempenho de compaction e nas consultas.

/usr/local/bin/prometheus 
  --config.file=/etc/prometheus/prometheus.yml 
  --storage.tsdb.path=/var/lib/prometheus 
  --storage.tsdb.retention.time=15d

Rodando Prometheus e Grafana com Docker Compose sem perder governança

Nem todo ambiente pede instalação nativa. Em laboratórios, edge e pequenas células de infraestrutura, Docker Compose resolve bem. O cuidado é manter volumes persistentes, rede clara e arquivos de configuração versionados. Abaixo, um exemplo pragmático.

version: '3.8'
services:
  prometheus:
    image: prom/prometheus:v2.54.1
    container_name: prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
    ports:
      - '9090:9090'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    restart: unless-stopped

  grafana:
    image: grafana/grafana:11.1.0
    container_name: grafana
    ports:
      - '3000:3000'
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=change_me_now
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped

volumes:
  prometheus-data:
  grafana-data:

Se preferir criar imagens customizadas, use um Dockerfile enxuto para Grafana com provisionamento de datasources e dashboards. Isso elimina configuração manual no navegador e encaixa melhor em pipelines CI/CD.

FROM grafana/grafana:11.1.0
COPY provisioning/datasources /etc/grafana/provisioning/datasources
COPY provisioning/dashboards /etc/grafana/provisioning/dashboards
COPY dashboards /var/lib/grafana/dashboards

Grafana sem clique: datasource e dashboard como código

O uso correto de Grafana em ambiente DevOps é provisionar tudo. Datasource, pastas, dashboards e alertas devem viver em arquivos versionados. Assim, qualquer servidor novo sobe com o mesmo estado visual e sem dependência de operação manual. O datasource para Prometheus é simples, mas a vantagem aparece quando você replica isso em múltiplos ambientes.

apiVersion: 1

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

Para provisionar dashboards, a abordagem mais limpa é apontar para JSONs montados em um diretório específico. Um exemplo de configuração:

apiVersion: 1

providers:
  - name: 'linux-hosts'
    orgId: 1
    folder: 'Linux'
    type: file
    disableDeletion: false
    editable: false
    options:
      path: /var/lib/grafana/dashboards/linux

O JSON do dashboard é longo, mas o conceito importa: mantenha variáveis para host, ambiente e job. Use painéis com consultas PromQL que realmente orientem operação. Por exemplo, CPU por modo, uso de memória efetiva, latência de I/O e saturação de filesystem.

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100
rate(node_disk_read_bytes_total[5m]) + rate(node_disk_written_bytes_total[5m])
(node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} - node_filesystem_free_bytes{fstype!~"tmpfs|overlay|squashfs"}) / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} * 100

Em servidores Linux, alguns gráficos enganam se interpretados sem contexto. Load average alto nem sempre significa CPU saturada; pode ser fila de disco ou espera em I/O. Por isso combine node_load1 com métricas de CPU e disco. Em hosts com container runtime, acrescente visão de pressure stall information e context switches quando houver necessidade de diagnóstico fino.

PromQL que ajuda a sair do achismo

O valor do Prometheus aparece quando você abandona consultas superficiais. Um erro clássico é olhar somente para percentual de CPU e ignorar filas, gargalos de disco e pressão de memória. Em Linux, a combinação certa de métricas reduz tempo de diagnóstico em incidentes.

Exemplos úteis:

node_load15 / count(node_cpu_seconds_total{mode="idle"}) by (instance)
rate(node_context_switches_total[5m])
rate(node_intr_total[5m])
rate(node_disk_io_time_seconds_total[5m])
rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m])

Para identificar filesystem quase cheio com antecedência, a consulta precisa ignorar pseudo-filesystems e focar nos mounts reais. Melhor ainda: gere alertas com limiar proporcional ao ambiente, porque 80% em um volume de logs pode ser normal durante janela de retenção curta, enquanto o mesmo valor em um volume de banco de dados pode ser risco imediato.

(node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"}) / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} > 0.85

Alertas que não acordam o time por ruído

Monitoramento ruim gera fadiga. A solução não é desativar alertas; é escrever regras com contexto suficiente para diferenciar condição transitória de falha persistente. Para Linux, o básico costuma incluir alto uso de CPU sustentado, filesystem crítico, memória disponível abaixo de limite, falha de exporter e host sem scrape.

groups:
  - name: linux-hosts
    rules:
      - alert: HostDown
        expr: up{job="linux-node"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Host indisponível: {{ $labels.instance }}"
          description: "Prometheus não consegue fazer scrape do node_exporter há mais de 2 minutos."

      - alert: FilesystemAlmostFull
        expr: (node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"}) / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} > 0.85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Filesystem acima de 85% em {{ $labels.instance }}"
          description: "Mount {{ $labels.mountpoint }} atingiu nível de atenção."

      - alert: NodeMemoryPressure
        expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) < 0.10
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Memória baixa em {{ $labels.instance }}"
          description: "Menos de 10% de memória disponível no host. Verifique processos, cache e pressure do kernel."

O for é essencial para impedir alarmes por spikes curtos. Em hosts Linux com picos sazonais, 30 segundos de CPU alto não justificam chamada. Em compensação, falta de scrape ou filesystem cheio merece alerta cedo. Essa diferença operacional é o que separa dashboard ornamental de observabilidade útil.

O Alertmanager também precisa ser versionado. Mesmo sem integrar com chatops neste passo a passo, o roteamento básico deve estar pronto para e-mail, webhook ou sistema de incidentes já existente na empresa. A lógica de grupos e silences é o que mantém o fluxo saudável em mudanças programadas.

Validação no terminal: sem confiar no painel

Antes de confiar no Grafana, valide diretamente no Prometheus e no exporter. Consultar o endpoint /api/v1/query ajuda a depurar alertas, etiquetas e agregações sem depender da interface web. Isso é especialmente útil em ambientes headless ou com acesso restrito.

curl -G 'http://prometheus:9090/api/v1/query' --data-urlencode 'query=up{job="linux-node"}'

curl -s http://server01:9100/metrics | grep '^node_cpu_seconds_total' | head

curl -s http://prometheus:9090/-/ready
curl -s http://prometheus:9090/-/healthy

Quando a métrica não aparece, o diagnóstico deve seguir uma ordem lógica: conectividade TCP, resolução de DNS, firewall, status do serviço, logs do node_exporter e, por fim, configuração do Prometheus. Esse roteiro evita perda de tempo com suposições.

journalctl -u node_exporter -n 100 --no-pager
journalctl -u prometheus -n 100 --no-pager
ss -lntp | egrep '9100|9090|3000'

Provisionamento e GitOps para não voltar ao clique manual

Se o ambiente cresce, a interface passa a ser efeito colateral, não fonte de verdade. Coloque configuração de Prometheus, alertas e Grafana em repositório Git. Use CI para validar sintaxe com promtool e, se possível, testes automatizados de regras. Isso evita deploy de regra quebrada em produção.

promtool check config /etc/prometheus/prometheus.yml
promtool check rules /etc/prometheus/rules/*.yml

Um pipeline simples em GitHub Actions, GitLab CI ou Jenkins já entrega muito valor. O princípio é testar antes de aplicar. Exemplo de fluxo em shell para validação local:

set -euo pipefail
promtool check config prometheus.yml
promtool check rules rules/*.yml

docker compose config >/dev/null

Em cenários mais maduros, use repositórios separados ou diretórios organizados por componente:

observability/
├── prometheus/
│   ├── prometheus.yml
│   ├── rules/
│   └── targets/
├── grafana/
│   ├── provisioning/
│   └── dashboards/
└── ansible/
    ├── playbooks/
    └── roles/

Esse layout facilita revisão por pares e reduz o risco de alteração acidental em produção. Também melhora a rastreabilidade de mudanças em incidentes, algo essencial em times de plataforma e SRE.

Ajustes finos que fazem diferença em produção

Depois da primeira instalação funcional, começam os refinamentos. Excluir métricas desnecessárias reduz carga e ruído. Em hosts Linux com muitas interfaces, você pode filtrar nomes previsíveis para evitar painéis cheios de veth, docker bridges e interfaces efêmeras.

scrape_configs:
  - job_name: 'linux-node'
    static_configs:
      - targets: ['server01:9100']
    metric_relabel_configs:
      - source_labels: [device]
        regex: '^(lo|docker0|veth.*|cni.*)$'
        action: drop

Também vale revisar a cardinalidade. Cada label extra aumenta custo no TSDB. Não coloque hostname duplicado em múltiplas labels sem necessidade, e não transforme caminho de mount, processo ou container em dimensões explosivas sem uma justificativa operacional clara.

Se a infraestrutura roda em containers, o node_exporter no host continua valioso, mas às vezes faz sentido complementar com métricas de runtime e serviços específicos. O erro comum é tentar colocar tudo no mesmo painel. Separe host, aplicação e plataforma. Isso acelera troubleshooting e simplifica a semântica dos alertas.

Fechando a malha entre host, métrica e ação

Quando a stack está madura, o objetivo deixa de ser “monitorar Linux” e passa a ser reduzir tempo entre degradação e resposta. O node_exporter mostra o estado do host; o Prometheus armazena e cruza os sinais; o Grafana dá contexto visual; o Alertmanager leva apenas o necessário para o time correto. O resto é disciplina de operação: configurações em Git, validação com promtool, deploy automatizado, filtros de cardinalidade e regras de alerta sem ruído.

Esse modelo funciona em servidores dedicados, VMs e ambientes distribuídos porque respeita a forma como Linux opera de fato. Não depende de agente pesado, não exige telemetria proprietária e encaixa bem em infra como código. A partir desse ponto, expandir para blackbox_exporter, process_exporter, mysqld_exporter, nginx_exporter ou métricas de storage vira apenas uma extensão da mesma arquitetura, não uma reinvenção da base.

Se a intenção é operar Linux com previsibilidade, Prometheus e Grafana entregam exatamente isso quando a implementação é tratada como parte da infraestrutura, e não como um painel decorativo.