Логи Docker-контейнеров: драйверы, ротация и как не заполнить диск

По умолчанию Docker пишет stdout и stderr каждого контейнера в JSON-файл на хосте. Без явной настройки ротации этот файл растёт неограниченно — пока не заполнит диск. Проблема проявляется незаметно: контейнер работает месяцами, а в один момент docker logs возвращает гигабайты, или файловая система уходит в read-only.

Как Docker перехватывает вывод контейнера​


Когда процесс внутри контейнера пишет в stdout или stderr, Docker перехватывает этот поток через pipe. Демон dockerd читает данные из pipe и передаёт их активному logging-драйверу. Для json-file каждая строка сериализуется в JSON с метаданными (timestamp, stream) и дописывается в файл на хосте:

Код:
/var/lib/docker/containers/<container-id>/<container-id>-json.log

Это означает две вещи. Во-первых, если приложение внутри контейнера пишет логи в файл (а не в stdout), Docker их не увидит и ротация не сработает. Во-вторых, интенсивный вывод создаёт нагрузку на pipe и на дисковую подсистему хоста — при высоком throughput логирование становится узким местом.

Проверить, какой драйвер активен для конкретного контейнера:

Bash:
docker inspect --format='{{.HostConfig.LogConfig.Type}}' <container_name>

Ротация на уровне контейнера​


Параметры max-size и max-file управляют ротацией для драйвера json-file. max-size задаёт порог, при достижении которого текущий файл закрывается и создаётся новый. max-file — количество хранимых файлов (включая текущий). Старейший файл удаляется при превышении лимита.

Пример при запуске:

Bash:
docker run -d \
  --log-opt max-size=50m \
  --log-opt max-file=3 \
  --name myapp \
  myimage:latest

Здесь Docker хранит не более трёх файлов по 50 МБ каждый — суммарный потолок 150 МБ на контейнер.

В docker-compose.yml:

YAML:
services:
  myapp:
    image: myimage:latest
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "3"

Значения max-size принимают суффиксы k, m, g. Если указать число без суффикса, Docker интерпретирует его как байты.

Глобальные настройки через daemon.json​


Чтобы не дублировать --log-opt для каждого контейнера, задайте параметры ротации на уровне демона. Файл конфигурации — /etc/docker/daemon.json:

JSON:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

После изменения файла перезапустите демон:

Bash:
sudo systemctl restart docker

Глобальные настройки применяются ко всем новым контейнерам. Уже запущенные контейнеры сохраняют ту конфигурацию, с которой были созданы — для них нужно пересоздание (docker rm + docker run или docker compose up --force-recreate).

Приоритеты конфигурации​


Docker применяет настройки логирования по следующему правилу: параметры, заданные при создании контейнера (через --log-opt или секцию logging в compose), имеют приоритет над глобальными из daemon.json. Если контейнер создан без явного указания драйвера и опций, используются глобальные значения. Если и они не заданы — драйвер json-file без ротации.

Практический вывод: глобальный daemon.json — это страховка для контейнеров, которые запускаются без явных настроек (например, через docker run в скриптах или ad-hoc). Для критичных сервисов лучше дублировать параметры в compose-файле, чтобы конфигурация не зависела от состояния демона на конкретном хосте.

Драйвер local: сжатие из коробки​


Начиная с Docker 18.09 доступен драйвер local, который пишет логи в формате protobuf и сжимает их по умолчанию. Это существенно экономит место при большом объёме вывода.

JSON:
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3",
    "compress": "true"
  }
}

Ограничение: docker logs работает с драйвером local, но сторонние инструменты, которые парсят JSON-файлы напрямую из /var/lib/docker/containers/, не смогут их прочитать без Docker API.

Альтернативные драйверы​


ДрайверКуда пишетРотацияКогда использовать
json-fileЛокальный JSONmax-size / max-fileПростые сценарии, dev
localЛокальный protobuf + сжатиеmax-size / max-fileПродакшн без внешнего стека
syslogСистемный syslog-демонЗависит от syslogИнтеграция с rsyslog/syslog-ng
journaldsystemd journalЗависит от journaldХосты с systemd
fluentdFluentd-агентЗависит от FluentdЦентрализованный сбор логов
gelfGraylogЗависит от GraylogGraylog-инфраструктура
awslogsCloudWatch LogsПолитика CloudWatchAWS-окружения

При использовании syslog или journald ротация управляется соответствующим системным сервисом, а не Docker. Для journald лимит задаётся в /etc/systemd/journald.conf через SystemMaxUse. Для rsyslog — через правила в /etc/rsyslog.conf или /etc/rsyslog.d/.

Что происходит при заполнении диска​


Когда файловая система, на которой расположен /var/lib/docker/, заполняется на 100%, последствия каскадные:

  • Docker-демон не может создавать новые контейнеры и образы.
  • Запущенные контейнеры могут аварийно завершиться, если их процессы пытаются писать на диск (не только логи, но и данные).
  • dockerd сам может перейти в нестабильное состояние, потому что ему нужно место для внутренних операций.
  • Файловая система может переключиться в read-only (зависит от настроек errors= в fstab и типа ФС).

