Docker «No Space Left on Device»: приховані місця, де Docker з’їдає ваш диск

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

Ви на виклику. Деплой не відбувся. CI червоний. Контейнери, які вчора запускалися без проблем, раптом відмовляються стартувати з помилкою no space left on device. Ви підключаєтесь по SSH, виконуєте df -h, і диск виглядає… відносно нормально. Або ще гірше: він повний, і ви не розумієте, що його заповнило, бо «ми запускаємо лише кілька контейнерів».

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

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

Це потік «викрутимося за 10 хвилин». Він пріоритезує перевірки, які скажуть вам, чи проблема в байтах диска, в інодах або в особливостях файлової системи (нюанси overlay, метадані thinp, квоти проектів).

По-перше: підтвердіть, що саме заповнене (байти проти інодів проти монту)

  • Перевірте вільні байти: df -h на відповідному монту (/, /var, /var/lib/docker та будь-якому виділеному диску для Docker).
  • Перевірте іноди: df -i. Якщо іноди на 100%, ви можете отримати «немає місця» при наявності гігабайтів вільного простору.
  • Підтвердіть Docker root: docker infoDocker Root Dir. Люди перевіряють / і забувають, що Docker на /var (або навпаки).

По-друге: визначте, яка категорія зростає

  • Власна звітність Docker: docker system df -v, щоб побачити образи, контейнери, томи та кеш збірки.
  • Реальність файлової системи: du -xhd1 /var/lib/docker (або вашого Docker root), щоб зрозуміти, де байти реально живуть. Числа Docker можуть відставати від реального стану, особливо щодо логів.
  • Логи: перевірте JSON-логи контейнерів або використання journald. Логи — найпоширеніший «ми про це не подумали» пожирач диска.

По-третє: усувайте проблему у найменш руйнівному порядку

  1. Зупиніть кровотечу: обертайте логи, обмежте драйвер логів або притримайте галасливі додатки.
  2. Звільніть безпечний простір: очистіть кеш збірки та dangling-образи. Уникайте знищення томів, якщо не впевнені.
  3. Вирішіть структурні проблеми: перемістіть Docker root на більший диск, додайте моніторинг, квоти, встановіть політику зберігання логів і виправте спаунер CI-білдерів.

Жарт №1: Диск як мінібар у готелі — ніхто не пам’ятає, що користувався ним, поки не прийшов час виїзду.

Що насправді означає «no space left on device»

Це повідомлення бреше шляхом опущення деталей. Воно може означати:

  • Немає вільних блоків на файловій системі, яка лежить під writable-слоем Docker, томом або тимчасовою директорією.
  • Немає вільних інодів (ви не можете створити нові файли, навіть якщо є байти).
  • Досягнута квота (квоти проектів, XFS-квоти або обмеження метаданих драйвера зберігання).
  • Метадані thin pool заповнені (поширено при старих налаштуваннях devicemapper).
  • Повний інший монтаж, ніж той, який ви перевіряли (наприклад, /var повний, а / — ні).
  • Обмеження файлової системи overlay, що проявляються як помилки простору (наприклад, занадто багато шарів або copy-up, що різко збільшує використання).

Операційно: трактуйте це як «ядро відмовило в виділенні». Ваше завдання — з’ясувати яке виділення і воно відбувалося.

Цитата, яку варто тримати на стікері в датацентрі:

«Надія — не стратегія.» — перефразована ідея, яку часто приписують лідерам з надійності в операціях

Якщо ваша стратегія управління диском — «попрунимо, коли болітиме», ви вже працюєте на надії.

Приховані місця, де Docker займає ваш диск

1) Записувані шари контейнера (overlay2): смерть від дрібних записів

Кожен запущений контейнер має записуваний шар. В overlay2 це структура директорій під приблизно такими шляхами:

  • /var/lib/docker/overlay2 (поширено)
  • /var/lib/docker/containers (логи та конфіг)

