TL;DR — Resumo Rápido
Configure o Grafana Loki para agregação centralizada de logs com Promtail. LogQL, dashboards, alertas e pipelines multi-tenant em produção com exemplos reais.
O Grafana Loki é um sistema de agregação de logs escalável horizontalmente construído sobre os mesmos princípios do Prometheus — ele indexa apenas labels, não o texto completo do log, tornando-o dramaticamente mais barato que o stack ELK. Este guia cobre a arquitetura do Loki, instalação via Docker Compose, configuração do Promtail, a linguagem de consultas LogQL, criação de dashboards no Grafana, alertas, multi-tenancy e dimensionamento em produção.
Pré-requisitos
- Docker e Docker Compose (ou um cluster Kubernetes para instalação via Helm).
- Grafana 10+ (ou Grafana Cloud) para visualização.
- Familiaridade básica com configuração YAML e ferramentas de linha de comando.
Arquitetura do Loki
O Loki distribui seu trabalho entre quatro componentes principais:
- Distributor — Recebe fluxos de logs do Promtail/agentes, valida labels e distribui para os ingesters via hashing consistente.
- Ingester — Mantém logs recentes em memória (tamanho de chunk configurável), descarrega chunks comprimidos para armazenamento de objetos e mantém um WAL para segurança em caso de falha.
- Querier — Executa consultas LogQL sobre chunks tanto em memória (recentes) quanto em armazenamento (históricos).
- Compactor — Mescla e deduplica as tabelas de índice do boltdb-shipper e aplica políticas de retenção.
No modo monolítico (padrão para instalações de nó único), os quatro componentes rodam em um único binário. Quando o volume de logs ultrapassa ~50 GB/dia, é recomendado mudar para modo microserviços onde cada componente escala de forma independente.
O insight central do design: o Loki nunca constrói um índice invertido do conteúdo dos logs. Uma consulta com |= "OutOfMemoryError" realiza um grep distribuído nos chunks que correspondem às labels selecionadas.
Por que Loki em vez do ELK?
| Característica | Loki | Elasticsearch |
|---|---|---|
| Modelo de indexação | Apenas labels | Índice invertido full-text |
| Custo de armazenamento | ~10x menos | Alto (índice + dados) |
| Linguagem de consulta | LogQL (similar ao PromQL) | Lucene / KQL |
| Nativo no Grafana | Sim — primeira classe | Via plugin |
| Escala horizontal | Sim (sharding por labels) | Sim (shards) |
| Mudanças de esquema | Não necessárias | Requer re-indexação |
| Alertas | Loki ruler + Alertmanager | ElastAlert / SIEM |
| Nativo no Kubernetes | Sim (Helm chart) | Sim (operador ECK) |
Passo 1: Instalação com Docker Compose
Crie um diretório de projeto e o seguinte docker-compose.yml:
version: "3.8"
services:
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:3.0.0
volumes:
- ./promtail-config.yaml:/etc/promtail/config.yaml
- /var/log:/var/log:ro
- /run/log/journal:/run/log/journal:ro
command: -config.file=/etc/promtail/config.yaml
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
volumes:
loki-data:
Passo 2: Configurar o Loki
Crie loki-config.yaml:
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 5m
chunk_retain_period: 30s
wal:
enabled: true
dir: /loki/wal
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/boltdb-cache
shared_store: filesystem
filesystem:
directory: /loki/chunks
compactor:
working_directory: /loki/compactor
shared_store: filesystem
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
limits_config:
retention_period: 30d
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_query_length: 721h
Passo 3: Configurar o Promtail
Crie promtail-config.yaml:
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
host: __HOSTNAME__
relabel_configs:
- source_labels: [__journal__systemd_unit]
target_label: unit
- job_name: nginx
static_configs:
- targets: [localhost]
labels:
job: nginx
env: prod
__path__: /var/log/nginx/access.log
- job_name: docker
static_configs:
- targets: [localhost]
labels:
job: docker
__path__: /var/lib/docker/containers/*/*-json.log
pipeline_stages:
- json:
expressions:
log: log
stream: stream
- output:
source: log
Inicie o stack:
docker compose up -d
docker compose logs -f loki
Passo 4: Linguagem de Consultas LogQL
LogQL tem duas formas: consultas de log e consultas de métricas.
Seletores de stream (obrigatórios, sempre primeiro):
{job="nginx"}
{job="nginx", env="prod"}
{job=~"nginx|apache"}
Expressões de filtro (pipe após o seletor):
{job="nginx"} |= "error"
{job="nginx"} != "health_check"
{job="nginx"} |~ "5[0-9]{2}"
Expressões de parser — extraia campos de logs estruturados:
{job="app"} | json
{job="app"} | logfmt
{job="app"} | json | level="error"
Consultas de métricas — converta logs em séries temporais:
rate({job="nginx"} |= "error" [5m])
count_over_time({job="app"} | json | level="error" [1h])
sum by (status) (rate({job="nginx"} | logfmt | status=~"5.." [5m]))
topk(5, sum by (uri) (rate({job="nginx"}[10m])))
Passo 5: Fonte de Dados e Dashboards no Grafana
- No Grafana, vá a Connections → Data sources → Add data source → Loki.
- Configure a URL para
http://loki:3100(ou seu endereço do Loki). - Clique em Save & test — você verá “Data source connected.”
Dicas do painel Explore:
- Use Live tail para transmitir logs em tempo real — ideal para depurar deploys.
- Clique em qualquer linha de log para ver o contexto do log — linhas antes e depois do match.
- Mude para a aba Metrics para visualizar consultas
rate()como painéis de séries temporais.
Passo 6: Alertas com o Loki Ruler
Adicione um bloco ruler ao loki-config.yaml:
ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://alertmanager:9093
ring:
kvstore:
store: inmemory
enable_api: true
Crie /loki/rules/fake/rules.yaml:
groups:
- name: alertas-app
interval: 1m
rules:
- alert: TaxaErroAlta
expr: |
sum(rate({job="app"} | json | level="error" [5m])) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Taxa de erros alta nos logs da app"
Loki vs. Alternativas
| Ferramenta | Modelo de armazenamento | Auto-hospedado | Custo | Nativo Grafana | Melhor para |
|---|---|---|---|---|---|
| Loki | Índice de labels + chunks comprimidos | Sim | Baixo | Sim | Equipes com Grafana/Prometheus |
| Elasticsearch | Índice invertido full-text | Sim | Alto | Via plugin | Busca full-text, SIEM |
| Fluentd | Apenas agente (sem armazenamento) | Sim | Grátis | Não | Roteamento e transformação |
| Datadog Logs | SaaS, proprietário | Não | Alto | Não | Empresa, observabilidade completa |
| CloudWatch Logs | SaaS (AWS) | Não | Médio | Via plugin | Cargas de trabalho nativas AWS |
Multi-Tenancy
Ative multi-tenancy com auth_enabled: true no Loki. Cada requisição deve incluir X-Scope-OrgID: <tenant-id>. Configure o Promtail para enviar o cabeçalho:
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: equipe-backend
Recomendações de Dimensionamento em Produção
| Volume de logs | Arquitetura | Armazenamento | RAM |
|---|---|---|---|
| < 5 GB/dia | Monolítico, nó único | Filesystem local | 2 GB |
| 5–50 GB/dia | Monolítico + S3/GCS | Armazenamento de objetos | 4–8 GB |
| 50–500 GB/dia | Microserviços, 3 ingesters | Objetos + TSDB | 16–32 GB |
| > 500 GB/dia | Microserviços + Thanos ruler | Objetos multi-AZ | 64 GB+ |
Sempre ative o WAL (ingester.wal.enabled: true) em produção para evitar perda de dados durante reinicializações do ingester.
Resolução de Problemas
| Problema | Solução |
|---|---|
connection refused ao enviar | Verifique se o contêiner Loki está rodando e a porta 3100 é acessível do Promtail |
| Timeout nas consultas | Aumente querier.query_timeout e divida consultas em intervalos de tempo menores |
| Logs do Docker ausentes | Verifique positions.yaml do Promtail para o caminho do log do contêiner |
| Alta memória no ingester | Reduza chunk_idle_period para descarregar chunks ao armazenamento mais cedo |
| Retenção não funciona | Certifique-se de que compactor.retention_enabled: true e limits_config.retention_period estão definidos |
Resumo
- A indexação apenas de labels torna o Loki 10x mais barato que o Elasticsearch para o mesmo volume.
- O Promtail envia logs de arquivos, journal do systemd e Docker com enriquecimento de labels.
- O LogQL suporta filtragem de streams, parse JSON/logfmt, cálculos de taxa e agregação.
- O Loki ruler avalia alertas LogQL em um calendário e os roteia para o Alertmanager.
- Para produção, substitua o armazenamento em filesystem por S3/GCS e ative WAL e cache de chunks.