Если диск уже заполнен и Docker не отвечает, можно безопасно обнулить лог конкретного контейнера без его остановки:

Bash:
truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log

Это не удаляет файл и не ломает файловый дескриптор, который держит dockerd. После освобождения места демон обычно восстанавливает работоспособность без перезапуска.

Диагностика: найти контейнеры с раздутыми логами​


Быстрый способ оценить, сколько места занимают логи всех контейнеров:

Bash:
docker ps -q | xargs -I {} sh -c 'echo "$(du -sh /var/lib/docker/containers/{}/{}-json.log 2>/dev/null) {}"' | sort -rh | head -20

Или проще — найти файлы больше определённого порога:

Bash:
find /var/lib/docker/containers/ -name "*-json.log" -size +100M -exec ls -lh {} \;

Для оценки общего объёма:

Bash:
du -sh /var/lib/docker/containers/*/

Оценка необходимого пространства​


Чтобы подобрать max-size и max-file, оцените скорость вывода контейнера. Запустите на несколько часов в продакшн-режиме и замерьте рост файла:

Bash:
stat --format='%s' /var/lib/docker/containers/<id>/<id>-json.log

Повторите через час и вычислите разницу. Если контейнер генерирует 10 МБ/час, за сутки это 240 МБ. При max-size=100m и max-file=3 потолок — 300 МБ, что покрывает чуть больше суток. Для недельного запаса потребуется max-file=7 или больший max-size.

Типичные ошибки​


Ротация задана, но не работает. Параметры max-size и max-file в daemon.json применяются только к новым контейнерам. Если контейнер был создан до изменения конфига, его логи продолжат расти без ограничений. Решение — пересоздать контейнер.

max-size без max-file. Если указан только max-size, Docker по умолчанию хранит один файл (max-file=1). При ротации старый файл удаляется сразу. Это может быть приемлемо, но если нужен запас — задавайте оба параметра явно.

Логи в контейнере, а не в stdout. Если приложение пишет в файл внутри контейнера (например, /var/log/app.log), Docker-драйвер это не перехватит. Ротация max-size на такой файл не действует. Нужно либо перенаправить вывод в stdout, либо настроить ротацию внутри образа (logrotate, встроенные механизмы приложения).

docker logs блокирует терминал. Без флага --tail команда выводит весь накопленный лог. Для файлов в несколько гигабайт это занимает минуты и нагружает I/O. Используйте docker logs --tail 100 -f <container> для оперативного просмотра.

Смешивание драйверов в compose-файле. Если в одном compose-проекте часть сервисов использует json-file, а часть — journald, диагностика усложняется: нужно проверять разные места хранения. Единообразие упрощает мониторинг.

Миграция существующих контейнеров​


Если ротация не была настроена с самого начала, план действий:

  1. Зафиксировать текущие настройки: docker inspect <container>.
  2. Обновить daemon.json или compose-файл с нужными параметрами.
  3. Пересоздать контейнеры: docker compose up -d --force-recreate или вручную docker stop → docker rm → docker run.
  4. Убедиться, что новые контейнеры подхватили настройки: docker inspect --format='{{json .HostConfig.LogConfig}}' <container>.
  5. Удалить старые лог-файлы, если они остались (Docker обычно убирает их при удалении контейнера, но стоит проверить).

Пересоздание означает кратковременный простой. Для сервисов с restart policy always или unless-stopped это штатная операция, но для stateful-контейнеров убедитесь, что данные хранятся в volumes, а не в writable layer.

Проверка результата​


После настройки убедитесь, что ротация работает:

Bash:
## Проверить конфигурацию логирования контейнера
docker inspect --format='{{json .HostConfig.LogConfig}}' <container_name>

## Посмотреть список файлов логов
ls -lh /var/lib/docker/containers/<container-id>/

## Убедиться, что общий объём логов в пределах нормы
du -sh /var/lib/docker/containers/*/

Если в выводе ls видно несколько файлов вида <id>-json.log, <id>-json.log.1, <id>-json.log.2 — ротация активна и работает корректно.

Практический чек-лист​


  • Задать max-size и max-file глобально в /etc/docker/daemon.json для всех новых контейнеров.
  • Для критичных сервисов продублировать настройки в compose-файле, чтобы конфигурация не зависела от порядка развёртывания.
  • Рассмотреть драйвер local для продакшн-хостов с большим объёмом вывода.
  • Настроить мониторинг использования /var/lib/docker/containers/ (alert при превышении порога).
  • Убедиться, что приложения пишут в stdout/stderr, а не в файлы внутри контейнера.
  • Пересоздать старые контейнеры, которые были запущены до введения глобальной ротации.
  • Оценить скорость генерации логов и подобрать max-size × max-file с запасом на целевой период хранения.

Источники​


 
Назад
Верх Низ