Деплой: uvicorn, gunicorn, воркеры
Урок о том, как вывести FastAPI из режима разработки в боевую эксплуатацию: ASGI-сервер, воркеры, обратный прокси и контейнер.
ASGI-сервер — это программа (например, uvicorn), которая принимает HTTP-соединения и запускает ваше асинхронное приложение по стандарту ASGI; сам FastAPI сервером не является.
FastAPI — это фреймворк, а не сервер. Чтобы приложение отвечало на запросы, его запускает ASGI-сервер. Локально вы пишете uvicorn main:app --reload и забываете об этом. Но в проде так делать нельзя: --reload следит за файлами и ест ресурсы, один процесс не использует все ядра, а падение сервера оставляет вас без сервиса. Этот урок — про правильный боевой запуск: сколько процессов поднимать, чем ими управлять, зачем перед ними ставить nginx и как упаковать всё в контейнер.
Зачем это нужно на практике
Разница между dev- и prod-запуском — это разница между «работает у меня» и «держит нагрузку и не падает ночью». В проде вам нужно: задействовать все ядра CPU, переживать падение отдельного воркера, корректно перезапускаться при деплое, отдавать статику и TLS не самим приложением, а тем, кто это делает лучше. Правильная связка серверов решает всё это и стоит недорого в настройке.
uvicorn: ASGI-сервер
uvicorn — основной ASGI-сервер для FastAPI. Он быстрый, потому что построен на uvloop и httptools. Для разработки удобен флаг --reload, для прода — нет.
# разработка: автоперезагрузка при изменении кода
uvicorn main:app --reload
# прод: без reload, слушаем все интерфейсы на нужном порту
uvicorn main:app --host 0.0.0.0 --port 8000
Запомните: --reload в проде — частая и дорогая ошибка. Он держит наблюдатель за файловой системой и не рассчитан на нагрузку. В боевом окружении сервер запускают без него, а число процессов масштабируют отдельно.
gunicorn с uvicorn-воркерами
Сам uvicorn — это один процесс. Чтобы держать несколько процессов и управлять их жизненным циклом (рестарт упавших, плавный перезапуск), классически берут gunicorn как менеджер процессов, а внутри него — uvicorn-воркеры. Gunicorn следит за воркерами, uvicorn внутри каждого исполняет ваше ASGI-приложение.
# gunicorn управляет процессами, класс воркера — uvicorn
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000
Здесь --worker-class uvicorn.workers.UvicornWorker — ключевая деталь: без неё gunicorn попытается запустить приложение как синхронное WSGI и упадёт, ведь FastAPI — ASGI. Каждый воркер — отдельный процесс со своим интерпретатором Python; gunicorn перезапускает воркер, если тот умер, и умеет плавный перезапуск по сигналу. Альтернатива — запускать несколько процессов самим uvicorn (--workers N), но gunicorn даёт более зрелое управление.
Сколько воркеров
Главный вопрос: сколько процессов поднимать? Распространённая отправная формула — 2 × количество_ядер + 1. Но для FastAPI важна оговорка: приложение асинхронное, поэтому много операций ввода-вывода один воркер обслуживает конкурентно своим event loop, не требуя процесса на каждый запрос.
| Профиль нагрузки | Сколько воркеров |
| В основном ожидание I/O (БД, внешние API), код честно async | ≈ число ядер; event loop сам разруливает конкурентность |
| Заметная CPU-работа в обработчиках | ближе к 2 × ядра + 1, чтобы занять все ядра |
| Контейнер с лимитом CPU (Kubernetes) | считайте по выделенным ядрам, а не по ядрам хоста |
Не задирайте число воркеров «на всякий случай»: каждый процесс держит свою копию приложения и потребляет память, а лишние процессы лишь усиливают конкуренцию за CPU. Подбирайте число под реальную нагрузку и измеряйте, а не угадывайте.
За обратным прокси
В проде перед uvicorn почти всегда ставят обратный прокси — обычно nginx. Прокси берёт на себя то, в чём приложение слабо: TLS (HTTPS), отдачу статики, буферизацию медленных клиентов, базовую защиту и балансировку.
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Заголовки X-Forwarded-For и X-Forwarded-Proto сообщают приложению реальный IP клиента и оригинальную схему (http/https), которые иначе теряются за прокси. Чтобы FastAPI/uvicorn доверял этим заголовкам, сервер запускают с --proxy-headers (и при необходимости --forwarded-allow-ips). Без этого вы будете видеть IP прокси вместо клиента и можете неверно строить ссылки по схеме.
Контейнеризация
Стандарт упаковки сервиса сегодня — Docker-образ: один артефакт, который одинаково запускается и локально, и в проде. Базовый Dockerfile для FastAPI выглядит так:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# не root: меньше прав — меньше ущерба при взломе
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "main:app", \
"--workers", "4", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--bind", "0.0.0.0:8000"]
Несколько практик из урока о безопасности применимы и здесь: ставьте зависимости с --no-cache-dir, копируйте requirements.txt отдельным слоем (тогда кэш сборки переиспользуется, пока зависимости не менялись), запускайте процесс под непривилегированным пользователем через USER. В Kubernetes число воркеров на контейнер часто делают небольшим (1–2), а масштабируют горизонтально, добавляя поды.
Как это работает под капотом
ASGI — это стандартный интерфейс между сервером и асинхронным приложением: сервер для каждого запроса формирует scope (метод, путь, заголовки) и вызывает приложение, передавая ему функции receive и send для чтения тела и отправки ответа. uvicorn реализует HTTP-разбор и event loop, а FastAPI — это и есть ASGI-приложение, которое сервер вызывает. Когда gunicorn запускает uvicorn-воркеры, он работает по модели pre-fork: главный процесс fork-ает дочерние, и они наследуют слушающий сокет, поэтому несколько процессов делят один порт, а ОС раздаёт соединения между ними. Каждый воркер крутит собственный event loop. Плавный перезапуск использует сигналы: gunicorn по HUP поднимает новых воркеров и аккуратно гасит старых, поэтому деплой проходит без обрыва соединений — при условии, что воркеры корректно реагируют на сигнал завершения.
Частые ошибки
--reloadв проде. Наблюдатель за файлами ест ресурсы и не рассчитан на нагрузку.- Забыли
UvicornWorker. gunicorn пытается запустить ASGI-приложение как WSGI и падает. - Слишком много воркеров. Лишние процессы съедают память и усиливают борьбу за CPU, не давая выигрыша.
- Нет обратного прокси. Приложение само занимается TLS, статикой и медленными клиентами — не его работа.
- Не настроен
--proxy-headers. За nginx вы видите IP прокси вместо клиента, ссылки строятся по неверной схеме. - Контейнер от root. Нарушает least privilege; добавьте
USER.
Итоги
- FastAPI запускает ASGI-сервер (uvicorn);
--reload— только для разработки, не для прода. - В проде берут gunicorn как менеджер процессов с
UvicornWorker— рестарт воркеров и плавный перезапуск. - Число воркеров подбирайте под нагрузку: для I/O-bound async-кода ≈ число ядер, для CPU-работы ближе к
2 × ядра + 1. - Перед приложением ставьте обратный прокси (nginx) для TLS, статики и балансировки; включайте
--proxy-headers. - Упаковывайте в Docker: отдельный слой зависимостей, запуск под непривилегированным пользователем; в Kubernetes масштабируйте подами.