Записувані шари роздуваються, коли додатки пишуть у шляхи, які ви вважали епhemeral або винесеними. Класика:

  • Додатки пишуть у /tmp всередині контейнера, а ви думали, що це в пам’яті. Це не так, якщо ви не змонтували tmpfs.
  • Бази даних пишуть у /var/lib/postgresql/data всередині контейнера без іменованого тому або bind-монту.
  • Менеджери пакетів, рантайми мов або «корисні» перевірки оновлень пишуть кеші в /root, /var/cache, /home.

Особливий удар — copy-up в overlay: читання файлу з нижнього шару дешеве; його модифікація змушує скопіювати файл у записуваний шар. Доторк до великого файлу може продублювати його. Ось як «ми змінили невеликий конфіг» перетворюється на гігабайти.

2) JSON-логи: голосніші за всіх

Логування Docker за замовчуванням (json-file) записує логи кожного контейнера в:

  • /var/lib/docker/containers/<container-id>/<container-id>-json.log

Якщо не виставити ротацію логів, ці файли ростимуть вічно. І «вічно» в продакшені означає інциденти.

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

3) Іменовані томи: довговічні за дизайном, забуті за звичкою

Томи — місце, куди йде стан. Вони також переживають видалення контейнера. Ось у чому як сенс, так і пастка.

Розростання томів трапляється коли ви:

  • Використовуєте автогенеровані імена томів у Compose або CI і ніколи їх не чистите.
  • Запускаєте епhemeral тестові стеки, які створюють томи для кожного прогона.
  • Неправильно робите bind-mount і в результаті пишете в іменований том, якого не планували.

Важлива деталь: видалення образів рідко звільняє простір томів. Люди «docker rmi все» і все одно мають повний диск, бо диск — це томи.

4) Кеш збірки: BuildKit пам’ятає все

Сучасні збірки Docker (особливо з BuildKit) агресивно кешують шари. Це чудово для швидкості CI. Це також причина, чому ваші білдери стають сміттєзвалищем диска.

Кеш збірки росте через:

  • Мульті-стейдж збірки з багатьма кроками, які часто інвалідовуються.
  • Багато гілок і тегів, що продукують унікальні шари.
  • Завантаження пакетів, закешованих у шарах, помножене на варіації.

5) Невикористані образи: музей «можливо знадобиться»

Образи накопичуються, коли вузли виконують і роль рантайму, і роль збірки, або коли стратегія деплойменту скачує багато версій і ніколи їх не видаляє. У кластерах кожен вузол стає власним приватним музеєм «можливо корисних колись» шарів.

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

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

7) Тимчасові директорії поза Docker root: пастки /tmp і /var/tmp

Docker використовує тимчасові файли під час pull-ів, збірок і розпаковування. Залежно від конфігурації та змінних середовища тимчасові файли можуть опинятися в:

  • /tmp або /var/tmp на хості
  • приватному tmp systemd для служби docker

Отже ви можете заповнити /tmp і впасти непов’язані сервіси, навіть якщо /var/lib/docker на окремому диску.

8) Journald: «але ми не використовуємо json-file» (так, у вас все одно є логи)

Якщо Docker логить в journald, логи можуть розташовуватися не в директорії Docker взагалі. Вони накопичуються в сховищі journald (зазвичай під /var/log/journal), і підпорядковуються налаштуванням зберігання journald. Чудово—доки ваші налаштування логування не «зберігати все», а налаштування диска — «малі».

9) Краєві випадки драйверів зберігання: метадані devicemapper, квоти XFS та інші

Більшість сучасних інсталяцій використовують overlay2 на ext4 або XFS. Але старі середовища (або «ретельно збережені legacy-системи») все ще можуть використовувати devicemapper. При devicemapper часто спочатку спрацьовують обмеження метаданих, а не реальні обмеження диска — в результаті «немає місця», хоча диск не повний.

Квоти проектів XFS також можуть вас здивувати: Docker можна налаштувати на примусове обмеження по контейнеру. Добре, коли це навмисно; заплутано, коли це успадковано від AMI, який ви не перевірили.

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

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

Завдання 1: Визначте повну файлову систему

