Централизованное логирование: Loki vs ELK для 80 микросервисов

Исходная ситуация: 80 микросервисов без единого окна

Финтех-компания «БанкСофт» обратилась к нам с типичной проблемой роста: 80 микросервисов на Java и Go генерировали суммарно 120 000 событий в секунду, а инженеры тратили по 40 минут на расследование каждого инцидента — потому что логи были разбросаны по серверам и читались через ssh + journalctl.

Основные болевые точки:

  • Нет корреляции — связать запрос пользователя с цепочкой вызовов через 5-6 сервисов невозможно без сквозного trace_id.
  • Потеря логов — при рестарте контейнеров логи из stdout пропадали навсегда, systemd journal хранил последние 500 МБ.
  • Нет алертов — о 500-х ошибках узнавали от клиентов, а не из мониторинга.
  • Комплаенс — регулятор требовал хранение аудит-логов минимум 3 года с возможностью поиска.

Перед нами стоял выбор: классический ELK (Elasticsearch + Logstash + Kibana) или более новый стек Loki + Promtail + Grafana. Мы развернули оба варианта в тестовой среде и провели детальное сравнение.

Архитектура ELK: мощно, но дорого

Классический ELK-стек работает по принципу полнотекстового индексирования: Logstash (или Filebeat) собирает логи, парсит их, а Elasticsearch индексирует каждое поле каждого сообщения. Это даёт мощный поиск, но требует значительных ресурсов.

Мы развернули тестовый кластер для 30 сервисов:

# docker-compose.yml — минимальный ELK
version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=true
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - "ES_JAVA_OPTS=-Xms8g -Xmx8g"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    deploy:
      resources:
        limits:
          memory: 16G

  logstash:
    image: logstash:8.12.0
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    environment:
      - "LS_JAVA_OPTS=-Xms4g -Xmx4g"
    deploy:
      resources:
        limits:
          memory: 8G

  kibana:
    image: kibana:8.12.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"

Pipeline для парсинга JSON-логов микросервисов:

# logstash/pipeline/microservices.conf
input {
  beats {
    port => 5044
  }
}

filter {
  json {
    source => "message"
    target => "parsed"
  }

  mutate {
    add_field => {
      "service_name" => "%{[parsed][service]}"
      "trace_id" => "%{[parsed][trace_id]}"
      "log_level" => "%{[parsed][level]}"
    }
  }

  date {
    match => ["[parsed][timestamp]", "ISO8601"]
    target => "@timestamp"
  }

  if [log_level] == "DEBUG" and [service_name] != "payment-gateway" {
    drop {}
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "logs-%{service_name}-%{+YYYY.MM.dd}"
    user => "elastic"
    password => "${ELASTIC_PASSWORD}"
  }
}

Через неделю тестирования стало понятно: для 80 сервисов при 120K events/sec потребуется кластер из 6 нод Elasticsearch по 32 ГБ RAM, Logstash съедает 4-8 ГБ RAM на каждый инстанс, а хранение данных за 30 дней требует ~12 ТБ SSD. Суммарно — порядка 250 ГБ RAM и 48 vCPU.

Архитектура Loki: лёгкость и интеграция с Grafana

Loki работает принципиально иначе: он не индексирует содержимое логов, а индексирует только метки (labels). Сами логи хранятся в сжатом виде в объектном хранилище. Это радикально снижает требования к ресурсам, но делает полнотекстовый поиск медленнее.

Конфигурация Loki в monolithic mode для «БанкСофт»:

# loki-config.yaml
auth_enabled: false

server:
  http_listen_port: 3100

common:
  ring:
    kvstore:
      store: memberlist
  replication_factor: 1
  path_prefix: /loki

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

storage_config:
  filesystem:
    directory: /loki/chunks
  tsdb_shipper:
    active_index_directory: /loki/tsdb-index
    cache_location: /loki/tsdb-cache

limits_config:
  retention_period: 720h          # 30 дней для оперативных логов
  max_query_length: 721h
  max_query_parallelism: 32
  ingestion_rate_mb: 64
  ingestion_burst_size_mb: 128
  per_stream_rate_limit: 5MB
  per_stream_rate_limit_burst: 15MB

compactor:
  working_directory: /loki/compactor
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150

Promtail собирает логи из Docker-контейнеров и добавляет метки:

# promtail-config.yaml
server:
  http_listen_port: 9080

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push
    batchwait: 1s
    batchsize: 1048576

scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*?)'
        target_label: 'container'
      - source_labels: ['__meta_docker_container_label_com_docker_compose_service']
        target_label: 'service'
      - source_labels: ['__meta_docker_container_label_environment']
        target_label: 'env'
    pipeline_stages:
      - json:
          expressions:
            level: level
            trace_id: trace_id
            msg: msg
      - labels:
          level:
          trace_id:
      - timestamp:
          source: timestamp
          format: RFC3339Nano

