Пока серверов один или два, жизнь с журналами проста: зашёл по SSH, открыл папку /var/log, прогнал grep по нужному файлу. Пятый хост меняет картину. Записи живут в разных местах, ротация подтирает самое нужное, а сбой почти всегда размазан по двум-трём машинам сразу, и между окнами терминала приходится бегать вручную. Стек из Loki, Promtail и Grafana переворачивает механику: маленький агент на каждом сервере отдаёт строки в общее хранилище, а поверх него появляется одна поисковая строка сразу на все хосты. Ниже собран рабочий вариант на одной выделенной машине: установка и конфиг Loki, настройка агентов, приёмы запросов и разбор частых ошибок. Отдельно разобрана судьба Promtail, поддержку которого Grafana Labs прекратила 2 марта 2026 года, потому что об этом стоит знать каждому, кто выбирает стек сегодня.

Проблема логов разбросанных по серверам и цена поздней находки сбоя

Классическая сцена из практики: пользователи жалуются на медленную авторизацию, а расследование начинается в трёх разных окнах. На первом сервере журнал чист, на втором пусто, на третьем нужные строки уже сжаты ротацией и вот-вот исчезнут. Или проще: ночью на веб-фронте кончилось место, система молча обрезала журнал, и главный след аварии удалён раньше, чем его успели прочитать. Чем громче сбой, тем меньше следов он часто оставляет, и эту закономерность администраторы замечают на первых же дежурствах.

Обходной путь известен давно: цикл по хостам в одну строку.

for h in web-01 web-02 web-03; do
  ssh "$h" "grep -h ' 500 ' /var/log/nginx/access.log | wc -l"
done

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

Централизованный сбор убирает механическую часть работы. Строки со всех машин едут в одно место с меткой хоста и общим временем, поиск один, выдача сортируется по секунде. Вопрос "что случилось в 03:14" закрывается одним запросом вместо пяти терминалов. Ради этой разницы и затевают связку Loki, Promtail и Grafana.

Архитектура связки Loki, Promtail и Grafana без тяжёлой поисковой базы

Loki хранит журналы иначе, чем классические поисковые базы. Elasticsearch и его родственники индексируют каждое слово каждой строки, и за мгновенный полнотекстовый поиск платят памятью, процессорами и диском. Loki индексирует только метки: имя задания, хост, уровень важности. Сами строки сжимаются в блоки-чанки и лежат практически так, как записались. При запросе сначала срабатывает индекс меток, затем строки фильтруются по содержимому на лету. Для команды из нескольких человек и пары десятков машин такая экономия ценнее редкого поиска по миллиарду строк.

Promtail выполняет роль сборщика. Это компактный агент, который ставится на каждый сервер, следит за файлами журналов, приклеивает метки и отправляет строки в Loki по HTTP. Файл позиций хранит смещение в каждом прочитанном файле, поэтому перезапуск агента не порождает дублей.

Теперь о времени жизни инструмента, без этого картина неполная. Grafana Labs объявила Promtail завершённым проектом: с 2 марта 2026 года он признан достигшим конца жизненного цикла, коммерческая поддержка прекращена, новых выпусков не будет, вся разработка переехала в Grafana Alloy. Что это меняет на практике? Существующие установки продолжают работать, агент общается с Loki по прежнему протоколу, последние сборки доступны для скачивания. Для систем, которые строится с нуля, сразу стоит смотреть в сторону Alloy, тем более что настройки Promtail конвертируются в конфигурацию Alloy одной командой. Механика при этом не меняется: агент на сервере, метки, общее хранилище.

Grafana в этой связке рисует картину: графики, таблицы и режим Explore, интерактивную площадку для запросов. Роли в стеке простые: Loki хранит строки, агент собирает их с серверов, Grafana показывает человеку.

Установка Loki на центральный сервер с минимальным рабочим конфигом

Для центрального узла хватает виртуальной машины с парой гигабайт памяти, точные аппетиты зависят от объёма журналов. Актуальная ветка проекта имеет номер 3.7, выпуск 3.7.8 датирован сентябрём 2026 года. Проще всего запустить приёмник в контейнере:

docker run -d --name loki -p 3100:3100 \
  -v /opt/loki/config:/etc/loki \
  -v /opt/loki/data:/tmp/loki \
  grafana/loki:3.7.8 \
  -config.file=/etc/loki/local-config.yaml

Минимальная конфигурация одиночного узла умещается в один экран и повторяет официальный локальный пример:

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

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

Каждый блок решает конкретную задачу. Параметр auth_enabled отключает вход по логину, для закрытого сегмента сети между своими серверами это обычная практика. Тип хранилища filesystem складывает всё на локальный диск, для старта этого полностью хватает. Схема tsdb с версией v13 описывает формат индекса, принятый в третьей мажорной ветке Loki. Дата from обязана быть в прошлом, иначе приём записей не начнётся, об этом ниже отдельный пункт.

Проверка живости занимает секунду:

curl -s http://localhost:3100/ready

Ответ ready означает, что приёмник ждёт строки. Порт 3100 остаётся открытым только для своих машин, например отдельным правилом файрвола, и дальше наступает очередь агентов.

Настройка Promtail на каждом сервере и метки для поиска по хостам

