Ротация и хранение логов: retention policy в Elasticsearch и Loki без потери данных | AdminWiki

Ротация и хранение логов: retention policy в Elasticsearch и Loki без потери данных

30 сентября 2026 13 мин. чтения
Содержание статьи

Одна тысяча запросов в секунду на веб-сервере даёт около 10 ГБ access-логов в сутки: при 120 байтах на строку это 120 КБ/с, то есть примерно 10,4 ГБ в день и 320 ГБ в месяц.

Retention policy определяет, что с этими данными происходит дальше: сколько они живут в быстром хранилище, когда переезжают в сжатый архив и в какой момент удаляются. Работают с этим два инструмента: ILM (Index Lifecycle Management) в Elasticsearch и compactor с параметром retention_period в Grafana Loki. Ниже - готовые конфиги для обоих, матрица сроков хранения по типам логов, разбор компрессии, команды проверки и чек-лист.

Главный принцип: срок хранения задаёт ценность записи, а не размер диска. Аудит и события безопасности живут месяцами, debug-строки - дни.

Что такое retention policy и зачем она нужна

Политика хранения описывает жизненный цикл логов целиком: где данные лежат в первые часы, сколько доступны для быстрого поиска, когда сжимаются и когда удаляются безвозвратно. Состоит она из трёх сущностей, которые часто путают между собой.

Ключевые понятия: retention period, rollover, lifecycle policy

  • Retention period - срок, в течение которого лог обязан оставаться доступным. В Loki его задают в часах через limits_config.retention_period, в Elasticsearch срок вытекает из min_age в фазах ILM.
  • Rollover - создание новой порции данных при достижении порога: размера первичного шарда, возраста или числа документов. Rollover ничего не удаляет, он только открывает новый индекс или чанк.
  • Lifecycle policy - набор правил перехода между фазами: hot (запись и быстрый поиск), warm (редкие запросы, сжатие), cold (архив), delete (удаление).

Типичная цепочка выглядит так: индекс пишется в hot-фазе и ротируется каждые сутки или при достижении 50 ГБ в первичном шарде, на 7-й день уезжает на warm-узлы и сжимается до одного сегмента, на 30-й день переходит на cold-узлы, на 90-й удаляется. Rollover без lifecycle policy закрывает половину задачи: индексы получаются мелкими и аккуратными, но удалять и переносить их некому.

ПонятиеElasticsearchGrafana Loki
Срок храненияmin_age в фазах плюс действие deletelimits_config.retention_period
Новая порция данныхILM rollover и rollover aliasнарезка чанков и периодичность sharding
Управление жизненным цикломILM policycompactor с retention_enabled и per-tenant overrides

Риски отсутствия retention policy: переполнение диска и потеря аудита

Расчёт простой: 10 ГБ в сутки при диске 500 ГБ означают, что свободное место закончится за полтора месяца, а при всплеске трафика или включённом debug-логировании раньше. Elasticsearch реагирует раньше, чем кончится место. По умолчанию cluster.routing.allocation.disk.watermark.low равен 85%, high - 90%, flood_stage - 95%. На пороге high кластер начинает эвакуировать шарды с заполненного узла, на flood_stage переводит индексы в read-only, и запись логов встаёт с ошибкой. В Loki ингестер перестаёт принимать чанки, а агенты (Promtail, Vector, Fluent Bit) копят данные в буфере и теряют их при перезапуске.

Обратная крайность: хранить три дня. Инцидент, о котором узнали в пятницу вечером, к понедельнику останется без следов. Требования к аудиторским записям задают внешние документы. Требование 10.5.1 PCI DSS предписывает сохранять историю журнала аудита как минимум 12 месяцев, при этом по крайней мере последние три месяца должны быть немедленно доступны для анализа (формулировка приводится по руководству Microsoft Entra по требованию 10 PCI DSS; в самом стандарте есть проверочный вопрос о немедленном доступе к журналу аудита за последние три месяца). GDPR конкретный срок не фиксирует, но статья 5 устанавливает принцип ограничения хранения: персональные данные хранятся в форме, позволяющей идентифицировать субъектов, не дольше, чем это необходимо для целей обработки, а контролёр должен установить сроки, по истечении которых данные удаляются или пересматриваются (текст статьи 5 GDPR). По ISO/IEC 27001 подтверждённых данных о конкретных сроках хранения в наших материалах нет: стандарт не задаёт числа, а требует определить срок самостоятельно и ему следовать - это ориентир, который нужно сверять с актуальной редакцией стандарта.

