Пара 14: Dockerfile: сборка StudyPulse

90 минут · 3 курс, ML.

Содержание и результат

Собрать образ сервиса и проверить воспроизводимый запуск без ручных правок контейнера.

План занятия

0–10: контекст и исходная задача. 10–40: устройство и механизмы. 40–55: демонстрация команд. 55–80: лабораторная работа. 80–90: разбор результата и фиксация исправлений.

Практика выполняется в своей учебной папке и на localhost. Подготовка окружения описана в lab/README.md.

Инструкции Dockerfile

Сборка создаёт образ; CMD задаёт запуск.

FROM выбирает базовый образ, WORKDIR задаёт рабочую папку, COPY добавляет файлы, RUN выполняет действие при сборке, USER выбирает пользователя, CMD описывает команду запуска. exec-форма CMD передаёт процессу сигналы понятнее, чем лишняя shell-обёртка. EXPOSE документирует порт, но не публикует его на хосте. В учебном сервисе нет сторонних Python-зависимостей: Dockerfile остаётся коротким. Не добавляем apt и компилятор без необходимости. Рабочая папка и MODEL_PATH согласованы, чтобы относительный путь работал предсказуемо.

Аналогия: последовательная инструкция сборки и отдельная кнопка запуска.

Контекст и кеш

Сборка видит только переданный контекст и учитывает его изменения.

Точка в docker build ... . задаёт контекст. COPY не должен случайно захватить .env, .git, .venv, датасеты и личные файлы. .dockerignore исключает их из контекста. Кеш ускоряет повторную сборку, когда инструкция и её входы не изменились. Изменение раннего слоя может потребовать пересборки последующих. Не учим отключать кеш при любой проблеме: сначала проверяем, тот ли Dockerfile и контекст используется. В проекте с зависимостями полезно копировать описание зависимостей перед кодом, чтобы правка кода не заставляла ставить библиотеки заново.

Аналогия: на завод передают нужную коробку деталей, а не содержимое всей квартиры.

Пользователь и постоянные данные

Код в образе, изменяемые данные — в специально выбранном месте.

Запускаем StudyPulse от непривилегированного пользователя. Коду достаточно чтения; запись разрешена только в /data для учебного журнала событий. read-only root filesystem на следующем этапе ограничит непреднамеренные изменения. Не кладём секреты в ARG, ENV и COPY при сборке: они могут сохраниться в образе и истории слоёв. Базовый tag версии удобен для курса, но перед рабочим release фиксируем digest и обновляем образы осознанно. Контейнерное упаковывание не отменяет проверок входа и ошибок приложения.

Аналогия: запечатанный прибор и отдельная сменная кассета с результатами.

Команды и наблюдения

Окружение: Ubuntu / Bash, lab.

cd ~/devops-course/lab
docker build -t studypulse:v1 .
docker run --rm --name studypulse-demo -p 127.0.0.1:8000:8000 -e HOST=0.0.0.0 studypulse:v1
# In another terminal
curl -i http://127.0.0.1:8000/healthz
docker logs studypulse-demo
docker inspect --format "{{.Config.User}}" studypulse-demo

HOST=0.0.0.0 внутри контейнера позволяет принять трафик с его сети; публикация 127.0.0.1 на хосте ограничивает внешний доступ.

Ожидаемый результат: API доступен на localhost хоста; пользователь образа не root; журнал содержит старт и запрос.

Вариант для macOS

Dockerfile курса переносим при наличии образа для вашей архитектуры. Локальная ARM64-сборка не является автоматически AMD64-образом для сервера. Для рабочего выпуска используйте явно целевую платформу или multi-platform build и проверку. Эмуляция может замедлить сборку и нагрузочные измерения.

docker build -t studypulse:v1 .
docker run --rm -p 127.0.0.1:8000:8000 -e HOST=0.0.0.0 studypulse:v1

Практика: Собрать и проверить

  1. Прочитайте предоставленный Dockerfile; подпишите роль каждой инструкции.
  2. Соберите образ дважды и найдите cached-слои.
  3. Запустите сервис, проверьте healthz и корректный/некорректный predict.
  4. Измените небольшую часть документации, затем код в отдельной копии; сравните влияние на сборку и восстановите исходный код.

Результат: Образ studypulse:v1, ответы API и объяснение кеша.

Проверка: Контейнер работает non-root; .env и .venv не попали в образ; EXPOSE не путают с -p.

Неисправность для разбора: Приложение слушает 127.0.0.1 внутри контейнера и недоступно через опубликованный порт.

Решение и диагностика

Проверьте docker logs, HOST и -p. Внутри контейнера bind должен быть 0.0.0.0; на хосте -p 127.0.0.1:8000:8000. Запрос с hours=-1 должен получить 400, нормальный hours=4 — score=0.5. Ошибка порта решается устранением конфликтующего собственного запуска или выбором другого host port, а не массовым удалением контейнеров.

Основные выводы

Самостоятельная работа

Добавить Docker-запуск в README и зафиксировать Dockerfile и dockerignore в Git.

Источники