cr0x@server:~$ df -h
Filesystem                         Size  Used Avail Use% Mounted on
/dev/nvme0n1p2                      80G   62G   14G  82% /
/dev/nvme1n1p1                     200G  196G  4.0G  99% /var/lib/docker
tmpfs                               16G  1.2G   15G   8% /run

Значення: Ваш диск даних Docker повний (/var/lib/docker на 99%). Коренева файлова система — не головна проблема.

Рішення: Зосередьтеся на використанні Docker root; не витрачайте час на очищення /.

Завдання 2: Перевірка виснаження інодів (хитра помилка «немає місця»)

cr0x@server:~$ df -i
Filesystem                        Inodes   IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2                   5242880  841120 4401760   17% /
/dev/nvme1n1p1                  13107200 13107200       0  100% /var/lib/docker

Значення: Файлова система Docker витратила іноди, а не блоки. Часто трапляється з мільйонами дрібних файлів (node_modules, розпакування шарів образів, кеші збірки).

Рішення: Прунінг може допомогти, але в довгостроковій перспективі ймовірно потрібно створити файлову систему з більшою щільністю інодів (ext4) або перейти на XFS (динамічні іноди) і зменшити кількість дрібних файлів.

Завдання 3: Підтвердіть фактичний Docker root

cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker

Значення: Docker погоджується, що використовує /var/lib/docker.

Рішення: Всі наступні аналізи диска мають орієнтуватися на цей шлях (якщо ви не використовуєте альтернативний рантайм або rootless Docker).

Завдання 4: Отримайте високорівневу звітність зайнятості простору Docker

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          48        12        72.4GB    41.8GB (57%)
Containers      18        7         3.1GB     2.2GB (71%)
Local Volumes   64        9         88.6GB    55.0GB (62%)
Build Cache     214       0         61.3GB    61.3GB

Значення: Томи і кеш збірки домінують. Це не переважно «занадто багато контейнерів».

Рішення: Почніть з prune кешу збірки (зазвичай безпечно), потім ретельно перевірте томи перед очищенням.

Завдання 5: Поглиблена звітність Docker

cr0x@server:~$ docker system df -v
Images space usage:
REPOSITORY   TAG      IMAGE ID       CREATED        SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
app/api      prod     2a1b3c4d5e6f   2 days ago     1.21GB    820MB         390MB         4
app/api      old      7f6e5d4c3b2a   3 weeks ago    1.18GB    820MB         360MB         0

Build cache usage:
CACHE ID    CACHE TYPE    SIZE     CREATED         LAST USED      USAGE     SHARED
k9x...      regular       2.3GB    2 weeks ago     2 weeks ago    1
...

Значення: Ви помітите невикористані теги образів (0 контейнерів) і давні кеші.

Рішення: Видаліть невикористані образи та кеші в першу чергу; розгляньте політику зберігання останніх N версій на вузол.

Завдання 6: Знайдіть найбільші директорії під Docker root (перевірка реальності)

cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
1.1G    /var/lib/docker/network
3.6G    /var/lib/docker/containers
62G     /var/lib/docker/buildkit
112G    /var/lib/docker/overlay2
181G    /var/lib/docker

Значення: Overlay2 і buildkit — головні «важковаговики». Директорія containers також непогана за розміром (часто через логи).

Рішення: Якщо containers велика, перевірте логи. Якщо buildkit велика — пруньте кеш збірки. Overlay2 вимагає акуратного очищення через Docker, а не ручного видалення.

Завдання 7: Знайдіть найбільші файли логів контейнерів (драйвер json-file)

cr0x@server:~$ sudo find /var/lib/docker/containers -name "*-json.log" -printf "%s %p\n" | sort -nr | head
21474836480 /var/lib/docker/containers/4c2.../4c2...-json.log
 9876543210 /var/lib/docker/containers/91a.../91a...-json.log
 1234567890 /var/lib/docker/containers/ab7.../ab7...-json.log

Значення: Один контейнер записав ~20GB логів. Це вже не «трохи шумить». Це повідомлення про евакуацію диска.