На каждый сервер, откуда нужно собирать журналы, ставится Promtail: бинарник, файл настроек и юнит службы. Конфигурация сводится к трём вещам: адрес центрального приёмника, файл позиций и список журналов с метками.

server:
  http_listen_port: 9080
  grpc_listen_port: 0

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

clients:
  - url: http://10.10.10.5:3100/loki/api/v1/push

scrape_configs:
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          host: web-01
          __path__: /var/log/nginx/*.log

Метки job и host образуют скелет будущего поиска. На второй машине host меняется на web-02, на третьей на web-03, остальное совпадает буквально. Благодаря этому запрос по job соберёт все Nginx разом, а запрос по host покажет одну конкретную машину.

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

Службу агента описывает короткий юнит:

[Unit]
Description=Promtail collector
After=network-online.target

[Service]
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/promtail.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target

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

Подключение Grafana к Loki и поиск причины сбоя через LogQL

Grafana ставится рядом с Loki, в том числе контейнером:

docker run -d --name grafana -p 3000:3000 grafana/grafana

Первый вход выполняется логином admin и паролем admin, после чего система сразу просит сменить пароль. Дальше в настройках подключений добавляется Loki, в поле адреса повторяется хост приёмника с портом 3100, остальные поля пустые, вход по ключам отключён.

Язык запросов называется LogQL. Основа запроса это селектор меток в фигурных скобках, к которому добавляются фильтры строк:

{job="nginx"} |= " 500 "
{host="web-01"} |= "timeout"
{job="nginx"} |~ "error|crit"

Селектор по меткам использует индекс и срабатывает мгновенно, фильтры строк сужают выдачу по содержимому. Оператор |= оставляет строки с вхождением, оператор |~ принимает регулярное выражение.

Для подсчёта на селектор навешиваются функции:

sum by (host) (count_over_time({job="nginx"} |= " 500 " [5m]))

Так выглядит частота ошибок по всем хостам на одном графике. Хост, который первым пошёл вверх, и есть начало истории. Здесь главный выигрыш центрального хранилища перестаёт быть абстракцией: вместо ощущения "у кого-то что-то сломалось" получается конкретная машина в конкретную минуту.

Если Nginx пишет журналы в JSON, запросы становятся ещё точнее. Формат на стороне веб сервера настраивается так:

log_format jsonlog escape=json
  '{"time":"$time_iso8601","status":$status,'
  '"uri":"$request_uri","remote_addr":"$remote_addr",'
  '"request_time":$request_time,"upstream":"$upstream_addr"}';
access_log /var/log/nginx/access-json.log jsonlog;

И в Grafana строки разбираются по полям:

{job="nginx"} | json | status >= 500
{job="nginx"} | json | request_time > 1

Медленные запросы и ошибки ловятся отдельными строками, журналы старого текстового формата продолжают работать параллельно.

Ограничение срока жизни логов и контроль объёма хранилища

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

limits_config:
  retention_period: 168h

compactor:
  working_directory: /tmp/loki/compactor
  retention_enabled: true

168 часов это ровно неделя. Значение 720h даст месяц, 24h подойдёт для отладочного окружения. Компактор в фоне убирает блоки старше порога и сжимает индекс, вручную ничего чистить не нужно.

Объём стоит прикинуть до первого переполнения. Активно посещаемый сайт пишет единицы гигабайт сырых журналов в месяц, сжатие в чанки экономит место, но точная степень зависит от содержимого строк. Разумная практика: отвести диск с запасом и следить за графиком заполнения, не дожидаясь тревоги от мониторинга.

Ротация на самих серверах, откуда строки забираются, остаётся на месте. Она страхует на случай недоступности центрального узла: локальные копии никуда не деваются, а Promtail повторяет отправку, пока приёмник не ответит. Когда машин становится десятки, конфигурация перерастает одиночный сервер: хранилище переезжает в объектное S3-совместимое пространство, а Loki масштабируется горизонтально. На старте об этом думать рано, архитектура позволяет начать с малого.

Типичные ошибки при развёртывании Loki и их симптомы

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

  1. В метки попадают переменные значения вроде адресов посетителей, индекс распухает, и запросы, которые раньше отрабатывали мгновенно, начинают занимать секунды;
  2. Файл позиций Promtail сброшен или удалён, агент перечитывает журналы с нуля, и в хранилище всплывают дубли за прошлые недели;
  3. Часы на сервере приложений убежали вперёд, Loki бракует записи со слишком свежей меткой времени, и хост выглядит молчащим в выдаче;
  4. Одна машина отправляет слишком крупные пачки, приёмник отвечает кодом 429 и просит снизить темп, у хоста в Grafana появляются провалы;
  5. В схеме индекса указана дата из будущего, приём записей не стартует, а собственный журнал Loki ругается на схему при первом же запуске.

Диагностика почти всегда начинается с одного места: собственный журнал приёмника и агента. Каждая из перечисленных ошибок честно описана там словами, а не намёками. Обратная сторона той же медали: журналы самого Loki в сам Loki отправлять не стоит, петля из сообщений об ошибках способна заполнить хранилище за ночь.

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

Начать можно с одной машины и одного журнала Nginx: полчаса работы, а каркас уже стоит. Соседние хосты добавляются по одному, меткой за меткой. К моменту, когда поиск по общему хранилищу становится привычкой, возвращаться к grep в пяти терминалах уже не хочется.