Написать сервис это половина дела. На собесе 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.
- Как вы узнаёте, что сервис работает плохо: логи, метрики, алерты.