Сбор логов Docker в 2026: драйверы логирования, Docker Compose, Vector и Grafana Loki | AdminWiki

Сбор логов Docker в 2026: драйверы логирования, Docker Compose, Vector и Grafana Loki

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

Централизованный сбор логов Docker в 2026 строится по одной схеме: контейнер пишет поток через драйвер логирования (json-file или local), агент Vector читает его через source docker_logs, добавляет метку сервиса из метаданных Compose и отправляет через sink loki в Grafana Loki, где записи ищутся в Grafana запросами LogQL. Имя сервиса берётся из метки com.docker.compose.service, поэтому оно не теряется при пересоздании контейнера.

Дальше по шагам: выбор драйвера (json-file, local, fluentd, GELF, journald, syslog), настройка блока logging в Docker Compose с ротацией, полный файл vector.toml от source до sink, хранение и запросы в Loki, ограничение дискового пространства и разбор ошибок с метками. Конфигурации рассчитаны на Docker Engine 27+, Compose v2, Vector 0.40+ и Loki 3.x.

Зачем централизовать логи Docker и что изменилось к 2026 году

Стандартный docker logs показывает записи живого контейнера и только на том хосте, где он запущен. Драйвер json-file по умолчанию складывает их в файл /var/lib/docker/containers/<id>/<id>-json.log, а параметры max-size и max-file в свежей установке Docker не заданы. Сервис с активным логированием переполняет раздел /var/lib/docker за считанные дни, а после пересоздания контейнера история исчезает.

Централизованный сбор закрывает три задачи: логи остаются доступными после удаления контейнера, поиск идёт по всем сервисам сразу, а на всплеск ошибок ставится алерт. Рабочая схема 2026 года: драйвер пишет журнал на хост, Vector читает его через Docker API, добавляет имя сервиса и отправляет в Grafana Loki, откуда записи попадают в Grafana.

Что теряется без централизованного сбора

Сценарий из практики: сервис shop-api отвечает 500-ми, дежурный перезапускает контейнер командой docker compose restart api, и причину сбоя смотреть уже негде. Драйвер json-file привязан к каталогу контейнера, поэтому пересоздание стирает историю. Типовые потери выглядят так:

  • логи разбросаны по хостам: в кластере из трёх нод нужно заходить на каждую;
  • в записях нет имени сервиса, только container_id;
  • нет общей точки поиска: запрос «все 500-е по проекту» выполнить нечем;
  • нет алертов и статистики: о проблеме узнаёте от пользователей.

Архитектура решения: от контейнера до Loki

Конвейер выглядит так:

Docker-контейнер -> драйвер логирования (json-file или local) -> Vector: source docker_logs -> transform remap -> sink loki -> Grafana Loki -> Grafana: Explore и алерты

Vector подключается к Docker двумя способами. Первый: source docker_logs забирает поток через Docker API, и compose-файл менять не нужно. Второй: драйвер fluentd отправляет логи по forward-протоколу в source fluent самого Vector, тогда на диск они не пишутся вообще.

Схема одинаково работает на одиночном хосте и в кластере: на каждой ноде поднимается свой Vector, а метка host различает источники. Общая архитектура конвейеров и сравнение агентов разобраны в материале про централизованный сбор и анализ логов в 2026.

Драйверы логирования Docker: сравнение и выбор

В контейнерных окружениях 2026 года реально используются шесть драйверов. Таблица показывает разницу, а детали ротации собраны в статье про драйверы и централизованный сбор логов Docker.

ДрайверКуда пишетРотацияКогда использовать
json-fileJSON-файл на хосте, одна строка на записьmax-size, max-fileдефолт, отладка, агенты, читающие файлы
localбинарный файл на хостеmax-size, max-fileпродакшен на одном хосте, экономия диска
fluentdforward-протокол в Fluentd, Fluent Bit или Vectorбуфер приёмника, fluentd-asyncцентрализованный приёмник, много хостов
gelfUDP или TCP в Graylogна стороне Graylogинфраструктура уже на Graylog
journaldjournald хостанастройки journald.confLinux со systemd, единый journal
syslogsyslog-сервер по UDP или TCPна стороне сервераlegacy-инфраструктура и SIEM

json-file и local: что выбрать для продакшена

json-file хранит одну JSON-запись на строку: {"log":"...","stream":"stdout","time":"2026-09-11T10:00:00.000000000Z"}. Формат читаемый, работает с docker logs, файл разбирается любым парсером. Ротацию включают опции max-size и max-file; без них файл растёт бесконечно.