Единый срок для всех логов не работает. Нужна дифференцированная политика, где срок зависит от типа записи.

Настройка ILM-политики в Elasticsearch: пошаговый пример

ILM входит в состав Elasticsearch с версии 6.6, полный набор фаз и действий доступен в 7.x и 8.x. Политика применяется к индексам через шаблон, а ротацией управляет rollover alias. Развёртывание кластера целиком, от шардирования до Kibana, описано в статье ELK Stack 2026: готовое решение для сбора и анализа логов.

Создание ILM-политики: hot, warm, cold, delete

PUT _ilm/policy/logs-90d
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",
            "max_age": "1d"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "allocate": {
            "number_of_replicas": 1,
            "include": { "box_type": "warm" }
          },
          "forcemerge": { "max_num_segments": 1 },
          "set_priority": { "priority": 50 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "allocate": {
            "number_of_replicas": 1,
            "include": { "box_type": "cold" }
          },
          "set_priority": { "priority": 0 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": { "delete": {} }
      }
    }
  }
}

Что здесь важно. Параметр max_primary_shard_size пришёл на смену max_size, который считали устаревшим уже в 7.x, поэтому в 8.x указывайте размер первичного шарда. Действие forcemerge снижает число сегментов до одного: это освобождает место после удаления документов и ускоряет поиск, но создаёт пиковую нагрузку на диск, поэтому его ставят только в warm и cold. Действие allocate с include box_type перекладывает хранение на узлы с атрибутом node.attr.box_type: warm или cold. Раньше в cold-фазе применяли freeze, с версии 7.12 это действие устарело, и данные проще держать на отдельных узлах.

Для аудита создайте вторую политику: те же фазы, но min_age в delete равен 365 дням, либо фаза delete отсутствует совсем, если регулятор требует бессрочного хранения. Для отладочных логов хватит одной hot-фазы с rollover по 20 ГБ и удалением через 7 дней.

Применение политики через index template

PUT _index_template/logs-debug
{
  "index_patterns": ["logs-debug-*"],
  "priority": 200,
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs-debug-7d",
      "index.lifecycle.rollover_alias": "logs-debug"
    }
  }
}

PUT logs-debug-000001
{
  "aliases": {
    "logs-debug": { "is_write_index": true }
  }
}

Первый индекс создаётся вручную: ILM ротирует алиас, и до появления индекса-первоисточника ротировать нечего. В Elasticsearch 8.x есть альтернатива: data stream. Шаблон получает блок "data_stream": {}, а поток создаётся командой PUT _data_stream/logs-debug. Bootstrap-индекс в этом случае не нужен, ILM с действием rollover управляет бэкенд-индексами потока сам.

Шаблон применяется по маске имени, поэтому приоритет 200 защищает его от перекрытия более общим шаблоном на logs-*. Если шаблон не совпал с именем индекса, новый индекс окажется вне ILM и будет расти до ручного вмешательства.

Проверка работы ILM и типичные ошибки

GET _index_template/logs-debug
GET _ilm/status
GET logs-debug-*/_ilm/explain

Первый запрос покажет, что шаблон сохранён. Второй возвращает operation_mode: RUNNING или STOPPED. При STOPPED политики не выполняются, и запускают их командой POST _ilm/start. Третий показывает для каждого индекса текущую фазу, шаг и возраст.

Самая частая ошибка выглядит так: index.lifecycle.rollover_alias [logs-debug] does not point to index [logs-debug-000001]. Причина в том, что алиас не помечен как is_write_index или указывает на другой индекс. Лечится переустановкой алиаса: POST _aliases с действием add и полем is_write_index: true.