Результат: Loki в monolithic mode для тех же 80 сервисов потребляет 4 ГБ RAM, Promtail — по 50-80 МБ на каждой ноде. Хранение за 30 дней — ~800 ГБ (благодаря сжатию gzip/snappy). Разница с ELK — в 15 раз по RAM и в 15 раз по диску.

KQL vs LogQL: языки запросов в бою

Для инженеров, переходящих с ELK на Loki, самый болезненный момент — привыкание к LogQL. Вот как выглядят одинаковые запросы в обеих системах.

Поиск ошибок конкретного сервиса за последний час:

# KQL (Kibana)
service_name: "payment-gateway" AND log_level: "ERROR" AND @timestamp >= now-1h

# LogQL (Grafana/Loki)
{service="payment-gateway"} |= `` | json | level="ERROR"

Поиск по trace_id для отслеживания запроса:

# KQL
trace_id: "abc-123-def-456"

# LogQL
{env="production"} |= "abc-123-def-456"

Подсчёт ошибок по сервисам (метрики из логов):

# KQL — через Kibana Aggregations (визуальный интерфейс)
# или через Elasticsearch aggregation API:
GET logs-*/_search
{
  "size": 0,
  "query": { "match": { "log_level": "ERROR" } },
  "aggs": {
    "by_service": {
      "terms": { "field": "service_name.keyword" }
    }
  }
}

# LogQL — метрический запрос
sum by (service) (
  rate({env="production"} |= `` | json | level="ERROR" [5m])
)

Ключевое отличие: KQL ищет по заранее проиндексированным полям мгновенно. LogQL сначала фильтрует по меткам (быстро), затем делает grep по содержимому (медленнее). Для запросов типа «покажи все ERROR за час в конкретном сервисе» разницы нет. Но для запросов «найди все логи, содержащие слово timeout в любом сервисе за последнюю неделю» — ELK будет в 10-50 раз быстрее.

Для «БанкСофт» это оказалось приемлемым компромиссом: 95% запросов к логам делаются в контексте конкретного сервиса и короткого временного окна.

Многоуровневое хранение и ретеншн

Регуляторные требования «БанкСофт» диктовали хранение аудит-логов 3 года. Хранить всё на SSD — безумие. Мы реализовали трёхуровневую схему хранения:

# loki-config.yaml — tiered retention
limits_config:
  retention_period: 720h        # default: 30 дней
  retention_stream:
    - selector: '{log_type="audit"}'
      priority: 1
      period: 26280h             # 3 года для аудит-логов
    - selector: '{level="DEBUG"}'
      priority: 2
      period: 72h                # 3 дня для дебаг-логов
    - selector: '{service=~"payment.*"}'
      priority: 3
      period: 2160h              # 90 дней для платёжных сервисов

Для долгосрочного хранения мы переключили бэкенд с локальной FS на S3-совместимое хранилище (MinIO на дешёвых HDD):

storage_config:
  tsdb_shipper:
    active_index_directory: /loki/tsdb-index
    cache_location: /loki/tsdb-cache
    shared_store: s3
  aws:
    endpoint: minio.internal:9000
    bucketnames: loki-chunks
    access_key_id: ${MINIO_ACCESS_KEY}
    secret_access_key: ${MINIO_SECRET_KEY}
    insecure: true
    s3forcepathstyle: true

Стоимость хранения по уровням:

УровеньТип данныхХранилищеСтоимость за ТБ/мес
Hot (0-30 дней)Все логиNVMe SSD~8 000 руб
Warm (30-90 дней)Платёжные + аудитSATA SSD~3 000 руб
Cold (90 дней — 3 года)Только аудитMinIO на HDD~500 руб

В ELK аналогичная схема реализуется через ILM (Index Lifecycle Management) политики с hot/warm/cold нодами, но требует отдельных серверов для каждого уровня — что значительно дороже.

Multi-tenancy: изоляция команд

В «БанкСофт» работают 12 продуктовых команд, и каждая должна видеть только свои логи. В Loki multi-tenancy реализуется через HTTP-заголовок X-Scope-OrgID:

# promtail — отправка с tenant ID
clients:
  - url: http://loki:3100/loki/api/v1/push
    tenant_id: team-payments

# loki-config.yaml — включение multi-tenancy
auth_enabled: true

limits_config:
  per_tenant_override_config: /etc/loki/overrides.yaml

# overrides.yaml — лимиты по командам
overrides:
  team-payments:
    ingestion_rate_mb: 32
    max_streams_per_user: 50000
    retention_period: 2160h
  team-analytics:
    ingestion_rate_mb: 8
    max_streams_per_user: 10000
    retention_period: 168h