Драйвер local появился в Docker 18.09 и пишет журнал в бинарном формате. Накладные расходы и объём данных ниже, чем у json-file, поэтому на одиночном хосте local выгоднее. Плата за экономию: файл на диске не читается как JSON, и агенты, которые разбирают каталог /var/lib/docker/containers напрямую, с ним не работают. Команда docker logs записи local показывает через Docker API, а вот сторонние парсеры файлов бессильны.

Правило выбора: json-file, если нужны прямой доступ к файлам и отладка; local, если важнее место на диске и логи забираются через API.

fluentd и GELF: когда нужен внешний приёмник

Драйвер fluentd отправляет записи по forward-протоколу. Настройка в Compose выглядит так:

services:
  api:
    image: shop-api:2026.09
    logging:
      driver: fluentd
      options:
        fluentd-address: "127.0.0.1:24224"
        tag: "docker.{{.Name}}"
        fluentd-async: "true"

Опция tag задаёт имя потока: шаблон {{.Name}} подставляет имя контейнера, например shop-api-1. Без fluentd-async: "true" драйвер блокирует запуск контейнера, если приёмник недоступен. С async записи буферизуются в памяти, по умолчанию около 1 МБ, и теряются при переполнении буфера.

Драйвер GELF отправляет сообщения в Graylog:

logging:
  driver: gelf
  options:
    gelf-address: "udp://graylog.example.com:12201"
    tag: "shop-api"

UDP не гарантирует доставку, а встроенного буфера у драйвера нет: при недоступном Graylog записи пропадают. Для биллинга и платежей берите TCP либо отправляйте логи через Vector, где настраивается disk-буфер.

Общее ограничение драйверов fluentd, GELF и syslog: команда docker logs с ними не работает, логи уходят по сети и на хосте не остаются. Дежурным придётся смотреть их в центральном хранилище.

Настройка логирования в Docker Compose

Драйвер задаётся для каждого сервиса через блок logging: ключ logging.driver выбирает драйвер, logging.options передаёт параметры. Дефолт для всех новых контейнеров хоста прописывается в /etc/docker/daemon.json.

Пример compose.yaml с ротацией и метками

services:
  api:
    image: registry.example.com/shop-api:2026.09
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    labels:
      env: "prod"
      log_collect: "true"
  worker:
    image: registry.example.com/shop-worker:2026.09
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    labels:
      env: "prod"
      log_collect: "true"

Разбор строк:

  • driver: json-file оставляет логи на хосте в читаемом виде, их заберёт Vector;
  • max-size: "10m" ограничивает один файл десятью мегабайтами, max-file: "3" хранит три файла, то есть до 30 МБ на контейнер;
  • блок labels попадает в метаданные контейнера: по ним Vector фильтрует поток и строит метки Loki.

Метки контейнера не появляются в тексте лога сами по себе, их читает агент через Docker API. Изменения в блоке logging применяются только к новым контейнерам, поэтому после правки выполните docker compose up -d --force-recreate.

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

Compose помечает контейнеры набором меток, которые видно через docker inspect:

  • com.docker.compose.project, имя проекта;
  • com.docker.compose.service, имя сервиса из compose-файла;
  • com.docker.compose.container-number, номер реплики;
  • com.docker.compose.config-hash, хеш конфигурации.

Проверить метку конкретного контейнера можно так:

docker inspect -f '{{ index .Config.Labels "com.docker.compose.service" }}' shop-api-1

Если контейнеры работают через драйвер fluentd, имя сервиса передаётся в теге: tag: "docker.{{.Name}}" даёт поток вида docker.shop-api-1, из которого приёмник извлекает сервис. В связке с Loki надёжнее другой путь: Vector берёт метку com.docker.compose.service и превращает её в метку потока Loki.

Сбор логов через Vector: конфигурация и фильтрация

Vector ставится из пакета дистрибутива или установочным скриптом и работает как один статический бинарник. Ему нужен доступ к /var/run/docker.sock. Сокет открывает полный доступ к Docker API, включая управление контейнерами, поэтому подключайте его только к доверенному агенту.

Рабочий конфиг целиком: источник читает логи контейнеров, transform добавляет сервис и разбирает JSON, sink отправляет всё в Loki.

# /etc/vector/vector.toml

[sources.docker]
type = "docker_logs"
docker_host = "unix:///var/run/docker.sock"
include_containers = ["shop-"]
exclude_containers = ["vector", "grafana"]
include_labels = { log_collect = "true" }

