98 lines
5.5 KiB
Docker
98 lines
5.5 KiB
Docker
# DXA Quality Assessment — контейнер для инференса (CPU)
|
||
#
|
||
# Сборка: docker build -t dxa-quality .
|
||
# Запуск: docker run -p 8000:8000 dxa-quality
|
||
# или: docker compose up -d (сервис dxa-cpu)
|
||
#
|
||
# Чекпоинт лежит ВНУТРИ образа: models/dxa_model.pth отслеживается git (каталог
|
||
# models/ не исключён в .gitignore) и копируется на этапе сборки. Монтировать
|
||
# пути не нужно — условие приёмки в том, что модели уже в образе. Если всё-таки
|
||
# смонтировать каталог в /app/models, он перекроет встроенный чекпоинт.
|
||
#
|
||
# Требования методики: зафиксированные версии зависимостей и работа без
|
||
# внешних сервисов. Inference не скачивает веса из сети — они внутри
|
||
# чекпоинта, поэтому при запуске сеть не нужна.
|
||
FROM python:3.11-slim
|
||
|
||
# libgl1/libglib2.0-0 нужны opencv (импортируется через зависимости проекта),
|
||
# libgomp1 — для параллельных циклов torch.
|
||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||
libgl1 \
|
||
libglib2.0-0 \
|
||
libgomp1 \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
WORKDIR /app
|
||
|
||
# --- Слой зависимостей: отдельно от кода, чтобы кэшировался ---
|
||
# Все версии зафиксированы, torch ставится с CPU-индексом: образ для инференса
|
||
# не требует GPU, а версия из PyPI на Linux подтянула бы CUDA-колёса.
|
||
COPY requirements.txt ./
|
||
RUN pip install --no-cache-dir --upgrade pip \
|
||
&& pip install --no-cache-dir \
|
||
--extra-index-url https://download.pytorch.org/whl/cpu \
|
||
-r requirements.txt
|
||
|
||
# --- Код ---
|
||
COPY src ./src
|
||
|
||
# Скрипт пакетной обработки (оценка берёт его из корня образа).
|
||
COPY run.sh ./run.sh
|
||
RUN chmod +x ./run.sh
|
||
|
||
# Smoke-проверка импорта: падает на сборке, если расходятся зависимости или
|
||
# модуль не найден, вместо тихой 500-й на каждом запросе в рантайме.
|
||
RUN python -c "import src.main; print('app import ok')"
|
||
|
||
# --- Проверка статики для офлайн-работы ---
|
||
# Веб-интерфейс не должен зависеть от CDN: Tailwind и FontAwesome лежат в
|
||
# src/api/static/vendor и src/api/static/webfonts (см. index.html). Страница
|
||
# ручной разметки /label тоже должна попасть в образ целиком.
|
||
RUN python -c "import os; \
|
||
files=['src/api/static/vendor/tailwind.js','src/api/static/vendor/fontawesome.css', \
|
||
'src/api/static/webfonts/fa-solid-900.woff2', \
|
||
'src/api/static/label.html','src/api/static/js/labeling.js']; \
|
||
missing=[f for f in files if not os.path.exists(f)]; \
|
||
assert not missing, f'missing frontend assets: {missing}'; \
|
||
print('frontend assets ok')"
|
||
|
||
# --- Чекпоинт внутри образа ---
|
||
# Условие приёмки: модель уже в образе, чтобы проверяющему не приходилось
|
||
# указывать путь к каталогу с весами. models/dxa_model.pth отслеживается git,
|
||
# поэтому копирование работает и в CI.
|
||
COPY models/dxa_model.pth /app/models/dxa_model.pth
|
||
|
||
# Проверка, что чекпоинт читается и содержит всё нужное для инференса: без этого
|
||
# битый или забытый файл проявился бы только в рантайме как model_loaded: false
|
||
# и HTTP 500 на каждый запрос анализа.
|
||
RUN python -c "import torch; \
|
||
ckpt=torch.load('/app/models/dxa_model.pth', map_location='cpu', weights_only=False); \
|
||
missing=[k for k in ('model_state_dict','backbone','head','threshold') if k not in ckpt]; \
|
||
assert not missing, f'checkpoint missing keys: {missing}'; \
|
||
print('checkpoint ok:', ckpt['backbone'], ckpt['head'], 'threshold', ckpt['threshold'])"
|
||
|
||
# Каталоги монтирований swarm: создаём заранее, чтобы образ вёл себя одинаково
|
||
# и при обычном `docker run` без volume.
|
||
RUN mkdir -p /app/data /app/logs
|
||
|
||
# --- Переменные окружения ---
|
||
# Читается кодом только DXA_MODEL_PATH (src/main.py). MODEL_PATH, DATA_PATH,
|
||
# ANNOTATION_DIR, APP_* swarm передаёт по инерции от прежней версии — код их
|
||
# не использует, и они ни на что не влияют.
|
||
ENV PYTHONUNBUFFERED=1 \
|
||
PYTHONPATH=/app \
|
||
DXA_MODEL_PATH=/app/models/dxa_model.pth \
|
||
MODEL_PATH=/app/models \
|
||
DATA_PATH=/app/data \
|
||
ANNOTATION_DIR=/app/data/annotations
|
||
|
||
EXPOSE 8000
|
||
|
||
# HEALTHCHECK опирается на /api/v1/health, который отдаёт model_loaded.
|
||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||
CMD python -c "import urllib.request,sys; \
|
||
r=urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health', timeout=5); \
|
||
sys.exit(0 if r.status==200 else 1)"
|
||
|
||
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
|