Другие грабли: узлов с атрибутом box_type: warm или cold в кластере нет, тогда allocate зависает с ошибкой в фазе; у пользователя, создающего шаблон, не хватает прав, и шаблон сохраняется без настроек ILM; ротация настроена без учёта имени индекса, и новые индексы конфликтуют между собой. Проверяйте состояние после каждого изменения политики.

Построение hot-warm-cold архитектуры с архивированием в объектное хранилище и готовые конфигурации logrotate для Nginx и Docker разобраны отдельно в материале оптимизация хранения и ротации логов для production-сред.

Настройка retention в Loki: конфигурация и удаление старых данных

Loki хранит логи в чанках в объектном хранилище (S3, GCS, MinIO), а индексирует только метки. Поэтому старые данные удаляет не ингестер, а compactor: он сканирует чанки и убирает те, что вышли за retention_period.

Конфигурация retention_period и compactor

auth_enabled: true

limits_config:
  retention_period: 744h
  reject_old_samples: true
  reject_old_samples_max_age: 168h

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

744h - это 31 день. Параметр retention_enabled обязателен: без него compactor только уплотняет индекс, а старые чанки остаются на месте. Параметр delete_request_store должен указывать хранилище, где compactor ведёт очередь заявок на удаление, и является обязательным при включённом retention - без него удаление не заработает (см. документацию Grafana Loki по retention). Обратите внимание на reject_old_samples_max_age: он отбрасывает слишком старые записи на входе и защищает от данных с неправильными часами на источнике.

Compactor должен работать в одном экземпляре: при нескольких репликах запускайте его отдельной целью (target: compactor) или единственным процессом, иначе компоненты начнут конкурировать за одну рабочую директорию.

Проверка: curl -s http://localhost:3100/ready и curl -s http://localhost:3100/metrics | grep loki_compactor. Метрики compactor показывают длину очереди заявок на удаление и время последнего запуска. Если счётчик удалений не растёт, retention не работает, даже когда параметр задан.

Разделение retention для разных типов логов в Loki

Общий retention_period задаёт срок для всех, кто пишет в Loki. Для дифференцированного хранения нужны тенанты и runtime-конфигурация.

# loki.yaml
runtime_config:
  file: /etc/loki/runtime.yaml

# /etc/loki/runtime.yaml
overrides:
  audit:
    retention_period: 8760h
  debug:
    retention_period: 168h
  default:
    retention_period: 744h

8760h - это год, 168h - неделя. Тенант определяется заголовком X-Scope-OrgID, поэтому auth_enabled должен быть true, иначе все записи попадут в общий тенант fake. Агент указывает тенант при отправке:

# promtail-config.yaml
clients:
  - url: http://loki:3100/loki/api/v1/push
    tenant_id: audit

В актуальной документации Grafana Loki в блоке limits_config присутствует параметр retention_stream, который можно переопределить в секции overrides файла runtime_config (см. описание параметров конфигурации Loki). Это позволяет задавать разные сроки для отдельных потоков внутри тенанта, но требует аккуратной работы с кардинальностью меток; точный синтаксис селекторов и приоритетов сверяйте с документацией вашей версии Loki. Сравнение хранилищ и общую архитектуру конвейера смотрите в статье централизованный сбор и анализ логов в 2026.

Как разделить логи по важности: короткое хранение для отладки, длительное для аудита

Матрица хранения: уровни логирования и сроки

Тип логаПримерСрокГде лежитПримечание
debug, traceтела HTTP-запросов, SQL, стек вызовов3 дняhot, затем deleteвысокая скорость роста, низкая ценность после отладки
infoaccess-логи Nginx, старт сервисов14 днейhot, затем warmна 7-й день forcemerge и сжатие
warn, error5xx, таймауты, ретраи30-90 днейhot, warm, coldоснова для разбора инцидентов
auditвход в админку, смена прав, доступ к ПДн12 месяцевhot 7 дней, warm 30, coldPCI DSS: 12 месяцев, 3 из них доступны сразу
securityWAF, fail2ban, изменения конфигов24-36 месяцевcold, объектное хранилищезапросы редкие, объём большой, сжатие максимальное