[transforms.enrich]
type = "remap"
inputs = ["docker"]
source = '''
.service = .label."com.docker.compose.service" ?? .labels."com.docker.compose.service" ?? .container_name
.env = .label."env" ?? .labels."env" ?? "prod"
.host = get_hostname!()

if contains(string!(.message), "GET /health") {
  abort
}

if starts_with(string!(.message), "{") {
  parsed, err = parse_json(.message)
  if err == null {
    if is_object(parsed) {
      .app = parsed
    }
  }
}
'''

[sinks.loki]
type = "loki"
inputs = ["enrich"]
endpoint = "http://127.0.0.1:3100"
encoding.codec = "json"
remove_label_fields = true
labels.service = "{{ service }}"
labels.env = "{{ env }}"
labels.host = "{{ host }}"

[sinks.loki.buffer]
type = "disk"
max_size = 268435456
when_full = "block"

Проверьте конфиг командой vector validate /etc/vector/vector.toml и запустите сервис. Живой поток событий показывает vector top.

Source docker_logs: чтение логов без драйвера

Source docker_logs забирает stdout и stderr контейнеров через Docker API, поэтому менять compose-файл не нужно. Фильтры:

  • include_containers и exclude_containers принимают префиксы имён, например shop- или vector;
  • include_labels и exclude_labels фильтруют по меткам контейнера, сюда подходит метка log_collect: "true" из Compose;
  • docker_host указывает сокет; если Vector работает в контейнере, сокет монтируется внутрь.

Ограничение: драйверы fluentd, GELF и syslog логи на хосте не хранят, и Docker API их не отдаёт. Для таких контейнеров источником становится сам приёмник, например source fluent в Vector.

Transform: добавление service_name и парсинг

VRL-скрипт из конфига делает три вещи. Достаёт имя сервиса из метки com.docker.compose.service, а если контейнер запущен без Compose, подставляет имя контейнера. Отбрасывает шум health-check через abort. Разбирает JSON-сообщения в поле app.

Не объединяйте распарсенный JSON с событием через merge: поля приложения затрут служебные service и host, и записи потеряют метку сервиса. Держите структуру в отдельном поле.

Если приложение пишет JSON, Vector обходится без регулярок и разбирает поля напрямую. Готовые конфиги логгеров для Python собраны в статье про структурированное логирование в Python для ELK Stack и Grafana Loki.

Sink Loki: отправка и метки

Sink loki отправляет события по HTTP в endpoint Loki. Шаблоны {{ service }} подставляют значения из полей события, из них строятся метки потока. Опция remove_label_fields = true удаляет служебные поля из тела записи, чтобы данные не дублировались.

Disk-буфер в конфиге, 256 МБ с политикой when_full = "block", переживает перезапуск Vector и недоступность Loki. При заполнении буфера block останавливает чтение, а drop_newest теряет новые записи. Для продакшена выбирайте block и следите за свободным местом.

Grafana Loki: хранение и запросы

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

{service="shop-api"} |= "error"
{service="shop-api"} | json | level = "error" | status >= 500
count_over_time({service="shop-api"} |= "error" [5m])

Срок хранения задаётся в конфиге Loki:

limits_config:
  retention_period: 744h

compactor:
  retention_enabled: true
  delete_request_store: s3

744 часа, это 31 день. Параметр delete_request_store нужен, если чанки лежат в объектном хранилище: без него compactor не удалит старые данные.

Метки Loki: что можно и что нельзя

Каждая уникальная комбинация меток создаёт отдельный поток (stream). Хорошие метки: service, env, host, cluster, уровень логирования. Плохие: request_id, user_id, trace_id, IP-адрес, точный timestamp. Запрос с меткой request_id создаёт поток на каждый запрос, и инжестеры Loki падают от нехватки памяти. Правило простое: в метку идёт то, по чему вы фильтруете постоянно, всё уникальное остаётся в теле записи.

В Loki 3.x есть structured metadata: высококардинальные поля вроде trace_id можно приложить к записи, не создавая новый поток. В индекс они не попадают, но участвуют в фильтрах.

Сравнение Loki, ELK и Graylog по стоимости и сложности собрано в статье ELK vs Loki vs Graylog в 2026.

Ротация логов и ограничение дискового пространства

Ротация на уровне драйвера работает по двум параметрам: max-size задаёт предел одного файла, max-file задаёт количество файлов. При достижении предела Docker удаляет самый старый файл. Всё, что сервис написал раньше окна ротации, остаётся только в центральном хранилище, поэтому Vector должен успевать забрать поток до перезаписи.

