kai.kzLab

no space left on device в Docker: что занимает место и как его безопасно освободить

Команда KAI Lab · · 8 мин чтения

Ошибка no space left on device значит, что на файловой системе, где Docker хранит данные, закончились свободные блоки или inode: ядро вернуло ENOSPC, и запись не удалась. Чаще всего место съедают старые образы и кэш сборки, остановленные контейнеры, тома и логи контейнеров, которые драйвер json-file по умолчанию не ротирует.

Ошибка приходит с разными префиксами: при docker pull, при docker build, при записи в лог или из самого приложения в контейнере. Заканчивается она одинаково: no space left on device — это текст кода ENOSPC из man 3 errno. Ниже — порядок поиска виновника и безопасная очистка, без rm -rf по каталогам Docker.

Шаг 1. Какая файловая система кончилась

Сначала найдите, где Docker хранит данные, и проверьте и место, и inode:

$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker
$ df -h /var/lib/docker
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G   40G     0 100% /
$ df -i /var/lib/docker
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      2621440 412733 2208707   16% /

Здесь кончились блоки: Use% 100%. Если бы IUse% был 100% при свободных гигабайтах, закончились бы inode — это разбирается ниже отдельно.

Проверьте и /var/lib/containerd. По документации Docker, при хранилище образов containerd, которое с Docker Engine 29.0 используется по умолчанию на новых установках, образы и слои контейнеров лежат там, а в /var/lib/docker остаются тома и настройки. На системах, обновлённых со старых версий, всё по-прежнему в /var/lib/docker/overlay2.

Шаг 2. Что занимает место внутри Docker

$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          47        6         18.62GB   15.93GB (85%)
Containers      9         6         2.114GB   1.406GB (66%)
Local Volumes   14        4         9.87GB    6.2GB (62%)
Build Cache     312       0         11.4GB    11.4GB

RECLAIMABLE — сколько можно освободить, не трогая работающие контейнеры. В этом примере больше всего дадут образы и кэш сборки. Подробности по каждому образу, контейнеру и тому показывает docker system df -v. Документация предупреждает, что команда обходит файловые системы всех объектов и на загруженном сервере может работать долго.

docker system df не показывает логи контейнеров, а они часто и есть главный виновник.

Шаг 3. Логи контейнеров

По умолчанию Docker пишет stdout и stderr контейнеров драйвером json-file в /var/lib/docker/containers/ID/ID-json.log. Документация Docker прямо говорит: по умолчанию ротации нет, параметр max-size равен −1, то есть без ограничения. Болтливый контейнер за несколько месяцев пишет десятки гигабайт.

