Docker прочно вошел в нашу жизнь, став стандартом де-факто для контейнеризации приложений. Он обещает среду «работает на моей машине — будет работать везде», и в большинстве случаев так и есть. Но рано или поздно каждый разработчик или DevOps-инженер сталкивается с ситуацией, когда эта магия дает сбой. Контейнер не запускается, приложение не видит друг друга, а диск сервера таинственным образом переполняется.

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

❯ «No space left on device»

File Upload Error "No Space left in device" - Posit Cloud - Posit Community

Вы видите эту ошибку в логах контейнера или при попытке собрать новый образ. Первая реакция — проверить df -h, и часто оказывается, что корневая файловая система забита под завязку. Но куда именно делось место? Ведь ваши данные в volumes, казалось бы, в безопасности.

Корень проблемы: Docker. Это целая экосистема, состоящая из образов, остановленных контейнеров, сетевых настроек и сборного мусора (build cache, временные файлы). Всё это хранится в директории Docker (обычно /var/lib/docker), которая по умолчанию находится в корневом разделе. Со временем этот раздел засоряется.

Что именно занимает место?

  • «Подвешенные» образы (Dangling Images): это слои образов, которые остались после сборки новых версий и больше не связаны с каким-либо тегом. Они как черновики, которые вы забыли выбросить.

  • Остановленные контейнеры: контейнер, даже остановленный, хранит свою файловую систему (слой записи) и метаданные. Если вы их не удаляете, они накапливаются.

  • Volume'ы: неиспользуемые volume'ы также могут занимать значительное пространство, Docker их не удаляет автоматически.

  •  Build Cache: кэш сборки — палка о двух концах. Он ускоряет повторные сборки, но может разрастаться до гигантских размеров, особенно если в Dockerfile часто меняются начальные слои.

И так, давайте сформируем системный подход к очистке.

Вместо того чтобы бездумно удалять всё подряд, будем использовать целенаправленные команды.

Для начала сделаем диагностику:

docker system df

Эта команда покажет детальную разбивку: сколько места занимают образы, контейнеры, volume'ы и кэш сборки. Это ваш главный инструмент для понимания ситуации.

Далее займёмся очисткой всего мусора:

docker system prune

Это самая безопасная и эффективная команда для регулярного использования. Она удалит все остановленные контейнеры, неиспользуемые сети и «подвешенные» образы. Для более агрессивной чистки (включая неиспользуемые volume'ы и весь build cache) можно использовать:

docker system prune -a --volumes 

(!ВНИМАНИЕ: с флагом --volumes удаляются и named volumes, убедитесь, что в них нет нужных данных). 

Также можно производить чистку избирательно. Такая чистка подойдёт если вы хотите контролировать процесс.

# Удалить все остановленные контейнеры
docker container prune

# Удалить все неиспользуемые образы
docker image prune -a

# Удалить все неиспользуемые volume'ы
docker volume prune

Как говорится: профилактика — лучшее лечение, поэтому вот несколько профилактических советов:

  • Сделайте привычкой удалять контейнер после его остановки, если он больше не нужен: docker run --rm ....

  • Настройте CI/CD-пайплайны на очистку после себя.

  • Для критически важных систем рассмотрите хранение данных Docker на отдельном разделе или диске. 

❯ Проблема с правами доступа

Нарушение прав доступа, сообщение от 1С | Программы для 1С | Первый Модуль

Вы монтируете директорию с хоста в контейнер с помощью volumes, и ваше приложение внезапно начинает жаловаться на невозможность создать файл, прочитать конфиг или записать в лог. 

Корень проблемы: Внутри контейнера работают процессы от определенного пользователя (часто root по умолчанию, но в лучших практиках — непривилегированный пользователь, например, с UID=1000). Файлы же на хост-машине принадлежат вашему пользователю (также, например, с UID=1000). Если UID пользователя в контейнере не совпадает с UID владельца файла на хосте, возникают пермишны (permissions).

Это не проблема, пока контейнер работает от root (он может всё), но с точки зрения безопасности это плохая практика. Как только вы переходите на непривилегированного пользователя внутри контейнера, мир рушится.

Есть несколько вариантов решения проблемы:

Конечно же можно сразу применить грубую силу и запустить контейнер от root. Это работает, но сводит на нет одну из ключевых мер безопасности контейнеризации. Сразу скажу, что для продакшена так делать не рекомендуется.

Как вариант можно изменить права на хосте (хак). Вручную изменить владельца или права (chmod или chown) монтируемой директории на хосте так, чтобы пользователь из контейнера имел к ним доступ. Это небезопасно и не масштабируется.

 Самым честным методом решения этой проблемы будет «подогнать» пользователя внутри контейнера под вашу среду. Для этого нужно:

  • Узнать ваш UID на хосте: 

    id -u

  • В своем Dockerfile создайте пользователя с таким же UID. Это ключевой момент.

FROM ubuntu:22.04

# ... ваши шаги ...

# Создаем группу и пользователя с конкретным UID (например, 1000)
RUN groupadd -g 1000 myappuser && \
    useradd -u 1000 -g 1000 -m myappuser

# Переключаемся на этого пользователя
USER myappuser

# Далее ваши команды, которые будут выполняться от myappuser
CMD ["python", "app.py"]

Теперь, при монтировании volume, файлы вашего пользователя на хосте (с UID=1000) и пользователя в контейнере (также с UID=1000) будут «видеть» друг друга как свои, и проблемы с правами исчезнут.

❯ Сетевые проблемы между контейнерами

Вы запускаете два контейнера: app и database. Приложение пытается подключиться к БД по адресу localhost:5432 и получает отказ. Почему?

Всё потому, что по умолчанию каждый контейнер изолирован в своей собственной сетевой области видимости (network namespace). У него свой localhost, и он не видит другие контейнеры. Концепция localhost внутри контейнера ограничена этим контейнером.

Проблему будем решать при помощи сети Docker. Docker предоставляет встроенный механизм для организации общения контейнеров — пользовательские сети (user-defined networks).

Для начала создадим свою сеть: 

docker network create my_app_network

Затем запускаем БД и контейнеры в этой сети:

# Запускаем БД

docker run -d --name database --network my_app_network postgres:15

# Запускаем приложение

docker run -d --name app --network my_app_network -p 80:5000 my-app-image

 Что это даёт?

  • DNS-резолвинг по имени: самое главное — Docker встроил в эти сети DNS. Теперь вашему приложению в контейнере app не нужно знать IP-адрес контейнера database (который может меняться). Достаточно использовать его имя в качестве хоста.
    В коде приложения строка подключения к БД изменится с localhost:5432 на database:5432. Docker автоматически преобразует имя database в правильный IP-адрес.

  • Изоляция: вы можете иметь несколько окружений (dev, stage) в разных сетях, и они не будут мешать друг другу.

Да и вообще в отличие от устаревшего подхода с --link, сети Docker — это современный, гибкий и рекомендуемый способ организации взаимодействия контейнеров.

Вообще, понимать внутреннее устройство Docker, его систему хранения, модель безопасности и сетевую модель очень полезно. Этот навык позволит вам не вечно тушить разгорающиеся вновь и вновь пожары, а строить стабильные и предсказуемые системы. Желаю всем удачной контейнеризации!


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале 

Комментарии (0)