Бюджет места считайте заранее. Двадцать контейнеров с max-size: "10m" и max-file: "3" держат на диске 20 × 30 МБ = 600 МБ. Те же контейнеры с размером 100 МБ на файл и пятью файлами займут уже 10 ГБ.

Настройка ротации на уровне демона

Общий дефолт для всех новых контейнеров задаётся в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

После правки перезапустите демон: systemctl restart docker. Настройка действует только на контейнеры, созданные после перезапуска; работающие контейнеры сохраняют прежние параметры, пока их не пересоздать. Блок logging в Compose перекрывает дефолт демона.

Для драйвера local параметры те же: max-size и max-file работают и с ним, а место на диске расходуется экономнее.

Контроль: df -h /var/lib/docker показывает свободное место, du -sh /var/lib/docker/containers показывает занятое логами. Алерт на 80% заполнения раздела стоит дешевле, чем ночь разбора упавшего хоста.

Типовые ошибки с метками и атрибутами

  1. Высококардинальные метки в Loki. request_id или user_id в labels создают поток на каждое значение. Инжестеры упираются в лимит потоков, запросы падают. Решение: убрать поле из меток, оставить его в теле записи или в structured metadata.
  2. Потеря service_name при парсинге. Склейка события с распарсенным JSON через merge затирает поле service. Держите структуру в отдельном поле, например app.
  3. Дублирование логов. Драйвер fluentd отправляет логи в приёмник, а Vector в это же время читает тот же контейнер через API. Источник должен быть один: или драйвер, или docker_logs.
  4. Потеря записей при недоступном хранилище. Memory-буфер sink переполняется, и политика when_full решает судьбу событий. Для Loki, который перезапускается при обновлении, ставьте disk-буфер.
  5. docker logs перестал показывать логи. После переключения на драйвер fluentd, GELF или syslog команда не работает: записи уходят по сети. Предупредите дежурную смену и дайте ссылку на Grafana.

Чек-лист перед продакшеном

  1. Ротация включена: max-size и max-file заданы в daemon.json или в Compose.
  2. Запрос {service="shop-api"} в Grafana Explore возвращает записи.
  3. В метках Loki нет request_id, user_id и других уникальных значений.
  4. У sink Loki включён disk-буфер, проверен перезапуском Loki.
  5. Retention в Loki настроен: retention_period и compactor.
  6. Алерты стоят на заполнение раздела /var/lib/docker и на пропадание потока логов от сервиса.

FAQ: частые вопросы по сбору логов Docker

Можно ли использовать несколько драйверов одновременно?

Один драйвер на контейнер. Разные сервисы в одном compose-файле могут применять разные драйверы, а дефолт для новых контейнеров задаётся в daemon.json. Два источника на одном контейнере дадут дубликаты.

Как читать логи драйвера local через docker logs?

Команда работает через API Docker и показывает записи, несмотря на бинарный формат файлов. Прямой разбор файлов на диске при этом недоступен, для такого сценария используйте json-file.

Что делать, если Loki недоступен?

Vector с disk-буфером накапливает события на диске и повторяет отправку после восстановления. Проверьте, что в конфиге есть блок [sinks.loki.buffer] с типом disk и политикой when_full, а размер буфера покрывает время простоя Loki.

Как масштабировать схему на Kubernetes?

Vector запускается как DaemonSet, читает файлы из /var/log/pods, а имя сервиса берёт из меток pod, namespace и container. Структура конвейера та же: source, transform с метками, sink в Loki. Отличие одно: контейнеры пишут в отдельные файлы на каждой ноде, поэтому источник файловый, а не docker_logs.

Итог

Пять шагов, которые дают рабочую систему сбора логов:

  1. Выберите драйвер: json-file для отладки и чтения файлов, local для экономии диска, fluentd для отправки по сети.
  2. Включите ротацию: max-size 10m и max-file 3 для всех контейнеров.
  3. Пропишите logging и labels в compose-файле, пересоздайте контейнеры.
  4. Поднимите Vector с source docker_logs, transform с меткой сервиса и sink loki с disk-буфером.
  5. Проверьте запросы в Grafana, настройте retention и алерты.

Логи отвечают на вопрос «что случилось», метрики показывают «как давно и насколько плохо». Связка Prometheus и Grafana разобрана в руководстве про мониторинг Docker-контейнеров с Prometheus, Grafana и Loki. Если стенда ещё нет, разместите Loki и Grafana на VPS: например, Timeweb Cloud предоставляет облачные серверы и хранилище с гибким изменением ресурсов.

Поделиться:
Сохранить гайд? В закладки браузера