$ sudo du -h /var/lib/docker/containers/*/*-json.log | sort -h | tail -3
1.1G    /var/lib/docker/containers/9b2e...41c7/9b2e...41c7-json.log
3.8G    /var/lib/docker/containers/51fd...0a9e/51fd...0a9e-json.log
27G     /var/lib/docker/containers/3c7a...d18f/3c7a...d18f-json.log
$ docker inspect --format '{{.Name}} {{.LogPath}}' $(docker ps -aq) | grep 3c7a
/importer /var/lib/docker/containers/3c7a...d18f/3c7a...d18f-json.log

Документация предупреждает, что эти файлы предназначены только для демона Docker, и сторонние инструменты могут нарушить работу логирования. Поэтому правильный путь — включить ротацию и пересоздать контейнер: при удалении контейнера удаляется и его лог.

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

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

Или драйвер local, который документация рекомендует, чтобы не исчерпать диск: он по умолчанию хранит до 100 МБ логов на контейнер, 5 файлов по 20 МБ, и сжимает их. После изменения перезапустите Docker. Настройка действует только на новые контейнеры, существующие нужно пересоздать, например docker compose up -d --force-recreate. В Compose лимит можно задать и для отдельного сервиса в секции logging.

Шаг 4. Очистка через prune

Docker не удаляет неиспользуемые объекты сам: для каждого типа есть своя команда prune. Порядок от безопасного к рискованному:

$ docker container prune --filter "until=24h"
$ docker image prune -a --filter "until=168h"
$ docker builder prune
$ docker system prune
  • docker container prune удаляет остановленные контейнеры. Их слои тоже занимают место.
  • docker image prune без флагов удаляет только «висячие» образы без тега. С -a — все образы, которые не использует ни один контейнер. Их придётся скачивать заново при следующем запуске.
  • docker builder prune чистит кэш сборки. На CI-серверах это часто самый большой пункт.
  • docker system prune делает всё сразу: остановленные контейнеры, неиспользуемые сети, висячие образы и кэш сборки. Тома по умолчанию не трогает.

С томами осторожнее: в них живут данные. docker volume prune удаляет только анонимные тома, не подключённые ни к одному контейнеру, а docker volume prune --all — и именованные. Перед этим посмотрите docker system df -v и убедитесь, что в томе нет базы, которую просто остановили.

Шаг 5. Контейнер пишет в свой слой

Если приложение пишет временные файлы, кэш или логи в файловую систему контейнера, растёт его записываемый слой. Размер видно в docker ps -s:

$ docker ps -s --format 'table {{.Names}}\t{{.Size}}'
NAMES      SIZE
importer   14.2GB (virtual 14.9GB)
api        48.1kB (virtual 212MB)

Первое число — слой самого контейнера, в скобках — вместе с образом. Данные, которые должны жить, выносите в том, временные — чистите в самом приложении.

Шаг 6. Закончились inode

Если df -i показывает IUse% 100%, место есть, но создать новый файл нельзя. Виноваты миллионы мелких файлов: кэш, сессии, распакованные зависимости в слоях. Найти каталог-рекордсмен помогает GNU du:

$ sudo du --inodes -x /var/lib/docker 2>/dev/null | sort -n | tail -5

Очистка та же — prune и удаление мусора в приложении. Драйвер overlay2, по документации Docker, расходует inode экономнее старых драйверов, но от миллиона файлов в слое не спасает.

Шаг 7. Место не освобождается после удаления

Если после очистки df не изменился, файлы могли удалить, но процессы продолжают держать их открытыми, и место не освободится до закрытия. Проверка — sudo lsof +L1. Подробно этот случай и работа с df, du и inode разобраны в статье закончилось место на диске в Linux.

Если места мало в принципе

Когда /var/lib/docker живёт на маленьком корневом разделе, очистка лишь откладывает проблему. Данные Docker переносят на отдельный диск через параметр data-root:

$ sudo systemctl stop docker
$ sudo rsync -aHAX /var/lib/docker/ /mnt/docker-data/
$ echo '{ "data-root": "/mnt/docker-data" }' | sudo tee /etc/docker/daemon.json
$ sudo systemctl start docker

Команда tee перезапишет daemon.json целиком: если там уже есть настройки, например ротация логов, добавьте data-root к ним вручную. Для хранилища containerd data-root не действует: его корень задаётся параметром root в /etc/containerd/config.toml. И никогда не удаляйте файлы в /var/lib/docker/overlay2 руками: документация Docker прямо запрещает трогать этот каталог, после такого образы и контейнеры ломаются.

Причины no space left on device: таблица

ПричинаКак узнатьКак исправить
Старые образыв docker system df много RECLAIMABLE у Imagesdocker image prune -a с фильтром until
Кэш сборкибольшая строка Build Cachedocker builder prune, настройка GC сборщика
Остановленные контейнерыdocker ps -a, RECLAIMABLE у Containersdocker container prune
Логи без ротациибольшие *-json.log в /var/lib/docker/containersmax-size и max-file или драйвер local, пересоздать контейнер
Контейнер пишет в свой слойбольшой SIZE в docker ps -sтом для данных, очистка временных файлов
Забытые томаRECLAIMABLE у Local Volumesdocker volume prune после проверки содержимого
Кончились inodeIUse% 100% в df -iнайти каталог с мелкими файлами, чистить его
Удалённые, но открытые файлыlsof +L1перезапустить процесс, который их держит
Маленький раздел/var/lib/docker на корневом разделеdata-root на отдельном диске

Как не допустить повторения

  • Ротация логов для всех контейнеров в daemon.json с первого дня.
  • Регулярная очистка на CI-раннерах и серверах сборки, например docker system prune -af --filter "until=72h" по расписанию.
  • Алерт на заполненность диска и inode задолго до 100%: при 100% падают не только контейнеры, но и сам демон.
  • Отдельный раздел под данные Docker, чтобы логи контейнеров не могли заполнить корневую файловую систему.

На нодах Kubernetes до no space left on device обычно не доходит: kubelet начинает выселять поды, когда на основном разделе остаётся меньше 10% места. Как это выглядит, мы разобрали в статье под Evicted. А если контейнер из-за нехватки места падает и поднимается снова, пригодится разбор контейнер Docker постоянно перезапускается.

Частые вопросы

Безопасно ли запускать docker system prune -a? Работающие контейнеры и их образы команда не тронет. Удалятся остановленные контейнеры, неиспользуемые сети, все образы без контейнеров и кэш сборки, а тома останутся, если не добавить --volumes. Риск в другом: остановленный на время контейнер исчезнет, а удалённые образы придётся скачивать заново.

Почему df показывает свободное место, а Docker пишет no space left on device? Три частые причины: кончились inode (df -i), заполнена другая файловая система, например /var/lib/containerd, или место кончилось не на хосте, а на томе или tmpfs, куда пишет приложение внутри контейнера.

Почему после prune место не освободилось? Логи работающих контейнеров prune не трогает. Тома без флагов тоже не удаляются. А файлы, удалённые, пока их держит процесс, занимают место до его перезапуска: проверьте lsof +L1.

Как ограничить логи всех контейнеров сразу? Задать log-driver и log-opts в /etc/docker/daemon.json и перезапустить Docker. Настройка применится к новым контейнерам, существующие нужно пересоздать. Как устроены слои, тома и драйверы логов, часто спрашивают на собеседовании по Docker.

Потренироваться на практике

В симуляторе DevOps KAI Lab есть отдельные месяцы про Linux и Docker: диски, inode, логи и контейнеры разбираются на тикетах от коллег в консоли, которая воспроизводит реальные выводы команд. Начать можно с бесплатного теста навыков DevOps.

Источники

Читать дальше