Упаковка модели в Docker-образ

Чтобы модель работала одинаково на ноутбуке и в кластере, её пакуют со всем окружением в образ.

Упаковка модели — превращение обученной модели в самодостаточный переносимый артефакт: сериализованные веса + код инференса + зафиксированные зависимости, обычно в виде Docker-образа.

Зачем упаковывать

«У меня работает» — главный враг продакшна. Модель зависит от версии Python, версий библиотек, системных пакетов. Перенесёте на другую машину — и сериализация может не прочитаться, а препроцессинг повести себя иначе. Docker-образ замораживает всё окружение, гарантируя одинаковое поведение везде.

Проблема глубже, чем кажется. Pickle scikit-learn технически привязан к версии библиотеки: модель, сохранённая в scikit-learn 1.3, при загрузке в 1.4 может выдать предупреждение или вовсе сломаться, потому что внутренняя структура объекта изменилась. На вашей машине стоит одна версия, на сервере коллеги — другая, в CI — третья. Без заморозки окружения эти расхождения всплывают в самый неподходящий момент — в проде, под нагрузкой. Образ снимает этот класс проблем целиком: внутри контейнера всегда ровно те версии, на которых модель обучалась и тестировалась, независимо от того, на каком хосте он запущен.

Упаковка — это ещё и единица деплоя и отката. Тег образа fraud-detector:v7 — это атомарный артефакт: либо он целиком в проде, либо целиком откатан на :v6. Не нужно думать, совпали ли версии библиотек на сервере, докатилась ли модель, тот ли это код инференса. Один образ — один воспроизводимый, переносимый, версионированный артефакт, который оркестратор разворачивает одинаково где угодно.

Шаг 1: сериализация

Модель сохраняют в файл. Способ зависит от фреймворка:

ФорматКогда
pickle / joblibscikit-learn (внимание: версия библиотеки важна)
SavedModelTensorFlow
state_dict / TorchScriptPyTorch
ONNXфреймворк-независимый обмен

Важно сериализовать весь pipeline (препроцессинг + модель), а не только веса — иначе вернётся training-serving skew.

Шаг 2: фиксация зависимостей

Точные версии в lock-файле — иначе образ соберётся с другой версией библиотеки, и pickle может не прочитаться:

scikit-learn==1.3.2
joblib==1.3.2
fastapi==0.110.0
uvicorn==0.29.0

Шаг 3: Dockerfile

FROM python:3.11-slim

WORKDIR /app

# сначала зависимости — кешируется отдельным слоем
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# затем артефакт модели и код инференса
COPY model.pkl serve.py ./

EXPOSE 8000
CMD ["uvicorn", "serve:app", "--host", "0.0.0.0", "--port", "8000"]

Порядок слоёв важен: зависимости копируем и ставим раньше кода, чтобы при изменении только кода Docker переиспользовал кешированный слой с библиотеками и не пересобирал их.

Сборка и запуск

docker build -t fraud-detector:v7 .
docker run -p 8000:8000 fraud-detector:v7
# тег = версия модели; образ воспроизводим и переносим

Как работает под капотом

Docker-образ — это слоёная файловая система: базовый Python, слой зависимостей, слой с моделью и кодом. Тег образа (например, v7) привязывают к версии модели в реестре, замыкая lineage: образ → артефакт → run → данные → код. Контейнер из этого образа запускается идентично в любом окружении, поддерживающем Docker, потому что внутри заморожены и библиотеки, и системные зависимости.

Частые ошибки

  • Сериализовать только модель без препроцессинга. Возвращает skew; пакуйте весь pipeline.
  • Не пинить версии. Образ с «свежей» версией библиотеки может не прочитать ваш pickle.
  • Ставить зависимости после COPY всего кода. Ломает кеш слоёв, каждая мелкая правка пересобирает библиотеки.
  • Жирный базовый образ. Используйте slim/distroless, чтобы образ был лёгким и быстро деплоился.

Итог

  • Упаковка превращает модель в переносимый артефакт: сериализованный pipeline + зависимости + код в Docker-образе.
  • Фиксация версий и сериализация всего pipeline защищают от «у меня работает» и от skew.
  • Тег образа привязывают к версии модели, замыкая цепочку воспроизводимости.
Проверьте себя
1. Почему модель пакуют именно в Docker-образ?
AЧтобы файл весил меньше
BЧтобы заморозить всё окружение и гарантировать одинаковое поведение везде
CЭто требование scikit-learn
DЧтобы скрыть веса
2. Что нужно сериализовать вместе с моделью, чтобы избежать skew?
AТолько веса
BВесь pipeline, включая препроцессинг
CЛоги обучения
DБазу данных
3. Почему зависимости в Dockerfile ставят до копирования всего кода?
AТак требует Python
BЧтобы Docker переиспользовал кешированный слой с библиотеками при правках кода
CИначе образ не соберётся
DДля шифрования