Пара 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
Практика: Собрать и проверить
- Прочитайте предоставленный Dockerfile; подпишите роль каждой инструкции.
- Соберите образ дважды и найдите cached-слои.
- Запустите сервис, проверьте healthz и корректный/некорректный predict.
- Измените небольшую часть документации, затем код в отдельной копии; сравните влияние на сборку и восстановите исходный код.
Результат: Образ 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, а не массовым удалением контейнеров.
Основные выводы
- Нет, RUN выполняется при сборке образа.
- Нет, публикация задаётся при запуске.
Самостоятельная работа
Добавить Docker-запуск в README и зафиксировать Dockerfile и dockerignore в Git.