Логи Docker вибухають: виправте ротацію логів, поки хост не вийде з ладу

Було корисно?

Одного дня ваші контейнери працюють нормально. Наступного дня диск хоста “таємниче” закінчується, SSH повільний, і кожен деплой перетворюється на рулетку.
Ви перевіряєте /var/log як порядна людина. Це не винуватець. Тоді ви згадуєте темне місце: /var/lib/docker.

Це режим відмови, коли логи Docker ростуть, поки файловій системі не стає тісно. Це нудно, поширено й цілком запобіжно.
Виправте це правильно, і ваше майбутнє “я” не доведеться робити екстрену операцію на живому вузлі о 2:00 ночі.

Швидкий план діагностики

Коли диск заповнюється, вам не потрібна філософська лекція. Вам потрібні відповіді за кілька хвилин: що велике, що росте і що змінити, щоб це не повторилося.

По-перше: чи справді це логи Docker?

Перевірте використання файлової системи і підтвердьте, який монтування «палає». Якщо / заповнений, але Docker знаходиться на окремому розділі, не витрачайте час не там.
Якщо Docker живе на корені (поширено), ви зараз дізнаєтеся, чому це небезпечно.

По-друге: швидко знайдіть найбільших порушників

Визначте, які файли логів контейнерів величезні. Якщо один контейнер генерує гігабайти на годину, це не “виправлення ротації”, а логінговий DDoS.

По-третє: підтвердіть лог-драйвер і його ліміти

Якщо ви використовуєте json-file без max-size і max-file, логи ростимуть, поки диск не скаже “ні”.
Якщо ви використовуєте journald, обсяг логів переміститься до systemd-journald, і там теж потрібно ставити обмеження.

По-четверте: вирішіть, який тип виправлення потрібен

  • Негайне стримування: обнуліть великі логи, звільніть місце, зупиніть кровотечу.
  • Конфігураційне виправлення: встановіть глобальні значення демона, забезпечте ліміти, безпечно перезапустіть Docker.
  • Структурне рішення: направте логи в централізовану систему і перестаньте використовувати локальний диск як бездонну яму.

Що фактично зростає (і чому)

Поведінка Docker за замовчуванням обманливо проста: все, що ваш контейнер пише в stdout і stderr, захоплює Docker і записується кудись. За замовчуванням “кудись” у більшості Linux-систем — це лог-драйвер json-file.

З json-file кожному контейнеру відповідає файл логів (плюс ротовані файли, якщо ви налаштували ротацію) під
/var/lib/docker/containers/<container-id>/<container-id>-json.log. Цей файл росте. І росте. І продовжує рости, поки файлова система не буде повна — якщо ви не наказали Docker ротувати його.

Ось неприємна правда: «ми маємо моніторинг» нічого не дає, якщо немає обмежень. Сповіщення про диск спрацьовують, коли вже погано.
Ротація — це ремінь безпеки. Централізоване логування — подушки безпеки. Потрібні обидва.

Цитата, яку варто тримати на стікері:
Надія — не стратегія — часто приписується Gordon “Nick” Haskins (перефразовано).

Також: контейнери не роблять логування автоматично простішим. Вони роблять простішим створювати багато логів, швидко, з багатьох місць.
Якщо ваш сервіс балакучий, ваш хост стає щоденником, якого він ніколи не просив.

Жарт №1: Логи Docker — як безкоштовні наклейки на конференції — візьмеш забагато і раптом ноутбук не закривається.

Цікавинки та історичний контекст

  • Спочатку Docker просував ідею “логи в stdout” як чистий поділ: додатки виводять логи; платформа вирішує, куди їх направити.
  • Лог-драйвер json-file став дефолтним, бо він простий, автономний і не потребує зовнішніх залежностей.
  • Опції ротації Docker залежать від драйвера: прапорці, що працюють для json-file, не обов’язково застосовні до journald.
  • Kubernetes стандартизував логування контейнерів як файли на вузлі (зазвичай під /var/log/containers), що зробило ротацію на рівні вузла першочерговою операційною проблемою.
  • systemd-journald має власні механізми утримання і може зберігати логи в пам’яті, на диску або обидва — чудово, поки хтось не поставить ліміти.
  • Overlay-файлові системи змінили відчуття “використання диска”: у вас може бути багато місця в одному шарі й при цьому закінчитися місце на хості, що його підтримує.
  • Ранні контейнерні платформи часто виходили без чіткої політики зберігання логів, бо політика зберігання є бізнес-специфічною (комплаєнс, розслідування, вартість).
  • Багато інцидентів у продакшені, звинувачених на “Docker storage”, насправді були просто подіями повного диска, спричиненими необмеженими логами або режимом відладки, що вийшов з-під контролю.

