Задача: централизованное логирование для группы сервисов — отказоустойчиво, на трёх машинах, в Docker Compose. Ниже — рабочая схема на Graylog 7 с DataNode, вход трафика через HAProxy с Keepalived и, самое интересное, методика расчёта ретенции: сколько дней логов мы реально можем хранить на дисках заданного размера. Методика выстрадана — однажды на одном из стендов диск заполнился, OpenSearch перевёл индексы в read-only, и приём логов встал. С тех пор ретенция у меня считается заранее, а не «по факту».
Архитектура
Три одинаковые машины (log-01…03, 10.0.0.21–23), на каждой три контейнера:
- graylog — приём, обработка и UI;
- graylog-datanode — управляемый OpenSearch (с Graylog 5.2+ это рекомендуемый способ: DataNode сам поднимает и конфигурирует OpenSearch, не нужен отдельный кластер);
- mongodb — конфигурация Graylog, replica set
rs01на те же три ноды.
Перед кластером — две машины с HAProxy и Keepalived: единый VIP (10.0.0.20) принимает и трафик Filebeat (Beats input, TCP 5044), и HTTP для веб-интерфейса, и балансирует на живые ноды. Агентам и людям не нужно знать про три ноды — только VIP.
Сеть — network_mode: host: контейнеры общаются напрямую по портам хостов, без
оверлеев и проброса портов. Для статичного набора из трёх машин это самый простой и
предсказуемый вариант; имена нод резолвятся через extra_hosts.
Подготовка хостов
На каждой машине:
| |
Ключ для аутентификации узлов MongoDB replica set (генерируется один раз, копируется на все ноды):
| |
Секреты — в .env рядом с компоузом (в git не коммитим):
| |
GRAYLOG_PASSWORD_SECRET обязан совпадать на всех нодах — им шифруются данные в MongoDB,
общей для кластера.
docker-compose.yml
Файл почти одинаков на всех трёх нодах — отличаются только имя своей ноды в
GRAYLOG_DATANODE_OPENSEARCH_NETWORK_HOST и адреса в GRAYLOG_HTTP_*_URI.
Полный файл для log-01:
| |
Отличия log-02/log-03: убрать сервис mongo-init, поменять
GRAYLOG_DATANODE_OPENSEARCH_NETWORK_HOST и оба GRAYLOG_HTTP_*_URI на своё имя.
Полезные детали, которые обычно узнают методом боли:
- healthchecks + depends_on выстраивают порядок: MongoDB (healthy) → DataNode (healthy) → Graylog. Без этого при перезагрузке хоста Graylog стартует раньше базы и падает;
- start_period: 120s у DataNode — он реально стартует долго, с коротким периодом healthcheck загонит его в restart loop;
- журнал 10gb/12h — буфер на диске: если OpenSearch недоступен, приём логов не останавливается, сообщения копятся в журнале;
- таймзона прописана везде (TZ, JAVA_OPTS, ROOT_TIMEZONE) — иначе время в UI, в логах JVM и в индексах живёт по-разному, и отладка превращается в квест.
Запуск
| |
Дальше — веб-интерфейс: при первом старте Graylog 7 проводит через preflight-настройку, где DataNode-ноды обнаруживаются автоматически (через общую MongoDB) и провижинятся в кластер OpenSearch. После этого System → Nodes должен показывать три ноды Graylog, System → Data Nodes — три DataNode.
Вход трафика: HAProxy + Keepalived
Две машины (lb-01, lb-02), между ними Keepalived с VIP 10.0.0.20. HAProxy
балансирует два потока: Beats (TCP 5044) от Filebeat и HTTP UI.
frontend beats_in
bind 10.0.0.20:5044
mode tcp
default_backend graylog_beats
backend graylog_beats
mode tcp
balance roundrobin
server log-01 10.0.0.21:5044 check
server log-02 10.0.0.22:5044 check
server log-03 10.0.0.23:5044 check
frontend web_in
bind 10.0.0.20:80
mode http
default_backend graylog_web
backend graylog_web
mode http
balance roundrobin
option httpchk GET /api/system/lbstatus
server log-01 10.0.0.21:9000 check
server log-02 10.0.0.22:9000 check
server log-03 10.0.0.23:9000 check
/api/system/lbstatus — специальный эндпоинт Graylog для балансировщиков: нода,
которая жива, но не готова принимать (например, догоняет журнал), отвечает не-200,
и HAProxy выводит её из ротации.
Filebeat на хостах приложений смотрит только на VIP:
| |
Beats input создаётся в Graylog как Global — тогда он слушает 5044 на всех нодах сразу.
Расчёт ретенции
Самая частая ошибка — задать ретенцию временем («храним 30 дней») и не сверить с дисками. Правильный порядок обратный: от размера диска — к количеству дней.
Вводные для примера: 3 ноды по 200 ГБ под данные.
Шаг 1. Бюджет хранения. OpenSearch следит за заполнением диска: на 85 % (low watermark) перестаёт размещать новые шарды на ноде, на 90 % (high) — начинает эвакуировать шарды, на 95 % (flood stage) — переводит индексы в read-only, и приём логов останавливается. Планировать нужно с запасом до нижней границы, плюс место для журнала Graylog и служебных данных. Берём рабочий потолок 75 %:
Бюджет = 3 × 200 ГБ × 0.75 = 450 ГБ на индексы (включая реплики)
Шаг 2. Скорость записи. Считается только по факту: запускаем кластер, льём
реальный трафик несколько дней и смотрим прирост индексов (System → Indices или
_cat/indices). Важно: размер данных в индексе ≠ размеру сырых логов — после
индексации объём обычно ×1.1–1.3. Допустим, намерили 12 ГБ/день прироста
primary-шардов.
Шаг 3. Реплики. Для отказоустойчивости replicas = 1: каждый индекс хранится
дважды, потеря любой ноды не теряет данные. Расход удваивается:
Расход = 12 ГБ/день × (1 + 1 реплика) = 24 ГБ/день
(На тестовых стендах, где потеря логов не страшна, replicas = 0 — и та же дисковая
ёмкость даёт вдвое больше дней.)
Шаг 4. Дни хранения:
Ретенция = 450 ГБ / 24 ГБ/день ≈ 18 дней
Шаг 5. Настройка index set. В Graylog (System → Indices → index set):
- Rotation strategy: Index Size — ротация по размеру, а не по времени: предсказуемый размер каждого индекса. Max index size: 10 ГБ;
- Retention strategy: Delete, max number of indices: 22 (22 × 10 ГБ × 2 копии = 440 ГБ ≤ бюджета 450 ГБ);
- Shards: 3 (по числу нод), replicas: 1.
Итог: кластер физически не может занять больше рассчитанного объёма, сколько бы трафика ни прилетело — при росте потока сокращается глубина хранения в днях, а не стабильность кластера.
Мониторинг. Даже с расчётом — следим за заполнением дисков (алерт на 70 %) и за приростом индексов: поток логов имеет свойство расти незаметно. Именно так у нас однажды и заполнился диск на стенде: за несколько недель приложения стали писать заметно больше, ретенция была задана «по времени», и OpenSearch дошёл до flood stage. С ротацией по размеру такой сценарий исключён by design.
Грабли
INSECURE_STARTUP: true— в этой конфигурации трафик между DataNode-нодами не шифруется. Допустимо в изолированном сегменте; для продакшена настраивайте TLS (DataNode умеет управлять сертификатами сам, см. документацию Graylog);PASSWORD_SECRETразошёлся между нодами — ноды видят друг друга, но не могут прочитать общие данные; симптомы странные, причина неочевидная. Секрет должен быть строго одинаковым;- mongo-keyfile с неправильными правами — mongod молча не стартует; нужны 400 и владелец uid 999;
- vm.max_map_count забыт — OpenSearch падает на старте;
- ротация по времени вместо размера — см. историю выше.