В Grafana каждая команда получает свой datasource с фиксированным X-Scope-OrgID, что делает изоляцию прозрачной: команда платежей физически не может увидеть логи аналитиков.

В ELK аналогичная изоляция делается через Kibana Spaces + Document Level Security, но настройка значительно сложнее и требует платной лицензии X-Pack Security (от $95/месяц за ноду).

Миграция с ELK на Loki: пошаговый план

У «БанкСофт» изначально стоял ELK для 15 из 80 сервисов. Мы провели миграцию параллельным запуском:

  1. Неделя 1: Развернули Loki + Grafana параллельно с существующим ELK. Promtail стал отправлять логи в оба стека одновременно.
  2. Неделя 2: Перенесли дашборды из Kibana в Grafana. Адаптировали KQL-запросы в LogQL.
  3. Неделя 3: Подключили к Loki оставшиеся 65 сервисов, которые ранее вообще не имели централизованного логирования.
  4. Неделя 4: Настроили алертинг в Grafana, выключили ELK.

Скрипт для параллельной отправки логов (на время миграции):

# vector.toml — замена Logstash на Vector (DataDog)
[sources.docker_logs]
type = "docker_logs"

[transforms.parse_json]
type = "remap"
inputs = ["docker_logs"]
source = '''
. = parse_json!(.message)
.service = get_env_var!("SERVICE_NAME")
.env = "production"
'''

# Отправляем одновременно в Loki и Elasticsearch
[sinks.loki]
type = "loki"
inputs = ["parse_json"]
endpoint = "http://loki:3100"
encoding.codec = "json"
labels.service = "{{ service }}"
labels.env = "{{ env }}"
labels.level = "{{ level }}"

[sinks.elasticsearch]
type = "elasticsearch"
inputs = ["parse_json"]
endpoints = ["http://elasticsearch:9200"]
bulk.index = "logs-%Y.%m.%d"

Vector вместо Logstash — отдельная находка: потребление памяти упало с 4 ГБ до 120 МБ при той же пропускной способности (написан на Rust).

Результаты и рекомендации

Итоги проекта для «БанкСофт» через 2 месяца эксплуатации:

МетрикаДо (ELK на 15 сервисов)После (Loki на 80 сервисов)
Потребление RAM96 ГБ (6 нод ES)6 ГБ (Loki + Promtail)
Потребление CPU48 vCPU8 vCPU
Хранение (30 дней)4.2 ТБ SSD800 ГБ SSD + HDD
MTTR инцидентов40 минут8 минут
Покрытие сервисов15 из 8080 из 80
Стоимость инфраструктуры~85 000 руб/мес~18 000 руб/мес

Когда выбирать ELK: полнотекстовый поиск критичен, бюджет позволяет, команда уже знает KQL, нужна аналитика по логам (Kibana ML).

Когда выбирать Loki: уже используете Prometheus + Grafana, бюджет ограничен, логи структурированные (JSON), не нужен поиск по произвольным полям без знания сервиса.

Для большинства проектов с микросервисной архитектурой Loki — оптимальный выбор. Если ваша инфраструктура требует централизованного логирования — оставьте заявку на itfresh.ru, и мы поможем выбрать и внедрить стек, подходящий именно вашим задачам.

Часто задаваемые вопросы

Да, это стандартный подход при миграции. Vector или Fluentd могут отправлять логи в оба стека параллельно. Мы рекомендуем параллельный запуск минимум на 2 недели, чтобы убедиться, что все запросы в LogQL дают результаты, идентичные KQL.
Для проекта до 20 сервисов (10-15K events/sec) достаточно одного сервера: 4 vCPU, 8 ГБ RAM, 200 ГБ SSD. Loki в monolithic mode, Promtail на каждой ноде, Grafana для визуализации. Для 50+ сервисов рекомендуем microservices mode с отдельными компонентами ingester/querier.
Promtail ведёт файл positions.yaml, запоминая позицию чтения в каждом лог-файле. При восстановлении Loki Promtail дочитает логи с последней сохранённой позиции. Для гарантированной доставки добавьте Kafka между Promtail и Loki как буфер.
Да, через Loki Ruler. Можно создавать правила алертинга на LogQL-запросах: например, «если rate ошибок 500 в payment-gateway превысил 5 в минуту — отправить в Telegram». Алерты маршрутизируются через Alertmanager, как и в Prometheus.
Vector написан на Rust, потребляет в 10-30 раз меньше памяти (120 МБ вместо 4 ГБ у Logstash) при сопоставимой пропускной способности. Поддерживает отправку в Loki, Elasticsearch, Kafka, S3 и ещё 40+ систем. Конфигурация проще, чем у Logstash. Рекомендуем как замену обоим: Filebeat + Logstash.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#centralized logging#elk stack#loki#promtail#grafana#logql#kibana#elasticsearch
Комментарии 0

Оставить комментарий

загрузка...