Телефон чергового гуде. API повертає 500. Затримки нормальні, CPU ледве працює, пам’ять спокійна.
Потім ви бачите це: No space left on device. Не на хості, який ви ретельно розрахували — всередині файлової системи контейнера,
у стеку шарів, у логах, у «тимчасовій» директорії, що начебто мала бути тимчасовою ще в 2019 році.
Це найпоширеніший нудний інцидент у світі контейнерів. І він повністю запобіжний, якщо припинити ставитися до диска як до
бездонної ями і почати розглядати /tmp, логи та кеші як ресурси з бюджетами.
Ментальна модель: де фактично живе «диск контейнера»
Коли хтось каже «файлова система контейнера заповнена», зазвичай мають на увазі: записуваний шар, пов’язаний із контейнером,
зберігається на хості під драйвером зберігання Docker (зазвичай overlay2) і вичерпав місце на базовій файловій системі хоста.
Це за замовчуванням для змін у кореневій файловій системі контейнера. Ніякої магії — це дерево директорій і кілька монтувань.
Контейнери — це не віртуальні машини. Вони не мають власного блочного пристрою, якщо ви явно не дали його через томи, монтування або CSI.
Якщо не встановити бюджети зберігання, контейнер може охоче з’їсти диск хоста. Він такий ввічливий.
Цікаві факти та історичний контекст (бо від outage-ів у них є предки)
- Union-файлові системи передували Docker: overlay-стиль шарування виник у довгій історії Linux прагнення робити незмінні бази з записуваними верхніми шарами практичними.
- Ранні драйвери зберігання Docker були різні: aufs, devicemapper, btrfs, overlay, overlay2 — кожен з власними режимами відмови та поведінкою очищення.
- json-file логування стало «пасткою за замовчуванням»: воно було простим і працювало скрізь, тож тихо стало причиною багато чийого витоку диска.
- Ротація логів старша за контейнери: syslog і logrotate існують тому, що заповнення диска — класичний Unix-обряд ініціації.
- Кеші збірки вибухнули з сучасним CI: зі зростанням multi-stage збірок і кешування шарів, build cache оселився в production.
- Рантайми не винайшли тимчасове сховище: tmpfs існував завжди; ми просто постійно забуваємо використовувати його для справді тимчасових даних.
- Kubernetes підняв «ephemeral storage» до ресурсу, що впливає на розклад: здебільшого тому, що вузли вмирали через ріст логів і emptyDir.
- Семантика OverlayFS важлива: видалення файлу всередині контейнера не завжди «звільняє місце», якщо хтось тримає дескриптор відкритим (класична проблема з логами).
Є цитата, яку варто приклеїти на монітор:
Сподівання — не стратегія.
— часто цитують в операціях; ідея з практики надійності.
Жарт №1: Диск — це єдиний ресурс, який починається нескінченним, а потім раптово стає нульовим о 03:00.
Швидка діагностика — план дій
Це послідовність, яку я використовую, коли хтось пише «контейнер заповнив диск» і очікує від мене чаклунства.
Це не чаклунство. Це чек-лист із твердими правилами.
Перше: підтвердити, що саме заповнено
- Файлова система хоста? Якщо розділ хоста, що підтримує Docker, заповнений — все горить, навіть якщо видимо лише один контейнер.
- Область зберігання Docker?
/var/lib/docker(або ваш налаштованийdata-root) часто лежить на невідповідному розділі. - Том? Томи можуть заповнюватись окремо і не показуватимуться як використання шару контейнера.
- Виснаження інодів? Може бути багато байтів і нуль інодів. Маленькі файли — чудовий спосіб зіпсувати вихідні вихідні вихідні вихідні (жарт).
Друге: знайти категорію росту
- Логи (Docker-логи, логи додатка, access-логи, дампи для дебагу).
- Tmp (завантаження, розпаковані архіви, тимчасові файли OCR, scratch-простір обробки зображень).
- Кеші (кеші пакетних менеджерів, кеші залежностей, кеш рантаймів мов, кеші браузерів).
- Артефакти збірки (залишки CI, кеш збірки, шари).
- Дані, випадково записані в записуваний шар контейнера замість тому.
Третє: обрати найменш ризиковану «спускну клапан»
- Ротейшн / обмеження логів (краще без перезапуску).
- Видалити безпечні тимчасові директорії (з чіткою власністю та правилами за віком).
- Пронюхати непотрібні Docker-артефакти (цільове prune).
- Перемістити «гарячий шлях» на том або tmpfs (фактичне виправлення).
Вузьке місце не в «диску». Вузьке місце — відсутність бюджету і власника.
Ваше завдання: виявити власника і застосувати бюджет.
Практичні завдання: команди, виводи та рішення (12+)
Це реальні завдання, які я виконую під тиском. Кожне включає: команду, що означає вивід і яке рішення приймати далі.
Виконуйте їх на хості, якщо не вказано інше.
Завдання 1: Чи дійсно заповнена файлова система хоста?
cr0x@server:~$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda2 ext4 100G 96G 2.0G 98% /
tmpfs tmpfs 3.1G 4.0M 3.1G 1% /run
/dev/sdb1 ext4 500G 120G 355G 26% /data
Значення: / заповнений на 98%. Якщо Docker’s data-root на /, ви близько до зупинки.
Рішення: Підтвердіть, де Docker зберігає дані. Якщо це на /, першочергово робіть термінове очищення і плануйте міграцію на /data.
Завдання 2: Де Docker зберігає свої дані?
cr0x@server:~$ docker info --format 'DockerRootDir={{.DockerRootDir}} Driver={{.Driver}} LoggingDriver={{.LoggingDriver}}'
DockerRootDir=/var/lib/docker Driver=overlay2 LoggingDriver=json-file
Значення: Корінь Docker на /var/lib/docker (на /) з драйвером overlay2. Логи — json-file.
Рішення: Очікуйте, що overlay2 директорії і json-логи — основні підозрювані. Не поспішайте з «повним prune».
Завдання 3: Швидкий огляд розподілу дискового простору Docker
cr0x@server:~$ docker system df -v
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 12 18.4GB 6.2GB (33%)
Containers 36 18 4.8GB 1.1GB (22%)
Local Volumes 19 15 220.5GB 12.0GB (5%)
Build Cache 112 0 41.7GB 41.7GB
Значення: Томи домінують (220GB). Build cache великий і повністю відновлюваний. Контейнери додають ~5GB.
Рішення: Якщо потрібне швидке полегшення, build cache — низькоризикова ціль. Але також перевірте томи — хтось зберігає реальні дані.
Завдання 4: Знайти найбільші директорії під /var/lib/docker
cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
12G /var/lib/docker/containers
41G /var/lib/docker/buildkit
67G /var/lib/docker/overlay2
220G /var/lib/docker/volumes
340G /var/lib/docker
Значення: Томи і overlay2 важкі; buildkit суттєвий.
Рішення: Ідентифікуйте, які контейнери відповідають за ці томи. Не видаляйте дані томів, якщо не знаєте власника та політику зберігання.
Завдання 5: Які контейнери генерують гігантські Docker-логи?
cr0x@server:~$ sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s %p\n' | sort -n | tail -5
187654321 /var/lib/docker/containers/4c9f.../4c9f...-json.log
322198765 /var/lib/docker/containers/8a21.../8a21...-json.log
988877766 /var/lib/docker/containers/1b77.../1b77...-json.log
2147483648 /var/lib/docker/containers/aa11.../aa11...-json.log
4123456789 /var/lib/docker/containers/f00d.../f00d...-json.log
Значення: У вас багатогігабайтні json-логи. Це дисковий тиск з адресою.
Рішення: Ідентифікуйте імена контейнерів для цих ідентифікаторів, потім обмежте/ротейть логи. Не видаляйте файли навмання, якщо демон все ще пише.
Завдання 6: Смачна перевірка відповідності імені контейнера та образу
cr0x@server:~$ docker ps --no-trunc --format '{{.ID}} {{.Names}} {{.Image}}' | grep f00d
f00dbeefcafe1234567890abcdef1234567890abcdef1234567890abcdef api-prod myorg/api:2026.01
Значення: API-контейнер генерує великі логи.
Рішення: Розглядайте логи як симптом. Потрібно виправити голосність логування або цикл помилок.
Завдання 7: Перевірити розмір записуваного шару контейнера (швидкий тест)
cr0x@server:~$ docker ps -q | head -5 | xargs -I{} docker container inspect --format '{{.Name}} rw={{.SizeRw}} rootfs={{.SizeRootFs}}' {}
/api-prod rw=2147483648 rootfs=0
/worker-prod rw=536870912 rootfs=0
/nginx-prod rw=104857600 rootfs=0
/cron-prod rw=0 rootfs=0
/metrics rw=0 rootfs=0
Значення: SizeRw (записуваний шар) для api-prod ~2GB. Зазвичай це логи, tmp або випадкові записи даних.
Рішення: Виконайте exec в контейнері, щоб знайти, де знаходиться простір, або перевірте overlay2 mapping директорій.
Завдання 8: Всередині контейнера знайти найбільших споживачів диску
cr0x@server:~$ docker exec -it api-prod sh -lc 'df -h; du -xhd1 / | sort -h | tail -10'
Filesystem Size Used Available Use% Mounted on
overlay 100G 98G 2.0G 98% /
tmpfs 64M 0 64M 0% /dev
tmpfs 3.1G 0 3.1G 0% /sys/fs/cgroup
1.2G /var
1.5G /usr
2.8G /tmp
6.0G /app
9.1G /log
Значення: /tmp і /log великі. Це придатно до дій.
Рішення: Визначте, чи ці директорії мають бути tmpfs, змонтовані на томі або агресивно очищатися.
Завдання 9: Знайти великі окремі файли (часто core, дампи чи застряглі завантаження)
cr0x@server:~$ docker exec -it api-prod sh -lc 'find /tmp /log -type f -size +200M -printf "%s %p\n" | sort -n | tail -10'
2147483648 /log/app.log
536870912 /tmp/upload-20260103-0130.tmp
402653184 /tmp/image-cache.bin
Значення: Один лог — 2GB; тимчасовий файл завантаження — 512MB. Це не тонко.
Рішення: Обмежте логи (ротейшн). Для тимчасових завантажень забезпечте очищення після успіху/помилки і розгляньте tmpfs або виділений том з квотами.
Завдання 10: Перевірити, чи «видалені файли» все ще займають місце через відкриті дескриптори
cr0x@server:~$ sudo lsof +L1 | grep '/var/lib/docker/overlay2' | head
node 24781 root 21w REG 8,2 2147483648 0 /var/lib/docker/overlay2/7a2.../diff/log/app.log (deleted)
Значення: Файл видалено, але він все ще відкритий. Місце не звільниться, поки процес не перезапуститься або не закриє дескриптор.
Рішення: Перезапустіть контейнер (або перезавантажте процес) після впевненості, що він коректно підніметься. Також виправте ротацію логів, щоб це не повторювалося.
Завдання 11: Безпечне очищення build cache (зазвичай низький ризик)
cr0x@server:~$ docker builder prune --filter 'until=168h'
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
r1m2n3...
freed: 38.6GB
Значення: Ви відновили ~39GB, не торкаючись запущених контейнерів.
Рішення: Якщо це був головний споживач, заплануйте такий prune на CI-ранерах або вузлах збірки з політикою зберігання.
Завдання 12: Видалити невикористані образи (з обережністю)
cr0x@server:~$ docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:1a2b...
Total reclaimed space: 5.9GB
Значення: Старі образи займали місце; ви відновили ~6GB.
Рішення: На продакшн-вузлах prune образів може уповільнити розгортання і зламати відкат, якщо не обережні. Краще робити це на CI або контролювати стратегію розгортання.
Завдання 13: Ідентифікувати великі томи і зв’язати їх з контейнерами
cr0x@server:~$ docker volume ls -q | xargs -I{} sh -lc 'p=$(docker volume inspect -f "{{.Mountpoint}}" {}); s=$(sudo du -sh "$p" 2>/dev/null | awk "{print \$1}"); echo "$s {} $p"' | sort -h | tail -5
18G pgdata /var/lib/docker/volumes/pgdata/_data
22G redisdata /var/lib/docker/volumes/redisdata/_data
41G uploads /var/lib/docker/volumes/uploads/_data
55G elastic /var/lib/docker/volumes/elastic/_data
62G app-cache /var/lib/docker/volumes/app-cache/_data
Значення: Є том «app-cache» на 62GB. Це може бути легітимно, або неконтрольований ріст кешу.
Рішення: Перевірте, які контейнери монтують його, потім встановіть політику викидання/утримання. Кеш — не дані; ставтеся до нього як до витратного ресурсу з лімітом.
Завдання 14: Які контейнери монтують цей том?
cr0x@server:~$ docker ps --format '{{.Names}}' | xargs -I{} sh -lc 'docker inspect -f "{{.Name}} {{range .Mounts}}{{.Name}}:{{.Destination}} {{end}}" {}' | grep app-cache
/api-prod app-cache:/app/cache
/worker-prod app-cache:/app/cache
Значення: Дві служби діляться кеш-томом. Спільні кеші зручні і також чудово стають звалищем.
Рішення: Впровадьте обмеження розміру кешу в додатку і розклад очищення. Якщо кеш відтворюваний, розгляньте per-pod кеші або tmpfs.
Завдання 15: Перевірити вичерпання інодів (версія «много маленьких файлів»)
cr0x@server:~$ df -hi
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda2 6.4M 6.4M 12K 100% /
Значення: У вас закінчилися іноди, а не байти. Звичні причини: необмежені дрібні лог-файли, кеші з мільйонами записів або буря тимчасових файлів.
Рішення: Знайдіть директорію з найбільшою кількістю файлів і очистіть її. Розгляньте тонке налаштування файлової системи або перенесення DockerRootDir на FS з більшим числом інодів.
Завдання 16: Знайти директорії з шаленою кількістю файлів
cr0x@server:~$ sudo sh -lc 'for d in /var/lib/docker /var/log /tmp; do echo "## $d"; find "$d" -xdev -type f 2>/dev/null | wc -l; done'
## /var/lib/docker
1892345
## /var/log
45678
## /tmp
1203
Значення: Більшість файлів під Docker-ареєю. Overlay2 і логи контейнерів можуть вибухнути використання інодів.
Рішення: Звузьте пошук всередині /var/lib/docker, починаючи з overlay2 і containers. Якщо це build cache — пронюхайте його.
/tmp і тимчасові файли: стратегії очищення без випадкового видалення потрібного
Тимчасовий простір — місце, куди потрапляють добрі наміри. У контейнерах /tmp часто знаходиться на записуваному шарі, а це означає:
він конкурує з усім іншим за той самий диск хоста і живе довше, ніж ви думаєте (рестарти контейнера не обов’язково чистять його).
Стратегія: визначте, що значить «тимчасове» для вашого навантаження
- Справді еферемний scratch (обробка зображень, розпакування, сортування, невелике буферування): використовуйте
tmpfsз обмеженням розміру. - Великі тимчасові для завантажень (кілька ГБ): використовуйте виділений том або хост-шлях з квотами; не вірте, що tmpfs врятує вас тут.
- Черги робіт і spill-файли: робіть їх явними та спостережуваними; встановлюйте TTL і максимальний розмір.
Змонтировать /tmp як tmpfs (добре за замовчуванням для багатьох веб-застосунків)
У Docker run:
cr0x@server:~$ docker run --rm -it --tmpfs /tmp:rw,noexec,nosuid,size=256m alpine sh -lc 'df -h /tmp'
Filesystem Size Used Available Use% Mounted on
tmpfs 256.0M 0 256.0M 0% /tmp
Значення: /tmp тепер у RAM і обмежений до 256MB.
Рішення: Якщо ваш додаток використовує /tmp для невеликих scratch-файлів, це запобігає витокам на диск і робить помилки явними (схожими на ENOMEM), замість того щоб тихо заповнювати хост.
Для Compose:
cr0x@server:~$ cat docker-compose.yml
services:
api:
image: myorg/api:2026.01
tmpfs:
- /tmp:rw,noexec,nosuid,size=256m
Правило очищення: не робіть «rm -rf /tmp» у продакшні, мов злодій з мультфільму
Безпечний підхід — видалення за віком у директорії, що належить вашому додатку, а не глобальне видалення.
Нехай додаток пише у /tmp/myapp, тоді чистіть лише цей піддерево.
cr0x@server:~$ docker exec -it api-prod sh -lc 'mkdir -p /tmp/myapp; find /tmp/myapp -type f -mmin +120 -delete; echo "cleanup done"; du -sh /tmp/myapp'
cleanup done
12M /tmp/myapp
Значення: Файли старші за дві години видалені; директорія тепер 12MB.
Рішення: Якщо це повторюється, ваш додаток витікає тимчасові файли у помилкових шляхах. Виправте код, але залиште «мітлу» в операції.
Жарт №2: Ніщо не є більш постійним, ніж тимчасова директорія без власника.
Логи: Docker json-file, journald та логи застосунків
Логи — причина №1, чому «контейнери заповнили диск» з’являється в хроніках інцидентів.
Не тому що логування погане — а тому що логування без обмежень це довготривала DoS-атака проти власного диска.
Docker json-file: обмежуйте його або він вас потопить
Логування Docker за замовчуванням (json-file) пише логи по контейнерах під /var/lib/docker/containers/<id>/<id>-json.log.
Якщо не налаштувати ротацію, вони ростимуть нескінченно. Нескінченність довша за ваш диск.
Встановіть ротацію логів у /etc/docker/daemon.json
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
Значення: Кожен Docker-лог контейнера обмежений приблизно до ~250MB (5 файлів × 50MB).
Рішення: Застосуйте це на кожному хості. Потім перезапустіть Docker у вікні технічного обслуговування. Якщо не можете перезапустити — плануйте поступове розгортання; не ігноруйте це.
А що з journald?
Якщо Docker логуватиме до journald, ріст переміститься у системний журнал. Плюс: journald підтримує централізовані обмеження.
Мінус: ваш диск все одно заповниться — просто іншою формою файлів.
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 7.8G in the file system.
Значення: Журнали займають 7.8GB. Не катастрофа, але не безкоштовно.
Рішення: Встановіть утримання (SystemMaxUse, SystemMaxFileSize), якщо це поступово зростає.
Логи додатків всередині контейнера: визначте, де їм місце
Загалом бажано, щоб додатки логували в stdout/stderr і платформа займалася відправкою логів. Коли додатки пишуть у /log
всередині файлової системи контейнера, ви створюєте другу систему логування з іншими правилами утримання й більшою схильністю до тихого заповнення диска.
Якщо мусите писати файли (комплаєнс, батч-роботи, застарілі додатки), кладіть їх на том з явними політиками утримання і ротації.
І тестуйте ротацію: ротація логів — зміна, яка може щось зламати.
Безпечно скоротити файл, коли він занадто великий (лише в аварійному випадку)
cr0x@server:~$ sudo sh -lc ': > /var/lib/docker/containers/f00dbeefcafe*/f00dbeefcafe*-json.log; ls -lh /var/lib/docker/containers/f00dbeefcafe*/f00dbeefcafe*-json.log'
-rw-r----- 1 root root 0 Jan 3 02:11 /var/lib/docker/containers/f00dbeefcafe123.../f00dbeefcafe123...-json.log
Значення: Ви скоротили файл на місці. Docker може продовжувати писати без необхідності відкривати інший інод.
Рішення: Робіть це лише як тимчасовий захід під час інциденту. Реальне виправлення — ротація логів і зменшення їх обсягу у джерелі.
Кеші: пакетні менеджери, рантайми мов і артефакти збірки
Кеш — це функція продуктивності, яка перетворюється на баг зі зберіганням, коли ніхто ним не володіє. Контейнери підсилюють це, бо ті самі кеш-патерни,
що підходять для ноутбука розробника, стають неконтрольованим ростом на вузлі з десятками сервісів.
Типові точки росту кешів
- Пакетні менеджери ОС: apt, apk, yum кеші, залишені в образах або створені під час виконання.
- Рантайми мов: pip cache, npm cache, Maven/Gradle, Ruby gems, Go build cache.
- Кеші безголових браузерів: кеші Chromium можуть роздутися.
- Кеші на рівні застосунку: кеші мініатюр, зкомпільовані шаблони, кеші запитів, «тимчасові» експортні файли.
Усередині запущеного контейнера: знайти директорії кешів
cr0x@server:~$ docker exec -it worker-prod sh -lc 'du -xhd1 /root /var/cache /app | sort -h | tail -15'
12M /var/cache
420M /root
2.3G /app
Значення: Щось під /app велике; домашня директорія root — 420MB (часто кеш рантайму).
Рішення: Перегляньте піддерево /app на наявність кеш-директорій і вирішіть: чи має це бути том, чи обмеження розміру, чи видаляти при старті.
Запобіжіть осіданню кешів у записуваному шарі
Якщо кеш витратний і локальний, змонтуйте виділений том для кешу. Тоді він не роздує шар контейнера і його легше очищати.
Якщо потрібні жорсткі ліміти — розгляньте файлові квоти на шляху тому на хості (або storage classes у Kubernetes).
Також: не створюйте образи, що несуть кеші. Якщо ви використовуєте Dockerfile, очищайте кеші пакетів в тому ж шарі, де ви інсталюєте пакети,
інакше шар все одно міститиме старі дані. Це не моральний суд — це властивість шарових файлових систем.
Docker-артефакти: образи, шари, overlay2, build cache
Docker створює і зберігає багато речей: образи, шари, зупинені контейнери, мережі, томи, кеш збірки.
Правила зберігання навмисно консервативні, бо видалення неправильного об’єкта порушує робочі процеси. Ваше завдання — налаштувати ці правила під своє середовище.
Ріст overlay2: прихована вартість «писати в root»
Overlay2 зберігає зміни записуваного шару по контейнерах у директоріях під /var/lib/docker/overlay2.
Коли застосунок пише в /var/lib/postgresql або /uploads без тому, він пише в overlay2.
Це працює, доки вузол не помре від успіху.
Знайти контейнери з великими записуваними шарами
cr0x@server:~$ docker ps -q | xargs -I{} docker inspect --format '{{printf "%-25s %-12s\n" .Name .SizeRw}}' {} | sort -k2 -n | tail
/api-prod 2147483648
/worker-prod 536870912
/nginx-prod 104857600
Значення: Ці контейнери записують значні обсяги даних у записуваний шар.
Рішення: Для кожного вирішіть, чи записи повинні йти в stdout, tmpfs або іменований том. «Залишати всередині контейнера» — це не стратегія зберігання.
Prune: корисно, небезпечно і іноді те й інше
docker system prune — це бензопила. Зручно, коли ліс горить, але ви також можете відсікати можливість відкату.
У продакшні віддавайте перевагу вибірковим прунами:
- Prune build cache на вузлах збірки та CI-ранерах.
- Prune образів з урахуванням стратегії деплою й відкатів.
- Prune контейнерів лише якщо накопичуються зупинені контейнери без причини.
- Prune томів лише коли є сильні гарантії, що ви не видаляєте живі дані.
cr0x@server:~$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
3d2c...
Total reclaimed space: 1.0GB
Значення: Зупинені контейнери займали місце.
Рішення: Якщо регулярно бачите багато зупинених контейнерів, виправте процес розгортання або цикли крашу. Очищення — не коренева причина.
Томи і bind mount-и: «насправді не в контейнері» диск
Томи — це місце для довготривалих даних. Вони також — місце, куди кеші йдуть, щоб стати «випадково довготривалими».
Режим відмови передбачуваний: додаток потайки ростить директорію, диск вузла заповнюється, і всі звинувачують Docker.
Аудит томів, як аудит баз даних
Використання томів має операційну вагу. Встановіть алерти на це. Призначте власників. Якщо це кеш — встановіть викидання і максимальний розмір.
Якщо це довготривалі дані — робіть бекапи й політику утримання.
Bind mount-и: зручні і гострі
Bind mount-и можуть обійти кероване Docker зберігання і писати прямо в файлову систему хоста. Це може бути добре (відділення даних від DockerRootDir)
або погано (запис у / на маленькому root-розділі).
cr0x@server:~$ docker inspect api-prod --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume /var/lib/docker/volumes/app-cache/_data -> /app/cache
bind /data/uploads -> /uploads
Значення: Uploads змонтовано як bind до /data/uploads. Чудово — якщо /data великий і під моніторингом.
Рішення: Підтвердіть, що хост-шлях має квоти/алерти. Bind mount-и фактично «пишуть на хост», тож ставтеся до них відповідно.
Нотатки по Kubernetes: ephemeral storage і DiskPressure
У Kubernetes проблеми з диском зазвичай проявляються як DiskPressure, виселення подів і переведення вузлів у NotReady.
Під лежачими причинами ті самі: логи, emptyDir, записувані шари контейнерів, кеш образів і томи.
Kubernetes додає планувальник і гучніший режим відмови.
Ключовий операційний пункт: вказуйте request і limit для ephemeral storage
Якщо ви не вказали запити/ліміти для ephemeral storage, kubelet не зможе робити розумні рішення по розміщенню й буде вигнати поди, коли вузол вже страждає.
Це не планування ємності — це управління панікою.
emptyDir — не смітник без кришки
emptyDir без sizeLimit (і без моніторингу) — порожній чек.
Якщо навантаження потребує scratch-простору — встановіть бюджет. Якщо потрібно довготривале сховище — використайте PVC.
cr0x@server:~$ kubectl describe node worker-3 | sed -n '/Conditions:/,/Addresses:/p'
Conditions:
Type Status LastHeartbeatTime Reason Message
DiskPressure True Fri, 03 Jan 2026 02:19:54 +0000 KubeletHasDiskPressure kubelet has disk pressure
Значення: Вузол під дисковим тиском і почне виселяти.
Рішення: Негайно ідентифікуйте топ-споживачів (логи контейнера, образи, emptyDir, записувані шари) і зменшіть тиск. Потім додайте ліміти й алерти.
Три корпоративні міні-історії (реалістично, болісно, корисно)
1) Інцидент через неправильне припущення: «Контейнери еферемні, тож вони не можуть заповнити диск»
Середня компанія запустила API, пов’язаний з платежами, на кількох Docker-хостах. Додаток був без стану, тому всі вважали, що зберігання «не проблема».
Розгортання були з елементами blue/green: нові контейнери піднімались, старі зупинялися, і ніхто не дивився на зупинені, бо «вони не працюють».
За кілька місяців root-файлова система хостів тихо піднялася з комфортної до небезпечної. Насправді винуватець був нудний:
Docker json-логи на кількох балакучих контейнерах плюс десятки зупинених контейнерів, збережених «для дебагу пізніше».
Логи ніколи не ротувалися. У зупинених контейнерах були записувані шари з тимчасовими файлами, створеними під час піків запитів.
Аварія сталася під час рутинного деплою. Docker намагався підтягнути новий шар образу. Pull провалився з no space left on device.
Логіка оркестрації продовжувала намагатися, створюючи більше часткових завантажень і більше логів помилок. Тепер диск не лише був повний — він активно атакувався повторами.
Відновлення було інцидентним комодією: найшвидше вирішення — зупинити цикл повторів і пронюхати build cache та старі образи.
Але команда спочатку намагалася видаляти файли логів всередині контейнерів, не розуміючи, що Docker пише інакше. Вони мало що відновили й витратили час.
Коли ротували Docker-логи і пронюхали зупинені контейнери, вузол відновився.
Коренева причина не в «Docker». Це припущення, що еферемні обчислення означають еферемне зберігання.
Контейнери еферемні; файли, які вони створюють на хості, дуже зобов’язані до цього зв’язку.
2) Оптимізація, що обернулася проти: «Давайте кешувати все на спільному томі»
Інша організація мала дорогі запити: генерація PDF і рендеринг зображень. Хтось запропонував кешувати проміжні активи і результати, щоб повторні запити були швидші.
Це реалізували як спільний Docker-том на /app/cache, змонтований у API і воркер-контейнерах.
Продуктивність покращилась миттєво. Латентність впала, CPU знизився, команда похвалила себе за «масштаб без масштабування».
Кеш-директорія стала зіркою в щотижневому огляді метрик. Ніхто не питав, наскільки вона може зрости або як її очищувати.
Через місяць вузол почав флапати. Не CPU, не пам’ять — диск. Кеш-том зростав поступово, а потім різко,
бо нова фіча збільшила різноманітність ключів кешу й знизила hit-rate. Кеш перестав бути кешем і став другою базою даних,
але без компактування, TTL і бекапів.
Режим відмови був хитрим: коли том заповнився, записи перестали йти. Додаток трактував збої запису кешу як «cache miss»,
тож він перераховував важку роботу. Навантаження зросло, систему почало тягнути вниз, рівень помилок піднявся, кеш продовжував трястися.
Це оптимізація продуктивності, що перетворилася на баг надійності.
Виправлення було не героїчним: встановити максимальний розмір кешу, вилучення за LRU/віком і ставити до кеш-тома бюджет.
Також зробили кеш пер-воркер в окремих випадках, щоб уникнути спільної гарячої точки. Продуктивність залишилась приємною, а диск перестав бути тихим вбивцею.
3) Нудна, але правильна практика, що врятувала день: жорсткі ліміти логів і щотижневий огляд бюджету артефактів
Велика внутрішня платформа запускала сотні контейнерів на кластер. Вони були алергічні до інцидентів з диском, бо такі інциденти голосні, політичні
і зазвичай трапляються, коли керівники дивляться демо.
Їх підхід був агресивно нудним. Кожен вузол мав налаштовану ротацію Docker-логів за замовчуванням. Кожен workload мав документовану політику:
якщо ви логуватимете в stdout, отримаєте централізовану доставку; якщо логуватимете у файли, ви повинні ротувати і зберігати їх на виділеному томі.
У них був щотижневий «огляд бюджету артефактів»: топ томів, топ образів, топ build cache і найшумніші лог-продюсери. Не сесія звинувачень — просто гігієна.
Під час одного інциденту сервіс увійшов у цикл помилок і почав лити докладні стектрейс. В іншому середовищі це б заповнило диски
й вивело з ладу непов’язані сервіси. Тут обмеження Docker-логів стримало шкоду. Наблюваність не зникла: логи все ще надходили,
просто з ротацією й обмеженим зберіганням на вузлі.
Команда все ще мусила виправити баг. Але їм не довелося відновлювати флот з вичерпаним диском під час виправлення.
Ось реальна перевага нудних захисних заходів: вони купують вам час для справжньої роботи.
Поширені помилки (симптом → корінна причина → виправлення)
1) «Диск повний, але я видалив файли і нічого не змінилося»
Симптом: Ви видаляєте великі лог-файли, але df все ще показує мало вільного місця.
Корінна причина: Процес все ще тримає файл відкритим (deleted-but-open inode). Класично для лог-файлів.
Виправлення: Використайте lsof +L1, щоб знайти відкриті видалені файли, потім перезапустіть контейнер/процес або правильно ротируйте логи.
2) «Я виконала docker system prune і диск знову заповнився через добу»
Симптом: Тимчасове полегшення, а потім повторне виснаження диска.
Корінна причина: Ви лікували симптоми (артефакти), тоді як навантаження продовжує генерувати необмежені логи/tmp/кеші.
Виправлення: Додайте обмеження логів; змонтуйте tmp як tmpfs з розміром; впровадьте викидання кешу; перемістіть записи даних на томи з політикою утримання.
3) «Контейнер каже диск повний, але на хості є місце»
Симптом: На хості /data сотні ГБ вільно, але overlay контейнера показує 98% зайнято.
Корінна причина: DockerRootDir на / (малий), тоді як інші розділи великі.
Виправлення: Перенесіть Docker’s data-root на більшу файлову систему (планована міграція). Короткостроково: пронюхайте build cache/образи і обмежте логи.
4) «Ми налаштували ротацію логів, але логи все одно виростають»
Симптом: json-логи продовжують рости понад очікуване.
Корінна причина: Конфіг ротації застосований тільки до нових контейнерів; або використовується інший драйвер логування; або додаток пише логи у файли замість stdout.
Виправлення: Перевірте docker info на драйвер логування і опції по контейнеру. Перепроінсталюйте контейнери після змін у daemon config.
5) «Іноди досягли 100% при великій кількості вільних ГБ»
Симптом: df -hi показує 100% використання інодів.
Корінна причина: Мільйони дрібних файлів: кеш-директорії, розпаковані архіви, стихійне створення тимчасових файлів.
Виправлення: Ідентифікуйте ділянки з великою кількістю файлів; очистіть агресивно; перебудуйте кеш, щоб зменшити кількість файлів, або використайте FS з більшим числом інодів.
6) «Том росте, але це «лише кеш»»
Симптом: Кеш-том зростає без обмежень.
Корінна причина: Кеш без TTL/викидання; ключі стають практично унікальними; змінилося навантаження.
Виправлення: Впровадьте викидання і максимальний розмір у логіці додатку; розгляньте пер-інстанс кеші; додайте алерти і дашборди по тому.
7) «Джоб очищення видалив не ті файли»
Симптом: Додаток падає після cron-очищення.
Корінна причина: Очищення виконувалось широкими видаленнями (наприклад, /tmp або /var) без належних app-owned директорій і правил за віком.
Виправлення: Обмежте очищення до директорій, що належать додаткам; використовуйте видалення за віком; записуйте в виділені директорії; тестуйте в стейджингу з реалістичними навантаженнями.
Чек-листи / покроковий план
Покроково: термінове полегшення (утримати сервіси)
- Підтвердіть, що саме заповнено:
df -hTіdf -hi. Визначте, байти чи іноди. - Перевірте розподіл Docker:
docker system df -v, щоб побачити, чи то томи, build cache, образи чи контейнери. - Зупиніть кровотечу: якщо логи величезні — скоротіть їх на місці або перезапустіть винуватця після збору діагностики.
- Відновіть простір низьким ризиком: пронюхайте build cache; пронюхайте зупинені контейнери; пронюхайте невикористані образи, якщо стратегія розгортання дозволяє.
- Перевірте відновлення: переконайтесь у вільному просторі і що сервіси не в циклах повторів, які створюють додатковий дисковий шум.
Покроково: довготривале виправлення (зробити нудним)
- Налаштуйте глобальні обмеження Docker-логів (json-file max-size/max-file) і перепроінсталюйте контейнери.
- Перенесіть DockerRootDir з маленьких root-розділів. Помістіть його на виділене сховище, розраховане під артефакти й логи.
- Перестаньте писати довготривалі дані в записувані шари: вимагайте томи для всього персистентного.
- Використовуйте tmpfs для справжнього тимчасового простору: монтуйте /tmp як tmpfs з лімітами там, де це доречно.
- Впровадьте політики кешування в коді: TTL + max size + eviction. «Почистимо пізніше» — це не політика.
- Додайте алерти: використання файлової системи хоста, використання DockerRootDir, по томах, використання інодів і аномалії швидкості логів.
- Операціоналізуйте очищення: заплановані prune build cache на CI/вузлах збірки з ретеншен-вікнами і контролем змін.
Чого уникати (бо ви будете спокушені)
- Не видаляйте випадкові директорії під /var/lib/docker під час роботи Docker. Це чудовий спосіб пошкодити стан.
- Не використовуйте volume prune у продакшні якщо немає підтвердження, що томи не використовуються.
- Не робіть «rm -rf /tmp/*» якщо кілька процесів ділять /tmp. Майте власні тимчасові директорії.
- Не використовуйте prune як заміну політикам для системи, яка активно генерує необмежені записи.
Питання й відповіді
1) Чому видалення лог-файлу не звільняє місце?
Тому що процес може продовжувати писати у відкритий файловий дескриптор навіть після видалення шляху. Дисковий простір звільниться лише коли дескриптор закриється.
Використайте lsof +L1 і перезапустіть/перезавантажте процес.
2) Чи варто переключити Docker з json-file на journald?
Journald може централізувати утримання і інтегруватися з системними інструментами, але це не автоматичний виграш. Якщо вибираєте journald — встановіть обмеження для journald.
Якщо лишаєте json-file — задайте max-size і max-file. Оберіть одне і керуйте ним свідомо.
3) Чи безпечний docker system prune у продакшні?
«Безпека» залежить від ваших очікувань щодо розгортання/відкатів. Він може видаляти невикористані образи і build cache, що може сповільнити розгортання або видалити образи для відкату.
Краще цілеспрямовані prunes (build cache, зупинені контейнери) і агресивне очищення на CI-ранерах, а не на критичних prod-вузлах.
4) Як найкраще не допустити заповнення /tmp?
Якщо тимчасове використання невелике і справді тимчасове — монтуйте /tmp як tmpfs з обмеженням. Якщо тимчасове може бути великим — використайте виділений том з моніторингом і очищенням.
У всіх випадках додаток має писати у власну піддиректорію і очищати її за віком.
5) Як зрозуміти, чи записуваний шар контейнера — проблема?
Подивіться docker inspect SizeRw для контейнерів і всередині контейнера запустіть du -xhd1 /.
Якщо великі директорії знаходяться у місцях, що мали б бути томами або tmpfs — ви знайшли проблему.
6) Чому build cache стає таким великим?
Сучасні збірки генерують багато проміжних шарів і об’єктів кешу. BuildKit швидкий, бо зберігає роботу; він також ненажерливий.
Очищайте build cache за розкладом, особливо на спільних билдерах і CI-ранерах.
7) Що з вичерпанням інодів — як його уникнути?
Запобігайте створенню мільйонів дрібних файлів (дизайн кешів з багатьма файлами, тимчасові штормові події).
Моніторте іноди з df -hi. Розміщуйте файлові кеші на FS, пристосованих для великої кількості файлів або переробіть структуру кешу.
8) Чи можу я встановити обмеження диска на контейнер у plain Docker?
Docker не дає простого універсального «ліміту диска» як CPU/пам’ять. Можна наблизитись через tmpfs з лімітом, томи на файлових системах з квотами
і операційні правила (обмеження логів, ліміти кешу). У Kubernetes використайте ephemeral storage requests/limits.
9) Куди краще писати логи додаткам — stdout чи файли?
Stdout/stderr зазвичай правильний вибір для контейнерів: централізований збір, ротація платформою, менше складових.
Файлові логи прийнятні для специфічних вимог, але тоді ви відповідаєте за ротацію, утримання і бюджет диска.
Наступні кроки, які можна впровадити цього тижня
- Налаштуйте глобальну ротацію Docker-логів (json-file max-size/max-file) і перепроінсталюйте найбільш галасливі сервіси першими.
- Інструментуйте алерти по диску і інодам для файлової системи, що містить DockerRootDir, і для ваших топ-томів.
- Виявіть топ-3 споживачі диска за допомогою
docker system df -vіduпід DockerRootDir, потім призначте власників. - Перетворіть «temp» на tmpfs там, де це безпечно з явними обмеженнями розміру. Робіть помилки явними, а не заразливими.
- Впровадьте викидання кешу в коді і перевірте це під навантаженням. Якщо не можете пояснити максимальний розмір кешу — у вас немає політики кешування.
- Заплануйте очищення build cache на вузлах збірки/CI з вікном утримання, що відповідає ритму розробки.
Мета — не героїчні скрипти очищення. Мета — передбачувана поведінка зберігання. Контейнери легко створювати й вбивати.
Ваш диск — ні.