Stack ELK self-hosted na VPS: Elasticsearch + Kibana
A stack ELK (Elasticsearch + Logstash + Kibana) é o padrão de mercado para análise de logs em larga escala — full-text search, agregações em tempo real, dashboards poderosos. Pesada para VPS modestas (mínimo 8 GB RAM), mas insuperável para análise de logs estruturados e buscas complexas.
Instalar Elasticsearch e Kibana com Docker
Stack mínima: Elasticsearch + Kibana (sem Logstash):
yaml
# docker-compose.yml — ELK stack mínima:
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
restart: always
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- ES_JAVA_OPTS=-Xms2g -Xmx2g # 2 GB de heap
ports:
- "127.0.0.1:9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
kibana:
image: docker.elastic.co/kibana/kibana:8.13.0
restart: always
depends_on:
- elasticsearch
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
ports:
- "127.0.0.1:5601:5601"
volumes:
es_data:
# .env:
# ELASTIC_PASSWORD=senha-forte
# KIBANA_PASSWORD=outra-senha
# Configurar senha do kibana_system após subir:
docker exec -it elasticsearch \
elasticsearch-reset-password -u kibana_systemAtenção
Elasticsearch requer vm.max_map_count=262144 no host. Execute: sysctl -w vm.max_map_count=262144 e adicione ao /etc/sysctl.conf para persistir após reboot.
Enviar logs do Node.js para Elasticsearch
Indexar logs estruturados diretamente (sem Logstash):
typescript
// npm install @elastic/elasticsearch pino pino-elasticsearch
import { Client } from '@elastic/elasticsearch'
import pino from 'pino'
const elastic = new Client({
node: 'http://localhost:9200',
auth: { username: 'elastic', password: process.env.ELASTIC_PASSWORD! },
})
// Transport do Pino para Elasticsearch:
const logger = pino({
transport: {
targets: [
// Console:
{ target: 'pino-pretty', level: 'debug', options: {} },
// Elasticsearch:
{
target: 'pino-elasticsearch',
level: 'info',
options: {
node: 'http://localhost:9200',
auth: { username: 'elastic', password: process.env.ELASTIC_PASSWORD },
index: 'minha-api-logs', // índice por aplicação
// Ou usar data stream com prefixo de data:
// index: 'logs-minha-api-%{DATE}',
flushBytes: 1000, // flush a cada 1 KB
flushInterval: 5000, // ou a cada 5s
},
},
],
},
})
// Usar normalmente — o pino-elasticsearch envia em batch:
logger.info({ userId: '123', rota: '/pedidos', duracao: 45 }, 'Pedido criado')
logger.error({ err: error, pedidoId: '456' }, 'Falha no pagamento')Usar Filebeat em vez de envio direto
Coletar logs de arquivos e containers com Filebeat:
yaml
# docker-compose.yml — adicionar Filebeat:
filebeat:
image: docker.elastic.co/beats/filebeat:8.13.0
restart: always
user: root
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/log:/var/log:ro
# filebeat.yml:
filebeat.inputs:
# Logs de containers Docker:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
processors:
- add_docker_metadata:
host: unix:///var/run/docker.sock
# Logs do Nginx:
- type: log
paths:
- /var/log/nginx/access.log
fields:
service: nginx
type: access-log
json.keys_under_root: false
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
username: "elastic"
password: "${ELASTIC_PASSWORD}"
index: "filebeat-%{[agent.version]}-%{+yyyy.MM.dd}"Criar dashboards no Kibana
Configurar index patterns, queries KQL e visualizações:
bash
# No Kibana — primeiros passos:
# 1. Criar Data View (index pattern):
# Stack Management → Data Views → Create
# Index pattern: minha-api-logs* ou filebeat-*
# Timestamp field: @timestamp
# 2. Discover — explorar logs:
# Analytics → Discover → selecionar data view
# Query KQL: level: "error" AND service: "api"
# Filtros de tempo: Last 24 hours
# Queries KQL comuns:
# Erros HTTP:
# response: [500 TO 599]
# Logs de um usuário específico:
# userId: "123"
# Latência alta:
# duration > 1000
# Texto livre (full-text search):
# "pagamento falhou" OR "timeout"
# 3. Criar visualização:
# Analytics → Visualize Library → Create
# Lens → arrastar campos → configurar agregações
# 4. Dashboard:
# Analytics → Dashboard → Create
# Adicionar painéis: gráfico de erros por hora,
# top rotas por latência, mapa de erros
# 5. Alertas:
# Stack Management → Rules → Create rule
# Type: Elasticsearch query
# Condição: count > 10 erros em 5 min
# Ação: enviar email ou webhookGerenciar índices e retenção de dados
Configurar ILM (Index Lifecycle Management) para controlar custos:
json
# Index Lifecycle Management — via API do Elasticsearch:
# Criar política de ciclo de vida:
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "5GB",
"max_age": "1d" # rotacionar diariamente
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "30d",
"actions": { "delete": {} } # deletar após 30 dias
}
}
}
}
# Aplicar à template de índice existente:
PUT _index_template/logs-template
{
"index_patterns": ["minha-api-logs-*"],
"template": {
"settings": {
"index.lifecycle.name": "logs-policy"
}
}
}
# Via Kibana: Stack Management → Index Lifecycle Policies → Create$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.