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_system
    Atençã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 webhook

    Gerenciar í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.

    Perguntas frequentes