Упаковка модели в Docker-образ
Чтобы модель работала одинаково на ноутбуке и в кластере, её пакуют со всем окружением в образ.
Упаковка модели — превращение обученной модели в самодостаточный переносимый артефакт: сериализованные веса + код инференса + зафиксированные зависимости, обычно в виде Docker-образа.
Зачем упаковывать
«У меня работает» — главный враг продакшна. Модель зависит от версии Python, версий библиотек, системных пакетов. Перенесёте на другую машину — и сериализация может не прочитаться, а препроцессинг повести себя иначе. Docker-образ замораживает всё окружение, гарантируя одинаковое поведение везде.
Проблема глубже, чем кажется. Pickle scikit-learn технически привязан к версии библиотеки: модель, сохранённая в scikit-learn 1.3, при загрузке в 1.4 может выдать предупреждение или вовсе сломаться, потому что внутренняя структура объекта изменилась. На вашей машине стоит одна версия, на сервере коллеги — другая, в CI — третья. Без заморозки окружения эти расхождения всплывают в самый неподходящий момент — в проде, под нагрузкой. Образ снимает этот класс проблем целиком: внутри контейнера всегда ровно те версии, на которых модель обучалась и тестировалась, независимо от того, на каком хосте он запущен.
Упаковка — это ещё и единица деплоя и отката. Тег образа fraud-detector:v7 — это атомарный артефакт: либо он целиком в проде, либо целиком откатан на :v6. Не нужно думать, совпали ли версии библиотек на сервере, докатилась ли модель, тот ли это код инференса. Один образ — один воспроизводимый, переносимый, версионированный артефакт, который оркестратор разворачивает одинаково где угодно.
Шаг 1: сериализация
Модель сохраняют в файл. Способ зависит от фреймворка:
| Формат | Когда |
| pickle / joblib | scikit-learn (внимание: версия библиотеки важна) |
| SavedModel | TensorFlow |
| state_dict / TorchScript | PyTorch |
| 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.
- Тег образа привязывают к версии модели, замыкая цепочку воспроизводимости.