Практичні завдання: команди, виводи, рішення

Нижче реальні завдання, які можна виконати на Linux-хості з Docker. Кожне включає: команду, що типовий вивід означає, і яке рішення треба ухвалити далі.
Мета — перетворити “диск заповнений” на контрольовану послідовність перевірок.

Завдання 1: Підтвердьте, яка файлова система заповнена

cr0x@server:~$ df -hT
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4   120G  116G  2.5G  98% /
/dev/nvme0n1p1 vfat   512M  8.0M  504M   2% /boot/efi
tmpfs          tmpfs   32G     0   32G   0% /dev/shm

Що це означає: Корінь на 98%. Якщо дані Docker знаходяться на /, ви в одному кроці від проблеми.

Рішення: Негайно знайдіть споживачів диска Docker; не починайте “прибирання” навмання.

Завдання 2: Швидко виміряйте розмір /var/lib/docker

cr0x@server:~$ sudo du -sh /var/lib/docker
87G	/var/lib/docker

Що це означає: Docker споживає більшу частину кореня. Це не обов’язково погано, але це сильний сигнал.

Рішення: Розбийте сховище Docker на контейнери, образи, томи й логи.

Завдання 3: Подивіться на облік сховища Docker

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        18        19.6GB    6.2GB (31%)
Containers      65        24        3.8GB     1.1GB (28%)
Local Volumes   14        12        9.4GB     0B (0%)
Build Cache     0         0         0B        0B

Що це означає: Образи/томи не є достатньо великими, щоб пояснити 87G. Ймовірно, причиною є логи або записні шари контейнерів.

Рішення: Почніть полювання на файли логів у каталогах контейнерів.

Завдання 4: Знайдіть найбільші файли логів контейнерів

cr0x@server:~$ sudo find /var/lib/docker/containers -name "*-json.log" -printf "%s %p\n" | sort -nr | head -n 5
32213455120 /var/lib/docker/containers/9b2c.../9b2c...-json.log
11422577664 /var/lib/docker/containers/12ad.../12ad...-json.log
2213478400  /var/lib/docker/containers/7a11.../7a11...-json.log
845312000   /var/lib/docker/containers/fe88.../fe88...-json.log
331776000   /var/lib/docker/containers/0c19.../0c19...-json.log

Що це означає: Принаймні один файл логів розміром 32GB. Це ваша витік місця на диску.

Рішення: Зіставте ID контейнера з ім’ям/сервісом, потім вирішіть: зараз обнулити чи одразу налаштувати ротацію назавжди.

Завдання 5: Зіставте ID контейнера з ім’ям і образом

cr0x@server:~$ docker ps -a --no-trunc --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' | head
CONTAINER ID                                                       NAMES                 IMAGE                   STATUS
9b2c0f0e0d6c2f7d4c3d1b1e8c...                                      api-prod              registry/app:2.8.1      Up 3 days
12ad77c2b12d8b1f21b9e8f2aa...                                      worker-prod           registry/worker:5.1.0   Up 3 days
7a11aa90b9d9d7c0c1e0fda2bb...                                      nginx-edge            nginx:1.25              Up 10 days

Що це означає: Великий лог належить api-prod. Тепер ви можете зв’язатися з потрібною командою або принаймні знати, на кого дивитися.

Рішення: Перевірте налаштування логування й швидкість росту логів; розгляньте негайне обнулення, якщо диск критично малий.

Завдання 6: Перевірте дефолтний лог-драйвер демона

cr0x@server:~$ docker info --format '{{.LoggingDriver}}'
json-file

Що це означає: Ви використовуєте json-file. Ротація не автоматична, якщо її не налаштувати.

Рішення: Перевірте, чи є у демона log-opts; якщо ні, додайте їх.

Завдання 7: Перегляньте поточну конфігурацію демона

cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
  "storage-driver": "overlay2"
}