Рішення: Негайно вкоротіть цей лог (тимчасово безпечно), потім впровадьте ротацію і виправте галасливий додаток.

Завдання 8: Безпечно вкоротити занадто великий лог контейнера без перезапуску Docker

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

Значення: Ви миттєво звільнили простір; файл тепер порожній. Контейнер продовжує писати логи.

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

Завдання 9: Підтвердіть, який контейнер відповідає за галасливу директорію логів

cr0x@server:~$ docker ps --no-trunc --format 'table {{.ID}}\t{{.Names}}'
CONTAINER ID                                                       NAMES
4c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b   api-prod-1

Значення: Найгірший порушник логів — api-prod-1.

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

Завдання 10: Перевірте використання дискового простору journald (якщо використовується драйвер journald)

cr0x@server:~$ journalctl --disk-usage
Archived and active journals take up 18.7G in the file system.

Значення: Journald займає значний простір. Це можуть бути Docker-логи, системні логи або й те, й інше.

Рішення: Встановіть обмеження зберігання в конфігурації journald і вакуумуйте старі логи. Не просто видаляйте файли під /var/log/journal, поки journald працює.

Завдання 11: Вакуум journald, щоб звільнити простір

cr0x@server:~$ sudo journalctl --vacuum-size=2G
Deleted archived journal /var/log/journal/7a1.../system@000...-000...journal
Vacuuming done, freed 16.7G of archived journals on disk.

Значення: Простір було безпечно звільнено засобами journald.

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

Завдання 12: Prune кеш збірки (зазвичай низький ризик, висока віддача)

cr0x@server:~$ docker builder prune --all --force
Deleted build cache objects:
k9x...
m2p...
Total reclaimed space: 59.8GB

Значення: Ви відновили майже 60GB, видаливши кеш збірки. Зборки можуть сповільнитись, доки кеш не прогріється знову.

Рішення: Якщо це CI-білдер, заплануйте періодичний prune або обмежте кеш політикою, замість панічного очищення.

Завдання 13: Prune невикористані образи (відносно безпечно, але розумійте стратегію деплою)

cr0x@server:~$ docker image prune -a --force
Deleted Images:
deleted: sha256:7f6e5d4c3b2a...
deleted: sha256:1a2b3c4d5e6f...
Total reclaimed space: 28.4GB

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

Рішення: На продакшн вузлах розгляньте зберігання останніх N версій або покладанняся на регістр з гарантованою доступністю та хорошим кешуванням.

Завдання 14: Знайдіть великі томи та хто їх використовує

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     api_db_data
local     prometheus_data
local     tmp_ci_run_1738

cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/*/_data | sort -h | tail
6.2G  /var/lib/docker/volumes/prometheus_data/_data
48G   /var/lib/docker/volumes/api_db_data/_data
71G   /var/lib/docker/volumes/tmp_ci_run_1738/_data

Значення: Один тимчасовий CI-том — 71GB. Ймовірно це сміття. Том бази даних великий, але, ймовірно, легітимний.

Рішення: Перевірте прив’язки перед видаленням: ідентифікуйте, які контейнери використовують тимчасовий том. Не видаляйте томи баз даних легковажно.

Завдання 15: Зіставте томи з контейнерами (щоб уникнути видалення живого стану)

cr0x@server:~$ docker ps -a --format '{{.ID}} {{.Names}}' | head
a1b2c3d4e5f6 api-prod-1
d4e5f6a1b2c3 ci-runner-1738
...

cr0x@server:~$ docker inspect -f '{{.Name}} -> {{range .Mounts}}{{.Name}} {{end}}' d4e5f6a1b2c3
/ci-runner-1738 -> tmp_ci_run_1738

Значення: Великий тимчасовий том належить конкретному CI-runner контейнеру (можливо вже зупиненому, можливо ще використовуваному).

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

Завдання 16: Видаліть підтверджений сирітський том

cr0x@server:~$ docker rm -f ci-runner-1738
ci-runner-1738

cr0x@server:~$ docker volume rm tmp_ci_run_1738
tmp_ci_run_1738

Значення: Контейнер і том видалені; простір на диску має зменшитися.

Рішення: Додайте автоматизацію життєвого циклу для артефактів CI, щоб «тимчасові томи» не ставали постійними мешканцями.

Завдання 17: Одна команда для очищення очевидного сміття (використовуйте з розумом)

cr0x@server:~$ docker system prune --all --volumes --force
Deleted Containers:
...
Deleted Images:
...
Deleted Volumes:
...
Total reclaimed space: 132.6GB

Значення: Ви щойно видалили майже все невикористане, включно з томами. Це може бути катастрофічно, якщо ви помилково класифікували «невикористане».

Рішення: Використовуйте це лише на одноразових хостах (CI, dev-білдери) або коли перевірили безпеку томів. У продакшені віддавайте перевагу прицільному прунінгу.

Завдання 18: Перемістити Docker root на більший диск (доросле вирішення)

Коли ви постійно пруните, ви лікуєте симптоми. Іноді потрібно просто перемістити дані.

cr0x@server:~$ sudo systemctl stop docker
cr0x@server:~$ sudo rsync -aHAX --numeric-ids /var/lib/docker/ /mnt/docker-data/
cr0x@server:~$ sudo mkdir -p /etc/docker
cr0x@server:~$ sudo tee /etc/docker/daemon.json > /dev/null
{
  "data-root": "/mnt/docker-data"
}
cr0x@server:~$ sudo systemctl start docker
cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/mnt/docker-data

Значення: Docker тепер використовує новий data root. Якщо контейнери не стартують, ймовірно, ви пропустили права, SELinux контексти або прапорці rsync.

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

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

Міні-історія №1: Інцидент через хибні припущення (логи «не можуть бути такими великими»)

Компанія була в процесі міграції з VM у контейнери. Ключовий сервіс був стабільний роками, і контейнеризація робилася максимально мінімально: «підняти і перенести, не рефакторити». Це рішення саме по собі не було неправильним. Неправильним було припущення, яке до нього додали.

Вони думали, що логи «обробляються платформою», бо на старому VM образі був logrotate. У Docker додаток все ще писав у stdout/stderr. Платформа це «обробляла» — шляхом запису JSON-логів на диск назавжди, без ротації. Першого дня все було добре. На двадцятий день один вузол почав повертати 500. Оркестратор продовжував пересаджувати контейнери, бо «контейнери — це худоба». Вузол залишався повним, бо пересадка не видаляла лог-файли достатньо швидко, а нові контейнери продовжували писати в ту саму прірву.

Інженер на виклику перевірив df -h на /, побачив 40% вільного і оголосив «не диск». Вони пропустили, що Docker живе на /var, який був на іншому монту. Другий інженер запустив docker system df і нічого особливого не побачив — бо звіт Docker не «зауважив», що один лог-файл займає 20GB.

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

Міні-історія №2: Оптимізація, що повернулась бумерангом (кеш BuildKit усюди)

Інша команда пишалась швидкістю CI. Збори тривали кілька хвилин, в основному завдяки відмінному кешуванню BuildKit. Надто відмінному. Їхні билдери також запускали деякі довгоживучі сервіси (бо «у нас є вільні ресурси»), а билдерам дісталися великі локальні SSD. Здавалося ефективно: один тип машин, один золотий образ, все кудись заплановано.

Кеш ріс тихо. Мульти-арх збірки, часті оновлення залежностей і звичка тегувати кожен коміт створили високочастотний кеш. Тиждень це не помітно. Потім велика релізна гілка спричинила шквал збірок і варіантів шарів. Кеш роздувся і викинув диск за край у робочий час.

Більше болючого було не те, що диск заповнився. Більшою проблемою був другорядний ефект: як диск заповнювався, билдерам ставало повільно, завдання таймаутилися, повтори збільшували навантаження, і кеш зростав ще швидше. Система стала самопідживною петлею: «оптимізація» зробила відмову більш вибуховою.

Кінцеве рішення було не «прунь більше». Вони розділили ролі: виділені билд-ноди з плановим обмеженням кешу, виділені рантайми зі строгими правилами утримання образів і явні ліміти на логи. Також вони перестали удавати, що «швидкий билд» — це та сама метрика, що «стабільний билд».

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

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

Вони налаштували ротацію логів для драйвера json-file Docker і також задали обмеження journald на хостах, що використовували journald. Налаштували алерти на використання /var/lib/docker, використання інодів і на top-N файлів логів контейнерів. Шум алертів був низьким, бо пороги налаштовано, і до алертів додано рукавиці дій (runbooks).

Одної п’ятниці ввечері сервіс почав висипати помилки через проблему з оновленням облікових даних downstream. На платформах інших команд така ситуація перетворилась би на «диск повний» плюс «додаток впав». На цій платформі файли логів досягли капа, логи ротувалися, диск залишився здоровим, а на виклик прийшов лише один алерт: «зростання помилок сервісу + збільшення об’єму логів». Вони вирішили проблему з обліковими даними. Без паніки з очищенням. Без розкопок файлової системи. Нудна надійність знову перемогла.

Поширені помилки: симптом → корінь → виправлення

1) «df показує вільне місце, але Docker каже, що немає місця»

Симптом: pull/build/start завершується з no space left on device; df -h на / показує багато вільного.

Корінь: Docker root на іншому монту (/var або виділений диск), або ви заповнюєте /tmp під час збірок.

Виправлення: docker info для Docker Root Dir; запустіть df -h на тому монту і на /tmp. Перемістіть data-root або збільшіть правильну файлову систему.

2) «Немає місця», але у вас гігабайти вільні

Симптом: Записи відмовляються; df -h показує вільні GB; помилки тривають.

Корінь: Виснаження інодів (df -i показує 100%) або метадані thin pool заповнені (devicemapper).

Виправлення: Якщо іноди: пруньте кеші з великою кількістю дрібних файлів і відтворіть файлову систему з відповідною щільністю інодів (або використовуйте XFS). Якщо devicemapper: мігруйте на overlay2 або розширте метадані thin pool.

3) «docker system prune нічого не звільнив»

Симптом: Ви прунули, але використання диска майже не змінилося.

Корінь: Винуватцем є логи або journald, або великі іменовані томи, прикріплені до працюючих контейнерів.

Виправлення: Перевірте /var/lib/docker/containers і використання journald; перевірте розміри томів під /var/lib/docker/volumes і зіставте томи з контейнерами.

4) «Ми видалили контейнери, але диск не впав»

Симптом: Видалення контейнерів не звільнило очікуваний простір.

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

Виправлення: Перевірте томи і кеш збірки; якщо підозрюєте видалені-але-відкриті файли, перезапустіть винуватця (іноді демон Docker або рантайм контейнера) після безпечного очищення.

5) «Директорія overlay2 величезна; чи можемо її видалити?»

Симптом: /var/lib/docker/overlay2 домінує у використанні диска.

Корінь: Там живуть образні шари та записувані шари. Ручне видалення ламає стан Docker.

Виправлення: Використовуйте Docker-команди для прунінгу невикористаних образів/контейнерів; якщо стан пошкоджено, плануйте контрольований wipe-and-recreate для одноразових хостів, не для продакшн нод зі станом.

6) «Після переходу на journald логування диск все одно заповнюється»

Симптом: Ви змінили драйвер логування; використання диска продовжує зростати.

Корінь: Значення зберігання journald занадто ліберальні або включено персистентне зберігання журналів без обмежень.

Виправлення: Налаштуйте обмеження за розміром/часом у journald і перевіряйте journalctl --disk-usage.

7) «CI-білдери щотижня заповнюють диск»

Симптом: Вузли билдера заповнюються передбачувано.

Корінь: Утримання кешу BuildKit без обмежень; багато тулчейнів генерують багато унікальних шарів; надто багато тегів/гілок збирається на одному вузлі.

Виправлення: Запланований docker builder prune; відокремити білдери від рантайму; ввести політику утримання і/або періодично реконструювати білдери (immutable infrastructure тут дійсно допомагає).

8) «Простір звільнено, але сервіс все ще не працює»

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

Корінь: Пошкоджені метадані Docker, часткові pull-и або помилка на рівні додатку, яка спочатку спричинила надмірне логування.

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

Контрольні списки / покроковий план

Аварійний чекліст (продакшн вузол зараз повний)

  1. Підтвердіть монтаж: запустіть df -h і df -i на Docker root і /tmp.
  2. Спочатку зупиніть неконтрольовані логи: знайдіть найбільші файли логів контейнерів; вкоротіть найгірші; зменшіть рівень логування, якщо безпечно.
  3. Відновіть безпечний простір: виконайте docker builder prune --all на білдер-нодах; виконайте docker image prune -a, якщо розумієте вплив на rollback.
  4. Аудит томів перед дією: ідентифікуйте найбільші томи і зіставте їх з контейнерами. Видаляйте лише підтверджені сирітські томи.
  5. Перевірте вільний простір: повторно виконайте df -h. Тримайте принаймні кілька гігабайтів вільними; деякі файлові системи та демони поводяться погано біля 100%.
  6. Стабілізація: перезапускайте дефектні компоненти лише після зняття тиску диска; уникайте флапінгу.
  7. Напишіть інцидент-ноту: що заповнило диск, як швидко це росло і яка політика запобігає повторенню.

Чекліст укріплення (щоб припинити повторення)

  1. Встановіть ротацію логів Docker: обмежте розмір і кількість файлів для json-file.
  2. Встановіть ретеншн для journald: обмежте зберігання і/або час, якщо використовуєте journald.
  3. Розділяйте обов’язки: білдери і рантайми не повинні бути на одній флоті, якщо ви не любите загадкове зростання.
  4. Встановіть політику прунінгу: запланований прунінг кешу збірки та правила утримання образів за роллю хоста.
  5. Перенесіть Docker root на виділене сховище: особливо при малих кореневих FS.
  6. Алертуйте по інодах і байтах: і додавайте runbook з цими командами.
  7. Вимірюйте головних порушників: найбільші томи, найбільші логи контейнерів, найбільші образи на хості.
  8. Проєктуйте на відмови: якщо downstream ламається і тригерить storm повторів, платформа має деградувати без саморуйнування.

Рекомендовані базові налаштування демона Docker (практичні дефолти)

Якщо ви використовуєте драйвер json-file, встановіть ротацію логів. Це найрентабельніший крок контролю диска.

cr0x@server:~$ sudo tee /etc/docker/daemon.json > /dev/null
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}
cr0x@server:~$ sudo systemctl restart docker

Значення: Файл логів кожного контейнера ротується приблизно при ~50MB, зберігаючи 5 файлів (~250MB на контейнер у найгіршому випадку).

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

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

  • Факт 1: Ранні розгортання Docker часто використовували режим devicemapper loopback за замовчуванням, що було повільно і схильне до «містичних» помилок простору/метаданих під навантаженням.
  • Факт 2: Перехід Docker на overlay2 як типовий вибір зробив зберігання швидшим і простішим, але також зробив copy-up поведінку частим сюрпризом для команд, що пишуть у файлову систему контейнера.
  • Факт 3: Історично дефолтним драйвером логування Docker був json-file, що був оптимальний для простоти, а не для довготривалої гігієни диска.
  • Факт 4: Популярність BuildKit зросла, бо він робив збірки швидшими і більш паралельними, але операційна ціна — управління кешем, особливо на шарінгових билдерах.
  • Факт 5: Фраза «no space left on device» — це загальний errno (ENOSPC), що повертає ядро, і її використовують не лише для «диска повного».
  • Факт 6: Виснаження інодів — стара Unix-проблема, яка не зникла; контейнери повернули її назад, бо витяг образів і екосистеми мов генерують величезну кількість дрібних файлів.
  • Факт 7: Багато операторів важко вивчили, що «контейнери епhemeral» — це не про дані. Томи — це стан, а стан — назавжди, доки ви його не видалите.
  • Факт 8: Власна звітність Docker (docker system df) корисна, але не авторитетна; файлову систему слід вважати істинним джерелом, особливо стосовно логів і тимчасових поза-Docker файлів.

FAQ

1) Чому Docker каже «no space left on device», коли df -h показує простір?

Тому що ви перевірили невірний монтаж, або вичерпані іноди, або досягнута квота/обмеження метаданих. Завжди перевіряйте Docker root dir і виконуйте df -i.

2) Чи безпечно запускати docker system prune -a в продакшені?

Іноді. Команда видаляє невикористані образи, контейнери і мережі. Вона може зламати стратегії швидкого rollback і спричинити повільніші деплої через підкачування образів. Спочатку використовуйте прицільний прунінг.

3) Чи безпечно запускати docker system prune --volumes?

Тільки якщо ви перевірили, що «невикористані» томи справді придатні для видалення. «Unused» означає «не зараз посилається», а не «неважливі». Ось так губиться дані.

4) Чому мої логи контейнерів такі великі?

Бо json-file логування за замовчуванням необмежене, поки ви не встановите max-size і max-file. Також галасливий додаток може генерувати гігабайти на годину під час циклів помилок.

5) Якщо я вкорочу логи контейнера, чи зламається Docker або додаток?

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

6) Чому видалення контейнера не звільняє простір?

Бо простір, ймовірно, у томах, образах або кеші збірки. Також простір видалених файлів може залишатися зайнятим, якщо процес все ще має відкритий дескриптор.

7) Чому /var/lib/docker/overlay2 такий великий, хоча образів небагато?

Overlay2 містить і записувані шари, і вміст витягнутих шарів. Кілька «великих» образів плюс write-heavy контейнери легко можуть домінувати диском.

8) Як найкраще запобігти інцидентам диска на CI-білдерах?

Виділені білдери, запланований docker builder prune, обмежені кеші і періодичне реконструювання білдерів. Трактуйте кеш як споживний ресурс, а не як скарб.

9) Чи можна просто видаляти файли під /var/lib/docker вручну?

Не робіть цього. Ручне видалення часто корумпує стан Docker. Використовуйте Docker-команди або робіть контрольований wipe лише на дійсно одноразових хостах.

10) Скільки вільного простору слід тримати на Docker-хості?

Достатньо, щоб pulls/unpacks і сплески логів не штовхали вас до 100%. Практично: тримайте буфер у кілька гігабайтів і налаштуйте алерти задовго до критичної межі.

Висновок: наступні кроки, які реально запобігають повторенню

Коли Docker закінчує місце, це рідко «Docker великий» і майже завжди «ми не керували нудними речами». Логи, кеші та томи — нудні. Вони також — джерело інцидентів.

Ваші практичні наступні кроки:

  1. Поставте ліміт на логи сьогодні (ротація json-file і/або ретеншн journald). Це одразу виключає велику категорію відмов.
  2. Визначте ролі хостів: ноди рантайму не повинні накопичувати кеші збірки; білдери повинні мати плановий прунінг і передбачувані реконструкції.
  3. Алертуйте по байтах і інодах для Docker root файлової системи, а також на top container log sizes і найбільші томи.
  4. Перестаньте писати стан у записувані шари: свідомо використовуйте томи, монтуйте tmpfs для справді тимчасових даних і перевіряйте шляхи, куди пишуть ваші додатки.
  5. Коли очищуєте — будьте хірургами: спочатку кеші і невикористані образи, томи — лише за доказами.

Диск не гламурний. Через це він виграє багато боїв. Зробіть його чиєюсь відповідальністю — бажано вашою, поки це не стало вашими вихідними.

← Попередня
Proxmox — «dpkg was interrupted»: виправлення пошкоджених пакетів без перевстановлення
Наступна →
Планування ZFS scrub: як уникнути проблем у пікові години

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