Урок по треку Python для веба: Django и FastAPI 7 мин

Деплой Python-сервиса: gunicorn, uvicorn, Docker, health-check и логи

Gunicorn и uvicorn за nginx, число воркеров, Docker, статика, health-check, логи и мониторинг.

Написать сервис это половина дела. На собесе middle и senior спрашивают, как он живёт в проде: как запускается, сколько воркеров, как обновляется без простоя, как вы узнаёте, что он сломался.

Запуск в продакшене

# Django (WSGI)
gunicorn config.wsgi:application --workers 5 --timeout 30 --bind 0.0.0.0:8000

# FastAPI или Django ASGI
uvicorn app.main:app --workers 4 --host 0.0.0.0 --port 8000
gunicorn app.main:app -k uvicorn.workers.UvicornWorker --workers 4

Перед сервером приложений стоит nginx или балансировщик: TLS, статика, буферизация, лимиты. Процессом управляет systemd, Docker, Kubernetes, они же перезапускают его при падении.

Сколько воркеров

  • Отправная точка для sync-воркеров gunicorn: 2 * CPU + 1, дальше уточняется нагрузочным тестом.
  • Сверху ограничивает память: каждый воркер это процесс с копией приложения. --preload загружает приложение до форка и экономит память за счёт copy-on-write.
  • Для uvicorn процессов обычно около числа ядер: каждый держит много соединений в цикле событий.
  • В Kubernetes часто один-два процесса на под, а масштабирование делают числом подов.

Docker-образ

FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
COPY . .
RUN useradd -m app && chown -R app /app
USER app
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
  • Зависимости ставятся отдельным слоем до копирования кода: слой кешируется, пока не изменился lock-файл.
  • slim-образ, многоступенчатая сборка, если нужна компиляция, процесс не от root.
  • PYTHONUNBUFFERED=1, чтобы логи сразу уходили в stdout.
  • Секреты передаются переменными окружения при запуске, а не запекаются в образ.

Статика и медиа

Статику Django собирают collectstatic при сборке и отдают nginx, CDN или WhiteNoise. Пользовательские файлы (медиа) хранят в объектном хранилище S3-совместимого типа, а не на диске контейнера, который пропадёт при пересоздании.

Health-check и корректное завершение

  • Liveness: процесс жив (простой эндпоинт без зависимостей). Падение ведёт к перезапуску.
  • Readiness: готов принимать трафик (есть соединение с БД, прогрет кеш). Неготовый экземпляр убирают из балансировки, но не перезапускают.
  • Graceful shutdown: по SIGTERM сервер перестаёт принимать новые запросы и дообрабатывает текущие (у gunicorn --graceful-timeout). Иначе каждый деплой обрывает запросы пользователей.

Обновление без простоя

Rolling update или blue-green: новые экземпляры поднимаются, проходят readiness, затем на них переключается трафик, старые завершаются корректно. Условие: миграции базы обратно совместимы, ведь какое-то время работают обе версии.

Логи, метрики, ошибки

  • Логи в stdout в структурированном виде (JSON) с request id, их собирает платформа.
  • Метрики: RPS, латентность (p50, p95, p99), доля ошибок 5xx, загрузка воркеров, длина очередей. Prometheus и Grafana или аналоги.
  • Ошибки с трейсбэками в Sentry или GlitchTip.
  • Трейсинг (OpenTelemetry) показывает, где время запроса: БД, внешние API, код.

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

  • runserver или --reload в проде.
  • Секреты в образе или в git.
  • Медиафайлы на локальном диске контейнера.
  • Health-check, который ходит в базу, в роли liveness: база моргнула, и платформа перезапустила все экземпляры разом.
  • Нет таймаутов: зависшие запросы съедают всех воркеров.
  • Миграции, несовместимые со старой версией кода, при rolling update.

Что спросят на собесе

  • Как вы запускаете Django или FastAPI в проде, зачем nginx.
  • Сколько воркеров ставить и от чего это зависит.
  • Как собрать хороший Docker-образ для Python.
  • Чем liveness отличается от readiness, что такое graceful shutdown.
  • Как вы узнаёте, что сервис работает плохо: логи, метрики, алерты.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.