Сроки из таблицы - ориентир, а не норматив: подставьте требования своего регулятора и стоимость расследования инцидента. Аудит и безопасность выносите в отдельные индексы или тенанты, тогда сокращение срока хранения debug-логов их не затронет.

Маршрутизация логов в Elasticsearch и Loki

В Elasticsearch разделение делают по индексам, у каждого своя ILM-политика. Filebeat умеет подставлять тип лога в имя индекса:

# filebeat.yml
processors:
  - add_fields:
      target: ''
      fields:
        log_type: audit
output.elasticsearch:
  index: "logs-%{[log_type]}-%{+yyyy.MM.dd}"

Дальше шаблон logs-audit-* связывается с политикой на 365 дней, logs-debug-* с политикой на 7 дней. В Loki ту же задачу решают тенанты или метка log_type вместе с retention_stream. Критерии, какие события вообще стоит писать, собраны в статье что логировать, а что нет, а детали маршрутизации по тегам и приоритетам - в руководстве по маршрутизации логов и метрик.

Предупреждение: каждое новое имя индекса или тенанта требует отдельного шаблона, политики и мониторинга. Пять-семь категорий управляемы, тридцать превращаются в отдельный проект. Ограничьте список пятью группами: debug, info, warn и error, audit, security.

Компрессия логов: как сэкономить место и не потерять скорость поиска

Сжатие становится главным рычагом экономии после сокращения срока хранения. Elasticsearch хранит сегменты сжатыми по умолчанию: кодек LZ4 в настройке index.codec. Кодек best_compression включает DEFLATE и даёт меньший размер сегмента ценой более высокой нагрузки на CPU при поиске и при слияниях.

Влияние компрессии на скорость поиска

Порядок величин: gzip и zstd на текстовых логах дают сжатие в 3-10 раз, и это основной выигрыш в архивных выгрузках и в чанках Loki. Внутри Elasticsearch сегменты уже сжаты LZ4, поэтому переход на best_compression экономит обычно десятки процентов места, а не разы. Платите вы распаковкой блоков на каждом поисковом запросе: для аудиторских индексов, к которым обращаются несколько раз в месяц, разница в 100-300 мс на запрос незаметна, для отладки в реальном времени она ощутима.

Кодек задаётся при создании индекса. Сменить его можно только на закрытом индексе: сначала POST logs-audit-000001/_close, затем PUT logs-audit-000001/_settings с телом { "index.codec": "best_compression" }, затем POST logs-audit-000001/_open. Для аудиторских индексов задавайте кодек в шаблоне сразу, чтобы не закрывать индексы в продакшене.

В Loki тип сжатия чанков задаёт параметр chunk_encoding: значения по умолчанию и доступные кодеки отличались между 2.x и 3.x, поэтому сверьтесь с конфигурацией своей версии. zstd на логах обычно даёт лучший коэффициент при меньшем CPU, чем gzip, но требует поддержки на стороне всех читающих компонентов.

Измеряйте результат, а не верьте оценкам: размер и число документов по индексам показывает GET _cat/indices/logs-*?v&h=index,store.size,docs.count&s=store.size:desc. Сравните store.size до и после forcemerge на одном и том же наборе данных.

Чек-лист и мониторинг retention policy

  1. Для каждого типа логов определён срок хранения с обоснованием: требование регулятора, разбор инцидентов или отладка.
  2. В Elasticsearch созданы ILM-политики и index templates, rollover alias указывает на индекс записи.
  3. В Loki включён compactor с retention_enabled: true и заданным delete_request_store, значение retention_period проверено.
  4. Логи маршрутизируются по индексам или тенантам, а не пишутся в один общий поток.
  5. Настроен мониторинг свободного места и скорости роста логов, алерт срабатывает при остатке меньше 20%.
  6. Проверено, что удаление не затрагивает аудит: отдельный индекс или тенант с длительным сроком.
  7. Агенты переживают недоступность хранилища: буфер на диске и повторная отправка включены.