Що це означає: Глобальних лімітів логів немає.

Рішення: Додайте log-driver та log-opts за замовчуванням, а потім перезапустіть Docker у контрольованому вікні.

Завдання 8: Перевірте ефективний шлях до логу контейнера (json-file)

cr0x@server:~$ docker inspect --format '{{.LogPath}}' api-prod
/var/lib/docker/containers/9b2c0f0e0d6c2f7d4c3d1b1e8c.../9b2c0f0e0d6c2f7d4c3d1b1e8c...-json.log

Що це означає: Це точний файл, який росте. Жодних здогадок.

Рішення: Якщо тиск на диск високий, безпечно обнуліть цей файл як стримуючий захід.

Завдання 9: Виміряйте швидкість росту логу (чи він зараз вибухає?)

cr0x@server:~$ sudo ls -lh /var/lib/docker/containers/9b2c.../*-json.log
-rw-r----- 1 root root 30G Jan  2 11:41 /var/lib/docker/containers/9b2c.../9b2c...-json.log
cr0x@server:~$ sleep 10; sudo ls -lh /var/lib/docker/containers/9b2c.../*-json.log
-rw-r----- 1 root root 30G Jan  2 11:41 /var/lib/docker/containers/9b2c.../9b2c...-json.log

Що це означає: Якщо розмір стрибнув помітно за секунди, сервіс активно спамить логами. Якщо стабільно — “вибух” міг статися раніше.

Рішення: Якщо він активно росте швидко, класифікуйте інцидент: знизьте рівень логування, обмежте швидкість або виправте цикл помилок/перезапусків.

Завдання 10: Обнуліть великий файл логу без перезапуску контейнера

cr0x@server:~$ sudo truncate -s 0 /var/lib/docker/containers/9b2c.../9b2c...-json.log
cr0x@server:~$ sudo ls -lh /var/lib/docker/containers/9b2c.../9b2c...-json.log
-rw-r----- 1 root root 0 Jan  2 11:44 /var/lib/docker/containers/9b2c.../9b2c...-json.log

Що це означає: Ви негайно звільнили місце. Контейнер продовжує працювати; Docker записує в той же inode.

Рішення: Це стримування, а не вирішення. Впровадьте ротацію наступного кроку, інакше ви повернетеся сюди знову.

Завдання 11: Підтвердіть, що диск звільнився і хост може дихати

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  120G   86G   29G  75% /

Що це означає: Ви виграли час. Тепер використайте його мудро.

Рішення: Застосуйте глобальну ротацію демона, а потім перевірте на новому контейнері.

Завдання 12: Налаштуйте ротацію json-file на рівні демона (справжнє виправлення)

cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  },
  "storage-driver": "overlay2"
}
EOF

Що це означає: Нові контейнери будуть ротуватися приблизно по 10MB і зберігати 5 файлів (приблизно 50MB на контейнер плюс накладні витрати).

Рішення: Перезапустіть Docker у контрольованому режимі; зауважте, що існуючі контейнери можуть потребувати пересоздання, щоб прийняти ці значення.

Завдання 13: Перевірте синтаксис конфігурації перед перезапуском

cr0x@server:~$ sudo jq . /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  },
  "storage-driver": "overlay2"
}

Що це означає: Валідний JSON. Docker не відмовиться стартувати через пропущену кому.

Рішення: Перезапустіть Docker, але розумійте радіус ураження: контейнери можуть перезапуститися залежно від вашої системи ініціалізації та оркестрації.

Завдання 14: Перезапустіть Docker і підтвердіть, що він запустився чисто

cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ systemctl is-active docker
active

Що це означає: Docker працює. Тепер перевірте, що налаштування логування застосовані для нових контейнерів.

Рішення: Запустіть тестовий контейнер і перевірте його конфіг логування.

Завдання 15: Перевірте, що новий контейнер успадковує log-opts

cr0x@server:~$ docker run --rm --name logtest alpine:3.20 sh -c 'i=0; while [ $i -lt 20000 ]; do echo "line $i"; i=$((i+1)); done'
line 0
line 1
line 2
...
cr0x@server:~$ docker inspect --format '{{json .HostConfig.LogConfig}}' logtest
{"Type":"json-file","Config":{"max-file":"5","max-size":"10m"}}

Що це означає: Ліміти ротації присутні в конфігурації контейнера.

Рішення: Заплануйте, як застосувати ці налаштування до існуючих довгоживучих контейнерів (зазвичай через їх пересоздання).

Завдання 16: Визначте, які контейнери не мають ротації (реальність змішаної флотилії)

cr0x@server:~$ for c in $(docker ps -q); do
  name=$(docker inspect --format '{{.Name}}' "$c" | sed 's#^/##')
  cfg=$(docker inspect --format '{{.HostConfig.LogConfig.Config}}' "$c")
  echo "$name $cfg"
done | head
api-prod map[]
worker-prod map[max-file:3 max-size:50m]
nginx-edge map[]

Що це означає: map[] зазвичай означає відсутність перезаписів на рівні контейнера. Залежно від версії Docker, це може означати успадкування дефолтів демона або збереження старих налаштувань. Ставте це під сумнів.

Рішення: Для критичних довгоживучих контейнерів явно встановіть опції логування або пересоздайте їх після налаштування дефолтів демона.

Виправте ротацію логів правильно

Правильне виправлення залежить від того, як ви запускаєте контейнери. Standalone Docker-хости — це один світ. Docker Compose — інший. Swarm ще існує (так, ще).
Kubernetes — свій всесвіт. Принцип однаковий: локальні логи повинні бути обмежені, і у вас має бути місце для логів, коли вони справді потрібні.

Глобальні дефолти демона: базовий рівень для кожного хоста

Якщо ви нічого більше не зробите, поставте глобальні ліміти в /etc/docker/daemon.json. Це запобігає “новий контейнер — новий необмежений файл.”
Також це робить хости передбачуваними у різних середовищах.

Розумний початковий вибір для багатьох сервісів:
10–50MB max-size і 3–10 max-file.
Менше — для сервісів з високою чутливістю; більше — якщо потрібна більша локальна історія для триажу.

Компроміс простий: менша ротація означає, що ви можете втратити старі логи локально. Це нормально, якщо є централізоване логування.
Це ризиковано, якщо локальні логи — ваш єдиний запис.

Персональні налаштування на контейнер: використовуйте обережно, але використовуйте

Для одного контейнера, що завжди балакучий (ingress, auth proxy, веб-сервер з access-логами), можливо, захочете явні перезаписи, щоб майбутня зміна дефолту демона не застала вас зненацька.

Через CLI Docker можна вказати:
--log-opt max-size=... і --log-opt max-file=... у docker run. У Compose можна задати опції логування для кожного сервісу.
Суть не в синтаксисі; суть у намірі. Зробіть “обмежені локальні логи” властивістю робочого навантаження, а не надією на хості.

Не використовуйте OS-level logrotate для Docker json логів як основне рішення

Люди пробують це, бо знають logrotate. Це знайоме. Це також неправильний рівень для логів контейнерів Docker.
Docker очікує, що він контролює ці файли; якщо ви зовнішньо ротуватимете їх, перейменовуючи, Docker може продовжити писати у старий файловий дескриптор.

Тримування (truncate) може допомогти у крайньому випадку. Перейменування — де починаються дивні речі. Якщо ви наполягаєте на ротації на рівні ОС, треба розуміти дескриптори файлів і copytruncate. Більшість команд не хочуть такого драматичного зв’язку у масштабі.

Жарт №2: Ротація логів — як чистка зубів зубною ниткою — пропустити здається нормальним, поки це не стає дорогим і особистим.

Зробіть ротацію примусовою, а не рекомендацією

Найпоширеніша організаційна помилка: один хост виправлено, але наступний — “тимчасовий”, а ще наступний — сніжинка, створена з чиєїсь пам’яті.
Ваше виправлення має бути задокументоване:

  • Конфігурне керування (Ansible, Puppet, Chef) або immutable образи з вбудованим daemon.json.
  • CI-перевірки, що відхиляють Compose або run-специфікації без обмежень логування.
  • Базовий SRE-рукбук: «Будь-який контейнер повинен мати обмежені локальні логи.»

Оберіть лог-драйвер свідомо

Лог-драйвер Docker вирішує, куди йдуть stdout/stderr. Це не косметична опція. Це операційний контракт:
продуктивність, надійність, зберігання та хто отримує пейдж, коли диск заповнюється.

json-file: просто, достатньо швидко, небезпечно без обмежень

Переваги: локальні файли легко інспектувати; не потребує systemd; працює всюди; інструменти очікують цього.
Недоліки: за замовчуванням необмежений; дублює роботу, якщо ви також відправляєте логи; може створювати багато записів на диск при великому навантаженні.

На практиці: json-file підходить, якщо ви його обмежуєте і відправляєте логи. Якщо ви обмежуєте і не відправляєте — ви свідомо обираєте втрату даних в обмін на безпеку хоста.
Це може бути правильним вибором, але треба усвідомлювати компроміс.

journald: централізовано на вузлі, але теж має межі

Переваги: узгоджено зі системними логами; багата метаданими; доступно через journalctl; підтримує обмеження через налаштування journald.
Недоліки: якщо ви не встановите ліміти journald, ви просто перемістили проблему заповнення диска; дивні наслідки при дебагу між перезавантаженнями та налаштуваннях збереження.

Якщо ви використовуєте journald, налаштуйте утримання журналів. Інакше ви отримали кращого колекціонера логів, а не безпечнішу систему.

Віддалені драйвери (syslog, fluentd, gelf, awslogs, splunk тощо): менше локальних файлів, більше мережевих залежностей

Переваги: логи покидають вузол — саме в цьому сенс; централізоване зберігання; аналіз без SSH.
Недоліки: зворотний тиск може зашкодити; мережеві відключення можуть втратити логи або блокувати контейнери (залежно від драйвера/налаштувань); операційна складність переноситься в пайплайн логування.

Дружній до продакшену підхід часто такий: локальний обмежений буфер + надійна відправка. Не ставте на кон своє реагування на інциденти на один мережевий хоп.

Аварійна реакція: хост заповнений диском, що далі

Коли хост на 100%, режими відмови множаться: Docker не може писати логи, контейнери падають, ядру не вистачає місця для базових алокацій, і “проста проблема з логуванням” стає інцидентом доступності.

Цілі стримування (у порядку пріоритету)

  1. Швидко звільнити місце, щоб відновити стабільність системи (обнулити найбільші логи).
  2. Зупинити джерело великого логування, якщо це аномальна поведінка (режим відладки, цикл падіння, буря винятків).
  3. Застосувати обмеження ротації, щоб той самий шаблон не заповнив диск знову.
  4. Зберегти достатньо доказів, щоб зрозуміти, чому це сталося (збережіть частину логів перед обнуленням, якщо можливо).

Чого не варто робити під час паніки через заповнений диск

  • Не видаляйте випадкові директорії під /var/lib/docker. Так можна “виправити логи”, але знищити середовище виконання.
  • Не запускайте docker system prune -a як рефлекс на продакшн-вузлі. Ви звільните місце, але також видалите образи, які потрібні для відновлення.
  • Не перезапускайте Docker повторно в надії, що він “щось почистить”. Перезапуски можуть викликати каскадні перезапуски контейнерів.

Якщо потрібно зберегти докази

Якщо комплаєнс або дебаг вимагають збереження шматка логів, захопіть tail перед обнуленням. Це дає останні кілька тисяч рядків без копіювання всього 30GB файлу.

cr0x@server:~$ sudo tail -n 5000 /var/lib/docker/containers/9b2c.../9b2c...-json.log > /root/api-prod-last-5000.jsonl

Що це означає: Ви зберегли останні події. Це не ідеально, але зазвичай достатньо, щоб побачити шаблон помилок.

Рішення: Обнуліть великий файл після збереження потрібного, потім виправте ротацію.

Три міні-історії з корпоративної реальності

Міні-історія 1: Інцидент через хибне припущення

Середня компанія запускала API для клієнтів на кількох потужних Docker-хостах. Команда сервісу була дисциплінована щодо метрик і трасування, і вони вважали, що логи — “проблема когось іншого”, оскільки на хостах стояв агент логування. Усім здавалося, що логи відправляються кудись і тому безпечні.

Припущення виявилося неправильним у конкретний спосіб: агент збирав файли під /var/log і деякі специфічні шляхи додатків, але не інгестив лог-файли контейнерів Docker під /var/lib/docker/containers. Контейнери старанно писали в stdout. Docker старанно писав json-логи. Ніхто їх не обмежував.
Агент не помітив. Логи залишалися локально. Назавжди.

Аварія не починалася з падіння API. Вона почалася з того, що корінь хоста досяг 100%. Після цього все почало брехати: сервіси падали з причин, що виглядали не пов’язаними — TLS-хендшейки таймаутили, чекери здоров’я флапали, контейнери перезапускалися, бо власні записі логів почали давати помилки.
Оперування бачило купу симптомів і бігало за привидами.

Виправлення було непримітним: обмежити json-file глобально, явно відправляти логи контейнерів і додати моніторинг росту /var/lib/docker/containers.
Важливіша культурна зміна була ще більш нудною: “логування існує в двох місцях — збір і утримання.”
Вони мали збір для деяких логів, але нічого для утримання.

Міні-історія 2: Оптимізація, що відкотилася

Інша організація втомилася від стрибків використання диска. Вони вирішили встановити max-size надзвичайно низьким по всій флотилії. Мислення було просте: захистити ноди, відправляти все централізовано і використовувати локальний диск лише як аварійний буфер.

Через два тижні реагування на інциденти почало давати збої в новому вигляді. Коли центральний пайплайн логування мав глюки (обслуговування, мережеве насичення або просто перевантажений індексатор), локальні логи були занадто малі, щоб перекрити розрив. Інженери SSH-илися на ноди під час аварії і знаходили, що останні локальні логи покривають лише хвилину-дві. Пайплайн був вниз, і локальний буфер майже порожній. Тріаж перетворився на здогадки.

Потім прийшов другий ефект: часті ротації створювали багато дрібних файлів і більше мета-операцій. На деяких нодах поєднання великого обсягу логів і дуже малих порогів ротації підвищило накладні витрати. “Оптимізація” не була катастрофічною, але зробила систему голоснішою і складнішою для розуміння.

Певний компроміс: збільшити max-size, щоб зберігати значущий локальний вікон для триажу, але залишити межу; зробити доставку логів надійнішою; і додати сповіщення про здоров’я лог-пайплайну. Ротація — не заміна надійного збору. Це страховий трос, а не трапеція.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Компанія, близька до фінансів, запускала контейнери на хостах, яких вони вважали “свинями”, а не “улюбленцями”. Команда SRE мала звичку, яка здавалася нудною у переглядах тикетів:
кожен базовий образ містив дефолти Docker-демона для логування, і кожен хост при підготовці проходив невелику валідацію — запуск тестового контейнера й перевірка його ефективного LogConfig. Ніхто цього не святкував. Це був просто вжеще один чекбокс.

Одного дня реліз вніс бурю винятків. Сервіс почав вивантажувати стеки помилок у великому обсязі. Це міг бути класичний інцидент “логи заповнили диск і все померло”. Але на цих нодах у кожного контейнера були жорсткі ліміти логування. Використання диска зросло, а потім вирівнялося.

Інцидент все одно був болючим: сервіс був нездоровий і потребував відкату. Але флот вузлів залишився стабільним. Ніяких каскадних відмов. Ніякої панічної прибирання, що ризикує видалити образи та зламати відновлення. Команда могла зосередитися на справжньому багу, а не гратися у прибиральника файлових систем.

Постмортем був майже нудним. І це комплімент. Нудна практика — примусові дефолти плюс маленький тест валідації — перетворила потенційний інцидент платформи на простий інцидент додатку. Ваші найкращі операційні перемоги виглядають як “нічого особливого не сталося.”

Поширені помилки (симптом → причина → виправлення)

1) Симптом: Коренева файлова система заповнюється кожні кілька днів

Корінь проблеми: драйвер json-file без max-size/max-file, плюс балакучі навантаження.

Виправлення: Встановіть дефолти демона в /etc/docker/daemon.json, безпечно перезапустіть Docker і при потребі пересоздайте довгоживучі контейнери.

2) Симптом: Ви налаштували ротацію, але старі контейнери все ще мають великі логи

Корінь проблеми: Існуючі контейнери можуть не прийняти нові дефолти демона; вони зберігають свою попередню LogConfig.

Виправлення: Для критичних сервісів явно вкажіть опції логування (Compose/Swarm) або пересоздайте контейнери після застосування дефолтів.

3) Симптом: Диск заповнений, але docker system df виглядає нормально

Корінь проблеми: Облік Docker не завжди відображає сире роздування файлів логів або реалії файлової системи.

Виправлення: Виміряйте безпосередньо за допомогою find і du під /var/lib/docker/containers; розцінюйте логи як першокласних споживачів диска.

4) Симптом: Ви перейшли на journald і диск все одно заповнюється

Корінь проблеми: journald утримує забагато; немає лімітів або сталого зберігання росте без меж.

Виправлення: Налаштуйте утримання journald (наприклад, system max use) і моніторте дискове використання журналу. Не “встановлюйте journald” і йдіть далі.

5) Симптом: Після зовнішньої роботи logrotate Docker продовжує писати у “видалений” файл і простору не звільнилося

Корінь проблеми: Файловий дескриптор все ще вказує на старий inode; перейменування/видалення не звільняє простір, поки процес не закриє дескриптор.

Виправлення: Віддавайте перевагу вбудованій ротації Docker. У надзвичайних випадках використовуйте truncate на активному шляху логу.

6) Симптом: Контейнери застопорюються, коли бекенд логування повільний

Корінь проблеми: Деякі лог-драйвери можуть застосовувати зворотний тиск; записи в stdout можуть блокуватися, якщо драйвер не встигає.

Виправлення: Тестуйте поведінку драйвера під навантаженням, забезпечте достатнє буферування і уникайте синхронного віддаленого логування без стійкості.

7) Симптом: Ви встановили max-size дуже маленьким і тепер не можете дебагувати інциденти

Корінь проблеми: Локальне вікно зберігання логів занадто маленьке; централізоване логування недостатньо надійне.

Виправлення: Збільшіть локальні ліміти, щоб зберегти корисне вікно, і зробіть пайплайн логування більш стійким і моніторним.

8) Симптом: Файли логів ротуються, але використання диска все одно повільно зростає

Корінь проблеми: Справжній винуватець може бути в іншому місці: writable шари overlay2, відвислі томи, кеш збірки або не-Docker логи.

Виправлення: Розберіть використання диска за допомогою du і docker system df, а потім проведіть цілеспрямований prune (із контролем змін).

Чеклісти / покроковий план

Крок за кроком: стабілізувати хост, що зараз заповнюється

  1. Підтвердьте, яка файлова система заповнена (df -hT).
  2. Знайдіть найбільші файли логів під /var/lib/docker/containers.
  3. Зіставте ID контейнера з сервісом і перевірте, чи він у циклі падіння чи режимі відладки.
  4. Збережіть невеликий tail, якщо потрібні докази (tail -n).
  5. Обнуліть головних порушників (truncate -s 0).
  6. Застосуйте дефолти логування демона і перевірте JSON.
  7. Перезапустіть Docker контрольовано (або заплануйте). Переконайтесь, що він активний.
  8. Пересоздайте або перевідкладіть найгірших порушників, щоб вони підхопили нову політику логування.
  9. Встановіть сповіщення по використанню диска і по швидкості росту логів (зміни розміру директорії), а не тільки “диск 90%”.

Базовий чекліст: кожен Docker-хост повинен відповідати цьому

  • Демон має явний log-driver і обмежені log-opts (або налаштоване утримання journald).
  • /var/lib/docker має бути розмірено для образів + томів + обмежених логів, а не “щоб що залишилося на корені”.
  • Моніторинг включає:
    • Сповіщення про використання файлової системи з розумними порогами
    • Сповіщення про використання inode (так, ротація логів може змістити проблему)
    • Сповіщення про ріст /var/lib/docker/containers
  • Моніторинг здоров’я пайплайна логування, якщо логи відправляються з вузла.
  • Рукбук містить безпечні команди: інспекція шляхів логів, обнулення, перевірка конфіг демона.

Чекліст управління змінами: безпечне розгортання нових налаштувань логування

  • Визначте дефолти (max-size, max-file) на основі прийнятного локального вікна зберігання.
  • Оновіть /etc/docker/daemon.json валідним JSON; перевірте через jq.
  • Виберіть метод розгортання:
    • Пересоздавання контейнерів поступово (переважно)
    • Технічне обслуговування хост за хостом з перезапуском
  • Перевірте: нові контейнери мають очікуваний HostConfig.LogConfig.
  • Підтвердіть, що використання диска стабілізується протягом кількох днів і що реагування на інциденти все ще має достатньо локальних логів.

FAQ

1) Чому логи Docker такі великі, коли моє додаток “не так багато логує”?

Ваш додаток може не “логувати” навмисно, але він може писати шумний stderr, повторювати стектрейси, друкувати access-логи при високому QPS або працювати в режимі відладки.
Також деякі бібліотеки логують значно більше під помилковими умовами (шторм повторних спроб — класичний випадок).

2) Чи видаляє встановлення max-size і max-file логи?

Воно ротуватиме і видалятиме старі шматки після досягнення ліміту утримання. Це і є видалення за дизайном. Якщо потрібне довше утримання — відправляйте логи централізовано.
Локальний диск — не архів.

3) Чи застосуються дефолти в daemon.json до запущених контейнерів?

Не надійно. Запущі контейнери зберігають свої налаштовані параметри логування. Розглядайте дефолти демона як “для нових контейнерів” і плануйте пересоздання довгоживучих робочих навантажень.

4) Чи безпечно обрізати json лог?

Так — як засіб стримування. truncate -s 0 зберігає файл і inode; Docker продовжує писати. Ви втрачаєте історію в цьому файлі, тож перед цим збережіть tail, якщо треба.

5) Чи варто переходити з json-file на journald?

Якщо ваш флот на базі systemd і команда звична з journalctl, journald може бути гарним вибором. Але встановіть ліміти journald. Інакше ви просто змістили точку переповнення.

6) Чи це важливо для Kubernetes?

Так. Kubernetes все ще покладається на обробку логів на вузлі. Логи рантайму контейнерів, поведінка kubelet і налаштування ротації вузла впливають на тиск на диск.
Ігнорувати це — значить отримати евакуації нод і флапи робочих навантажень. Інструменти інші, фізика та сама.

7) Чому не просто регулярно робити prune Docker?

Prune образів і кешів може допомогти, але це не вирішує кореневу проблему: необмежені записи логів. Також агресивний prune може уповільнити деплої і зламати шляхи відкату.
Виправте політику логування спочатку, потім чистіть свідомо.

8) Як обрати хороші значення ротації?

Визначте локальне вікно зберігання, з яким ви згодні під час простою лог-пайплайна. Для багатьох команд 30 хвилин до кількох годин логів — корисний буфер.
Перетворіть це на розмір на основі типової швидкості логів, потім встановіть ліміт. Почніть консервативно, спостерігайте, коригуйте.

9) Диск заповнений, але не можу знайти великі json логи — що ще перевірити?

Перевірте томи і writable шари (кеші додатків, завантаження, тимчасові файли), кеш збірки і будь-які інші директорії на тій же файловій системі.
Також перевірте вичерпання inode: безліч дрібних ротованих файлів може нашкодити навіть якщо простір виглядає доступним.

10) Чи може ротація логів погіршити продуктивність?

Надто часта ротація може додавати накладні витрати. Але вартість необмежених логів гірша: постійні записи на диск, високий I/O wait і врешті-решт інцидент.
Правильна ціль — “обмежено і нудно”.

Висновок: наступні кроки, які справді працюють

Якщо логи Docker вибухають, це не дивна крайність. Це відсутність запобіжного механізму. Диск хоста — не благодійність.
Виправлення просте: знайдіть файли логів, обмежте їх правильно і перестаньте вважати “stdout” за нескінченну яму.

Зробіть це в такому порядку:

  1. Сьогодні: знайдіть найбільші файли *-json.log; обнуліть найгірші, якщо тиск на диск високий.
  2. Цього тижня: встановіть дефолти ротації на рівні демона; перевірте на тестовому контейнері; пересоздайте критичні довгоживучі сервіси, щоб вони прийняли політику.
  3. Цього місяця: зробіть політику логування обов’язковою у процесі підготовки і CI; забезпечте здоров’я централізованого логування і моніторинг; ставте сповіщення по росту, а не тільки по заповненню.

Коли це буде впроваджено, інциденти через заповнений диск від логів зникнуть із вашого розкладу. Ваші майбутні пейджі будуть про реальні відмови, а не про платформу, що тоне у власному наративі.

← Попередня
ZFS: заміна кількох дисків — безпечний порядок, що запобігає відмові
Наступна →
Карта шляху ZFS: від першого пулу до продукції без жалю

Залишити коментар