Grafana Loki: лёгкое централизованное логирование без Elasticsearch
Когда объём логов у клиента не зашкаливает — до 50–100 ГБ в день, и не нужен сложный полнотекстовый поиск типа «найди всё по этому слову», мы почти всегда выбираем Loki: ресурсов он ест в разы меньше, а администрировать его — одно удовольствие, всего один бинарник и S3. На наших серверах Dell Xeon Platinum 8280 в дата-центре МТС Москва Loki спокойно обрабатывает логи десятка клиентских инфраструктур.
Зачем Loki, если есть ELK
- Loki индексирует только лейблы, а не само содержимое логов. Именно поэтому он в 10 раз компактнее, чем Elasticsearch. Подумайте, сколько это экономит!
- Один бинарь Go (
loki) и внешнее хранилище (FS, S3, GCS). - Отличная новость: Loki нативно интегрируется с Grafana. А ведь многие из вас уже и так используют Grafana для мониторинга метрик, верно?
- Синтаксис LogQL очень напоминает PromQL. Это значит, что порог входа для нового специалиста будет крайне низким. Быстрое освоение — это же супер!
Конечно, есть и минусы. Полнотекстовый поиск здесь медленнее. Если речь идёт о security-форензике по терабайтам данных, то тут Elastic пока удобнее, спору нет.
Архитектура
| Компонент | Функция |
|---|---|
| Promtail / Grafana Agent | Агент на источнике, читает файлы/Event Log |
| Loki | Приём, хранение, запросы |
| Grafana | UI, дашборды, алерты |
| MinIO / S3 | Хранилище чанков и индекса (для scale) |
Для небольших проектов — когда мы говорим о single-binary mode — всё крутится на одной машине, используя локальную файловую систему. Если же нужно масштабирование, то тут в дело вступает microservices mode, где компоненты вроде distributor, ingester, querier и compactor уже разносятся.
Установка Loki (single-binary)
# Docker Compose
services:
loki:
image: grafana/loki:3.1.0
ports: ["3100:3100"]
restart: unless-stopped
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:11.1.0
ports: ["3000:3000"]
restart: unless-stopped
volumes:
- grafana-data:/var/lib/grafana
environment:
GF_AUTH_ANONYMOUS_ENABLED: "false"
volumes:
loki-data:
grafana-data:
Минимальный loki-config.yaml:
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore: { store: inmemory }
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index: { prefix: index_, period: 24h }
limits_config:
retention_period: 720h
max_query_length: 720h
compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem
Promtail: агент на Linux-сервере
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: https://loki.company.ru/loki/api/v1/push
basic_auth: { username: "promtail", password: "${LOKI_PASS}" }
scrape_configs:
- job_name: system
static_configs:
- targets: [localhost]
labels:
job: varlogs
host: web-01
__path__: /var/log/*.log
- job_name: nginx
static_configs:
- targets: [localhost]
labels:
job: nginx
env: prod
__path__: /var/log/nginx/*.log
pipeline_stages:
- regex:
expression: '^(?P<remote>[^ ]+) .*? "(?P<method>GET|POST|PUT|DELETE) (?P<path>[^ ]+) HTTP/\d\.\d" (?P<status>\d+)'
- labels:
method:
status:
LogQL: поиск и агрегация
Синтаксис напоминает PromQL. Примеры:
{job="nginx"} |= "500"
# Только GET с кодом 5xx за последний час
{job="nginx", method="GET"} |~ " 5\\d\\d "
# Счётчик ошибок по сервисам
sum by (service) (
count_over_time({env="prod"} |= "ERROR" [5m])
)
# Топ-5 URL с 4xx
topk(5,
sum by (path) (count_over_time({job="nginx"} |~ " 4\\d\\d " [1h]))
)
Оператор |= — содержит подстроку, |~ — регулярка, != — не содержит, !~ — не матчит regex. | json — парсит JSON-поля на лету.
Дашборды и алерты в Grafana
Подключение Loki как источника — Configuration → Data Sources → Loki → URL http://loki:3100. В дашбордах используем LogQL-запросы, визуализация — Logs, Stat, Time series (с sum_over_time).
Алерты создаются через Grafana Alerting:
# Пример правила — более 10 ошибок за 5 минут
expr: |
sum(count_over_time({service="api", env="prod"} |= "ERROR" [5m])) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Много ошибок в API"
Масштабирование: когда single-node мало
Single-node конфигурация справляется примерно со 100 ГБ логов в день и 1 ТБ хранилища. Если объёмы растут, есть только один путь — microservices mode.
- Distributor — принимает пуш от Promtail, валидирует, отправляет в ingester.
- Ingester — буферизует в памяти, сбрасывает чанки в S3.
- Querier — выполняет запросы, читает S3 и ingester.
- Query Frontend — разбивает большие запросы на подзапросы.
- Compactor — объединяет чанки, применяет ретенцию.
Для работы в microservices mode нам, конечно, понадобится S3 или MinIO. Мы в ITFresh используем MinIO на наших серверах Dell Xeon Platinum 8280 с NVMe-дисками. Это даёт нам внушительные 40 Гбит/с пропускной способности. Отлично, правда?
Реальный кейс: логирование для интернет-магазина
В 2024 году к нам обратился один клиент — крупный онлайн-магазин детских товаров. У них была приличная инфраструктура: 18 серверов, из которых 10 веб-бэкендов, 3 базы данных, плюс DNS, почта и CDN-кешпулы. До нас ELK им настраивали уже дважды разные подрядчики. И что? Три ноды Elasticsearch постоянно захлёбывались, приходилось буквально каждый месяц перетряхивать конфиги. Мы решили проблему кардинально: заменили всё на Loki в режиме single-node, развернув его на обычной виртуалке с 8 vCPU, 16 ГБ ОЗУ и 1 ТБ NVMe.
За четыре рабочих дня мы установили Loki и Promtail на все 18 источников, настроили парсинг логов nginx (access и error), подключили Docker-контейнеры через docker driver и импортировали готовые дашборды. Итоговая стоимость проекта — 58 000 рублей. Клиент получил быстрый поиск по логам за 90 дней, алерты в Telegram и экономию ресурсов: в 5 раз меньше затрат, чем с ELK.
Грабли Loki
- Высокая кардинальность лейблов. Лейбл
request_idс миллионом значений = миллион стримов = смерть производительности. - Неудачные regex в pipeline_stages. Каждая строка обрабатывается — плохая regex тормозит Promtail.
- Compactor отключен. Без него ретенция не работает, старые данные не удаляются.
- Нет лимитов запросов. Один «жирный» запрос кладёт querier. Настройте
max_query_length,max_entries_limit_per_query. - Promtail не читает после рестарта. Проверьте
positions.yaml— там последний offset. Если файл потерян, всё читается заново.
Promtail снят с поддержки: переход на Grafana Alloy
Если вы поднимаете Loki сейчас, учтите: Promtail достиг конца жизненного цикла (EOL) 2 марта 2026 года. Обновлений и исправлений для него больше не будет, вся разработка агента перенесена в Grafana Alloy. Конфиги Promtail из этой статьи продолжат работать на уже установленных серверах, но новые инсталляции я делаю сразу на Alloy, а старые перевожу при ближайшем плановом обслуживании.
Переносить конфигурацию руками не нужно — в Alloy есть встроенный конвертер:
# Конвертация конфига Promtail в формат Alloy
alloy convert --source-format=promtail --output=/etc/alloy/config.alloy /etc/promtail/config.yml
# Если конвертер ругается на неподдерживаемые блоки —
# сначала смотрю отчёт, потом решаю, пропускать ли ошибки
alloy convert --source-format=promtail --report=/tmp/convert-report.txt \
--output=/etc/alloy/config.alloy /etc/promtail/config.yml
alloy convert --source-format=promtail --bypass-errors \
--output=/etc/alloy/config.alloy /etc/promtail/config.yml
Ключ --bypass-errors я использую только после чтения отчёта: он пропускает то, что не удалось сконвертировать, и итоговый конфиг может молча не собирать часть логов. После переключения сверяю в Grafana, что по каждому job логи продолжают поступать.
Ретенция по потокам и проверка, что компактор работает
Глобальный retention_period — не единственный вариант. В limits_config можно задать retention_stream с селектором по лейблам и приоритетом: например, отладочные логи хранить сутки, а логи безопасности — дольше общей нормы.
limits_config:
retention_period: 744h
retention_stream:
- selector: '{env="dev"}'
priority: 1
period: 24h
Ограничения, о которые спотыкаются чаще всего:
- Ретенция работает только при периоде индекса 24h в
schema_config. - В компакторе нужны
retention_enabled: trueи заданныйdelete_request_store— без него Loki не примет конфигурацию с включённой ретенцией. periodу правилаretention_streamне может быть меньше 24h.- Если
retention_periodне задан вовсе, действует значение по умолчанию0s— логи хранятся бессрочно, и диск рано или поздно заканчивается. - В селекторе допустимы только сравнения лейблов, произвольные выражения LogQL не поддерживаются.
Состояние сервера я проверяю через HTTP API и logcli:
curl -s http://localhost:3100/ready # готов ли Loki принимать запросы
curl -s http://localhost:3100/config | grep -A3 retention # какие параметры реально применены
export LOKI_ADDR=http://localhost:3100
logcli series '{job="nginx"}' --analyze-labels --since=1h # поиск лейблов с высокой кардинальностью
--analyze-labels выводит сводку по лейблам с числом комбинаций значений — так быстрее всего найти лейбл вроде request_id, который раздувает количество потоков.
FAQ — Grafana Loki
- Чем Loki лучше ELK?
- Loki индексирует только лейблы, а не содержимое логов — это раз в 10 экономит место и CPU. Проще в эксплуатации: один бинарь + S3 хранилище. Но полнотекстовый поиск медленнее, чем в Elasticsearch.
- Сколько ресурсов нужно Loki для 50 ГБ логов в день?
- Single-node: 4 vCPU, 8 ГБ ОЗУ, 500 ГБ диск (или S3). Масштабируемая схема (distributor/ingester/querier) оправдана от 500 ГБ/день. На NVMe ingester работает быстрее.
- Можно ли собирать Windows Event Logs в Loki?
- Да, через Grafana Agent или Promtail с виндовым экспортером wevtutil/windows_exporter. Поля Event Log преобразуются в лейблы и сообщение. Поиск по EventID удобен через LogQL.
- Как ограничить ретенцию в Loki?
- В config.yaml: limits_config.retention_period = 720h (30 дней), плюс compactor с retention_enabled=true. Можно задавать ретенцию по лейблам: разные длительности для security/app/debug.
- Что такое высокая кардинальность в Loki?
- Loki хранит индекс по уникальным комбинациям лейблов. Лейбл с высокой кардинальностью (user_id, request_id) создаёт миллионы стримов и убивает производительность. Такие значения должны быть в сообщении, а не в лейблах.
Источники
- Grafana Loki: Promtail agent — статус EOL Promtail с 2 марта 2026 года и переход на Alloy.
- Grafana Alloy: Migrate from Promtail — синтаксис alloy convert, ключи --report и --bypass-errors.
- Grafana Loki: Log retention — retention_stream, требования к компактору, период индекса 24h, значение 0s по умолчанию.