Команды для проверки и мониторинга

# Elasticsearch
GET _ilm/status
GET _ilm/policy/logs-90d
GET logs-debug-*/_ilm/explain
GET _cat/indices/logs-*?v&h=index,health,store.size,docs.count,pri
GET _cat/allocation?v

# Loki
curl -s http://localhost:3100/ready
curl -s http://localhost:3100/metrics | grep loki_compactor
df -h /var/loki

Алерт в Prometheus по свободному месту занимает четыре строки правил:

groups:
  - name: logs-storage
    rules:
      - alert: LogDiskLow
        expr: node_filesystem_avail_bytes{mountpoint="/var/loki"} / node_filesystem_size_bytes{mountpoint="/var/loki"} * 100 < 20
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Свободно меньше 20% под логи"

Для кластеров Elasticsearch 6.x и ранних 7.x, где ILM ещё не покрывал все сценарии, применяли Curator с фильтрами по возрасту и префиксу индекса и запускали его по Cron. В 8.x такая схема остаётся рабочей альтернативой, но дублирует ILM и требует отдельного контроля, поэтому выбирайте что-то одно.

Типичные ошибки и как их избежать

  1. Rollover alias не настроен или не помечен is_write_index. Симптом: индексы не ротируются, один индекс растёт до отказа диска. Решение: проверить алиас командой GET _cat/aliases/logs-* и переустановить его через POST _aliases.
  2. В Loki не включён compactor. Симптом: retention_period задан, но место не освобождается. Решение: выставить retention_enabled: true, delete_request_store и запустить compactor отдельной целью.
  3. Срок хранения аудита выставлен по аналогии с debug. Симптом: проверка безопасности через месяц не находит записей о доступе. Решение: отдельные индексы или тенанты для audit и security с фиксированными сроками и без фазы delete.
  4. Мониторинг диска отсутствует. Симптом: кластер уходит в read-only при 95% заполнения, и запись логов останавливается. Решение: алерт по свободному месту и метрике скорости роста индексов.
  5. Все логи пишутся в один индекс или в один тенант. Симптом: приходится выбирать между потерей аудита и переполнением диска. Решение: разделить потоки по типу на этапе отправки, а не после хранения.

Адаптация стратегии под ваш масштаб и стек

Стратегия хранения зависит от числа узлов и объёма логов, но логика одинакова: короткий hot, сжатый warm, дешёвый cold.

МасштабИнфраструктураЧто настроить
Один-два сервераLoki или Elasticsearch в Docker, диск локальныйдве-три политики на 7, 30 и 365 дней, retention по тенантам, алерт по df, без выделенных warm-узлов
Кластер 3-5 узловвыделенные hot и warm-узлы, объектное хранилище для чанковILM с фазами hot, warm, cold, forcemerge в warm, Loki с compactor и delete_request_store в S3
Десятки узлов, облаконесколько уровней хранения, кросс-кластерная репликацияотдельный кластер или тенант для аудита, cold в объектном хранилище с дешёвым классом, репликация критичных индексов

Выбор инструмента под задачу простой. Elasticsearch берите, если нужен полнотекстовый поиск по содержимому, агрегации и аналитика, и вы готовы платить дисковым пространством за инвертированный индекс. Loki берите, если важны экономия и связка с Grafana, а поиск идёт по меткам и регулярным выражениям. В крупных инсталляциях оба живут рядом: метрики и оперативные логи в Loki, аудит и разбор инцидентов в Elasticsearch.

Начните с одного расчёта: сколько гигабайт в сутки пишет основной индекс или поток, умножьте на требуемый срок хранения и сравните со свободным местом на дисках. Если результат превышает 70% доступного объёма, сокращайте срок для debug-логов и выносите аудиторские данные в объектное хранилище с дешёвым классом, пока место ещё есть.

Поделиться: