Назад
341

Практические советы по работе с docker

341

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

📌 Аргументы, переменные и секреты

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

🔹 При сборке docker image аргументы передаются через ARG. Они используются только при сборке и недоступны в runtime. Через ARG удобно параметризировать базовый образ или его версию. Например:

ARG BASE_IMAGE=python:3.12
FROM ${BASE_IMAGE}

По умолчанию используется базовый образ python:3.12, но его можно поменять через --build-arg:

docker build --build-arg BASE_IMAGE=python:3.11 -t my-image .

⚠️ Важно: ARG, объявленный до FROM, доступен только для выбора базового образа.

🔹 При запуске docker container аргументы передаются через ENV / .env. Так можно передавать настройки приложения, или, например, гиперпараметры.

FROM python:3.12
WORKDIR /app
COPY app.py .
ENV LOG_LEVEL=INFO #теперь переменная окружения доступна в контейнере
... 

Сборка образа и запуск контейнера:

docker build -t myapp .
docker run -e LOG_LEVEL=DEBUG myapp # переопределили значение LOG_LEVEL

🔹 Токены, пароли и ключи передаются через docker secrets. Хранить такую информацию в Dockerfile или переменных окружения небезопасно, так как эти данные могут попасть в историю git или слои образа, следовательно, их можно посмотреть через команду docker inspect.

Secrets позволяет использовать секреты в процессе сборки, не включая их в слои образа.

Пример использования токена из token.txt при сборке:

FROM python:3.12
...
RUN --mount=type=secret,id=mysecret \ # получаем доступ к секрету
    TOKEN=$(cat /run/secrets/mysecret) && \ # считываем его в переменную
    pip install --index-url <https://$TOKEN@pypi.company.com/simple/my-package> # устанавливаем пакет из приватного репозитория 
DOCKER_BUILDKIT=1 docker build --secret id=mysecret,src=token.txt .

📌 Docker volumes

По умолчанию данные внутри контейнера теряются при его удалении. Если необходимо их сохранить — используйте docker volumes. Они здесь сохранятся, даже если контейнер удалён, и их можно переиспользовать в других контейнерах.

Как правило, в volumes пишутся файлы баз данных, логи. Также на этапе разработки удобно монтировать исходный код через volume и не пересобирать контейнер при его изменениях, что позволяет ускорить разработку.

Docker volume может задавать права при монтировании: чтение + запись (rw) используется по умолчанию, а ещё есть только чтение (ro).

В примере ниже монтируется папка app в контейнер в «app» с правами «чтение и запись» (rw) и папка «config в /config с» с правами «только чтение» (ro). Контейнер может использовать настройки, но не изменять их:

docker run -d -v $(pwd)/app:/app:rw -v $(pwd)/config:/config:ro my-image

📌 Использование диска

Часто для работы с данными может понадобиться много свободного места на диске. Если его мало, один из вариантов для увеличения — почистить docker-артефакты.

Команда docker system df показывает, сколько места занимают артефакты docker на диске. Так можно увидеть, что именно занимает место и сколько можно освободить (колонка RECLAIMABLE).

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          17        3         119GB     95.23GB (80%)
Containers      3         2         3.924GB   0B (0%)
Local Volumes   3         0         284MB     284MB (100%)
Build Cache     327       0         63.28GB   63.28GB

Команда docker system prune -a позволит удалить всё из RECLAIMABLE, кроме неиспользуемых Volume. Если нужно удалить ещё и их, то добавляется флаг --volumes. Важно отметить: в RECLAIMABLE попадает всё, что не используется в данный момент, но может понадобиться в будущем. Также стоит следить за build cache: при частых сборках и изменениях проекта кэш может накапливать временные или лишние файлы. Его стоит периодически чистить с помощью следующей команды (но скорость сборки может упасть):

docker builder prune

📌 Docker save / load

Порой сборка занимает много времени: базовый образ медленно качается, или же установка зависимостей съедает кучу времени. Если нужный образ уже собран у коллеги — его можно сохранить в архив с помощью команды docker save, передать по локальной сети и загрузить на своей машине через docker load:

docker save -o myapp.tar myapp:latest
# transfer archive by local net
docker load -i myapp.tar

📌 Ограничение ресурсов

При запуске на одной машине нескольких задач, каждой в своём docker container, нам не хочется их конкуренции за ресурсы и сбою остальных запущенных задач. Docker позволяет ограничивать потребление CPU и RAM, выделять конкретные CPU и GPU для контейнера.

В примере ниже для контейнера выставляются ограничения:

  • использование RAM — один гигабайт;
  • конкретная GPU — нулевая;
  • использование процессора половиной одного ядра — процессор будет применять ядро не более 50% от всего времени:
docker run --cpus=0.5 --memory=1g --gpus "device=0" myapp:latest

Также можно выделить контейнеру конкретные ядра процессора:

docker run --cpuset-cpus="0,1" --memory=1g --gpus "device=0" myapp:latest

📌 Root-права и безопасность

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

Например:

FROM python:3.12
...
# Создается пользователь с конкретным UID и домашней папкой
RUN useradd -m -u 10001 appuser
# Создается рабочая папка и выдаются права пользователю 
RUN mkdir /app && chmod 750 /app && chown -R appuser:appuser /app 
WORKDIR /app
# Переключение на непривилегированного пользователя
USER appuser
# Копирование приложения, которое будет принадлежать appuser 
COPY --chown=appuser:appuser app.py .
ENTRYPOINT ["python", "app.py"]

Запуск контейнера:

docker run -d --rm\
  -u 10001:10001 \ # Прокидываем UID пользователя и GID
  --security-opt=no-new-privileges \ #запрещаем escalation привилегий внутри контейнера
  myapp

⚠️ Важно: UID и GID не должны пересекаться с уже существующими, так как это создаёт уязвимость.

📌 Отладка

Часто контейнер, запущенный с опцией --rm, падает сразу после старта. Отладить ошибку можно, запустив контейнер с ENTRYPOINT /bin/bash и введя команду руками.

Флаг --entrypoint позволяет переопределить ENTRYPOINT, не пересобирая образ:

docker run -it --rm container_name --entrypoint /bin/bash

Иногда полезно подключиться к запущенному контейнеру и выполнить какие-то команды внутри (например, посмотреть версию какого-то пакета в окружении) — с этим поможет docker exec:

docker exec -it my_container /bin/bash

Старт — в январе
Деплой DL-сервисов

Научитесь создавать и деплоить DL-сервисы за 4 месяца. Наведите порядок в репозиториях, внедрите лучшие практики и повысьте свою ценность на рынке

0/0

Телеграм-канал

DeepSchool

Короткие посты по теории ML/DL, полезные
библиотеки и фреймворки, вопросы с собеседований
и советы, которые помогут в работе

Открыть Телеграм

Увидели ошибку?

Напишите нам в Telegram!