Перейти к содержанию

Концепции развёртывания

При развёртывании приложения FastAPI (и вообще любого веб‑API) есть несколько концепций, о которых стоит думать — с их помощью можно выбрать наиболее подходящий способ развёртывания вашего приложения.

Некоторые из важных концепций:

  • Безопасность — HTTPS
  • Запуск при старте
  • Перезапуски
  • Репликация (количество запущенных процессов)
  • Память
  • Предварительные шаги перед запуском

Посмотрим, как они влияют на развёртывания.

В конечном итоге цель — обслуживать клиентов вашего API безопасно, избегать перебоев и максимально эффективно использовать вычислительные ресурсы (например, удалённые серверы/виртуальные машины). 🚀

Здесь я немного расскажу о этих концепциях, чтобы у вас появилась интуиция, как развёртывать ваш API в разных окружениях, возможно даже в будущих, которых ещё не существует.

Учитывая эти концепции, вы сможете оценить и спроектировать лучший способ развёртывания своих API.

В следующих главах я дам более конкретные рецепты по развёртыванию приложений FastAPI.

А пока давайте разберём важные идеи. Эти концепции применимы и к другим типам веб‑API. 💡

Безопасность — HTTPS

В предыдущей главе про HTTPS мы разобрались, как HTTPS обеспечивает шифрование для вашего API.

Также мы увидели, что HTTPS обычно обеспечивает компонент, внешний по отношению к серверу вашего приложения — TLS Termination Proxy.

И должен быть компонент, отвечающий за обновление HTTPS‑сертификатов — это может быть тот же самый компонент или отдельный.

Примеры инструментов для HTTPS

Некоторые инструменты, которые можно использовать как TLS Termination Proxy:

  • Traefik
    • Автоматически обновляет сертификаты ✨
  • Caddy
    • Автоматически обновляет сертификаты ✨
  • Nginx
    • С внешним компонентом (например, Certbot) для обновления сертификатов
  • HAProxy
    • С внешним компонентом (например, Certbot) для обновления сертификатов
  • Kubernetes с Ingress Controller (например, Nginx)
    • С внешним компонентом (например, cert-manager) для обновления сертификатов
  • Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇)

Другой вариант — использовать облачный сервис, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy.

В следующих главах я покажу конкретные примеры.


Далее рассмотрим концепции, связанные с программой, которая запускает ваш реальный API (например, Uvicorn).

Программа и процесс

Мы часто будем говорить о работающем "процессе", поэтому полезно чётко понимать, что это значит и чем отличается от "программы".

Что такое программа

Словом программа обычно называют разные вещи:

  • Код, который вы пишете, то есть Python‑файлы.
  • Файл, который может быть запущен операционной системой, например: python, python.exe или uvicorn.
  • Конкретную программу в момент, когда она работает в операционной системе, используя CPU и память. Это также называют процессом.

Что такое процесс

Слово процесс обычно используют более конкретно — только для того, что реально выполняется в операционной системе (как в последнем пункте выше):

  • Конкретная программа в момент, когда она запущена в операционной системе.
    • Речь не о файле и не о коде, а конкретно о том, что исполняется и управляется операционной системой.
  • Любая программа, любой код могут что‑то делать только когда исполняются, то есть когда есть работающий процесс.
  • Процесс можно завершить (или «убить») вами или операционной системой. В этот момент он перестаёт выполняться и больше ничего делать не может.
  • У каждого запущенного приложения на вашем компьютере есть свой процесс; у каждой программы, у каждого окна и т.д. Обычно одновременно работает много процессов, пока компьютер включён.
  • Могут одновременно работать несколько процессов одной и той же программы.

Если вы посмотрите «диспетчер задач» или «системный монитор» (или аналогичные инструменты) в вашей операционной системе, то увидите множество работающих процессов.

Например, вы, скорее всего, увидите несколько процессов одного и того же браузера (Firefox, Chrome, Edge и т.д.). Обычно браузеры запускают один процесс на вкладку плюс дополнительные процессы.


Теперь, когда мы понимаем разницу между процессом и программой, продолжим разговор о развёртываниях.

Запуск при старте

В большинстве случаев, создавая веб‑API, вы хотите, чтобы он работал постоянно, без перерывов, чтобы клиенты всегда могли к нему обратиться. Разве что у вас есть особые причины запускать его только при определённых условиях, но обычно вы хотите, чтобы он был постоянно запущен и доступен.

На удалённом сервере

Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самый простой вариант — вручную использовать fastapi run (он использует Uvicorn) или что‑то похожее, как вы делаете при локальной разработке.

Это будет работать и полезно во время разработки.

Но если соединение с сервером прервётся, запущенный процесс, скорее всего, завершится.

А если сервер перезагрузится (например, после обновлений или миграций у облачного провайдера), вы, вероятно, даже не заметите этого. Из‑за этого вы не узнаете, что нужно вручную перезапустить процесс — и ваш API просто будет «мёртв». 😱

Автоматический запуск при старте

Как правило, вы захотите, чтобы серверная программа (например, Uvicorn) запускалась автоматически при старте сервера и без участия человека, чтобы всегда был процесс, запущенный с вашим API (например, Uvicorn, запускающий ваше приложение FastAPI).

Отдельная программа

Чтобы этого добиться, обычно используют отдельную программу, которая гарантирует запуск вашего приложения при старте. Во многих случаях она также запускает и другие компоненты/приложения, например базу данных.

Примеры инструментов для запуска при старте

Примеры инструментов, которые могут с этим справиться:

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker в режиме Swarm (Swarm Mode)
  • Systemd
  • Supervisor
  • Обработка внутри облачного провайдера как часть его услуг
  • Прочие...

Более конкретные примеры будут в следующих главах.

Перезапуски

Подобно тому как вы обеспечиваете запуск приложения при старте, вы, вероятно, захотите обеспечить его перезапуск после сбоев.

Мы ошибаемся

Мы, люди, постоянно совершаем ошибки. В программном обеспечении почти всегда есть баги, скрытые в разных местах. 🐛

И мы, как разработчики, продолжаем улучшать код — находим баги и добавляем новые возможности (иногда добавляя новые баги 😅).

Небольшие ошибки обрабатываются автоматически

Создавая веб‑API с FastAPI, если в нашем коде возникает ошибка, FastAPI обычно «локализует» её в пределах одного запроса, который эту ошибку вызвал. 🛡

Клиент получит 500 Internal Server Error для этого запроса, но приложение продолжит работать для последующих запросов, а не «упадёт» целиком.

Большие ошибки — падения

Тем не менее возможны случаи, когда код роняет всё приложение, приводя к сбою Uvicorn и Python. 💥

И вы, скорее всего, не захотите, чтобы приложение оставалось «мёртвым» из‑за ошибки в одном месте — вы захотите, чтобы оно продолжало работать хотя бы для операций пути, которые не сломаны.

Перезапуск после падения

В случаях действительно серьёзных ошибок, которые роняют работающий процесс, вам понадобится внешний компонент, отвечающий за перезапуск процесса, как минимум пару раз...

Совет

...Хотя если приложение падает сразу же, вероятно, нет смысла перезапускать его бесконечно. Но такие случаи вы, скорее всего, заметите во время разработки или как минимум сразу после развёртывания.

Давайте сосредоточимся на основных сценариях, когда в каких‑то конкретных ситуациях в будущем приложение может падать целиком, и при этом имеет смысл его перезапускать.

Скорее всего, вы захотите, чтобы перезапуском вашего приложения занимался внешний компонент, потому что к тому моменту Uvicorn и Python уже упали, и внутри того же кода вашего приложения сделать уже ничего нельзя.

Примеры инструментов для автоматического перезапуска

В большинстве случаев тот же инструмент, который запускает программу при старте, умеет обрабатывать и автоматические перезапуски.

Например, это может быть:

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker в режиме Swarm (Swarm Mode)
  • Systemd
  • Supervisor
  • Обработка внутри облачного провайдера как часть его услуг
  • Прочие...

Репликация — процессы и память

В приложении FastAPI, используя серверную программу (например, команду fastapi, которая запускает Uvicorn), запуск в одном процессе уже позволяет обслуживать нескольких клиентов одновременно.

Но во многих случаях вы захотите одновременно запустить несколько процессов‑воркеров.

Несколько процессов — Воркеры

Если клиентов больше, чем способен обслужить один процесс (например, если виртуальная машина не слишком мощная), и на сервере есть несколько ядер CPU, вы можете запустить несколько процессов одного и того же приложения параллельно и распределять запросы между ними.

Когда вы запускаете несколько процессов одной и той же программы API, их обычно называют воркерами.

Процессы‑воркеры и порты

Помните из раздела Об HTTPS, что на сервере только один процесс может слушать конкретную комбинацию порта и IP‑адреса?

Это по‑прежнему так.

Поэтому, чтобы одновременно работало несколько процессов, должен быть один процесс, слушающий порт, который затем каким‑то образом передаёт коммуникацию каждому воркер‑процессу.

Память на процесс

Когда программа загружает что‑то в память (например, модель машинного обучения в переменную или содержимое большого файла в переменную), всё это потребляет часть памяти (RAM) сервера.

И разные процессы обычно не делят память. Это значит, что у каждого процесса свои переменные и своя память. Если ваш код потребляет много памяти, то каждый процесс будет потреблять сопоставимый объём памяти.

Память сервера

Например, если ваш код загружает модель Машинного обучения размером 1 ГБ, то при запуске одного процесса с вашим API он будет использовать как минимум 1 ГБ RAM. А если вы запустите 4 процесса (4 воркера), каждый процесс будет использовать 1 ГБ RAM. Всего ваш API будет потреблять 4 ГБ RAM.

И если у вашего удалённого сервера или виртуальной машины только 3 ГБ RAM, попытка загрузить более 4 ГБ вызовет проблемы. 🚨

Несколько процессов — пример

В этом примере есть процесс‑менеджер, который запускает и контролирует два процесса‑воркера.

Процесс‑менеджер, вероятно, будет тем, кто слушает порт на IP. И он будет передавать всю коммуникацию воркер‑процессам.

Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения запроса и возврата ответа, и загружать всё, что вы кладёте в переменные, в RAM.

Конечно, на той же машине помимо вашего приложения, скорее всего, будут работать и другие процессы.

Интересная деталь: процент использования CPU каждым процессом со временем может сильно меняться, но память (RAM) обычно остаётся более‑менее стабильной.

Если у вас API, который каждый раз выполняет сопоставимый объём вычислений, и у вас много клиентов, то загрузка процессора, вероятно, тоже будет стабильной (вместо того, чтобы быстро и постоянно «скакать»).

Примеры инструментов и стратегий репликации

Есть несколько подходов для достижения этого, и я расскажу больше о конкретных стратегиях в следующих главах, например, говоря о Docker и контейнерах.

Главное ограничение: должен быть один компонент, который обрабатывает порт на публичном IP. И у него должен быть способ передавать коммуникацию реплицированным процессам/воркерам.

Некоторые возможные комбинации и стратегии:

  • Uvicorn с --workers
    • Один процесс‑менеджер Uvicorn будет слушать IP и порт и запускать несколько процессов‑воркеров Uvicorn.
  • Kubernetes и другие распределённые контейнерные системы
    • Некий компонент на уровне Kubernetes будет слушать IP и порт. Репликация достигается с помощью нескольких контейнеров, в каждом из которых работает один процесс Uvicorn.
  • Облачные сервисы, которые берут это на себя
    • Облачный сервис, скорее всего, возьмёт репликацию на себя. Он, возможно, позволит указать процесс для запуска или образ контейнера. В любом случае это, скорее всего, будет один процесс Uvicorn, а сервис займётся его репликацией.

Совет

Не беспокойтесь, если некоторые пункты про контейнеры, Docker или Kubernetes пока кажутся неочевидными.

Я расскажу больше про образы контейнеров, Docker, Kubernetes и т.п. в следующей главе: FastAPI внутри контейнеров — Docker.

Предварительные шаги перед запуском

Во многих случаях вы захотите выполнить некоторые шаги перед запуском приложения.

Например, запустить миграции базы данных.

Но чаще всего эти шаги нужно выполнять только один раз.

Поэтому вы захотите иметь один процесс, который выполнит эти предварительные шаги, прежде чем запускать приложение.

И вам нужно будет убедиться, что это делает один процесс даже если потом вы запускаете несколько процессов (несколько воркеров) самого приложения. Если эти шаги выполнят несколько процессов, они дублируют работу, запустив её параллельно, и, если речь о чём‑то деликатном (например, миграции БД), это может вызвать конфликты.

Конечно, бывают случаи, когда нет проблем, если предварительные шаги выполняются несколько раз — тогда всё проще.

Совет

Также учтите, что в зависимости от вашей схемы развёртывания в некоторых случаях предварительные шаги могут вовсе не требоваться.

Тогда об этом можно не беспокоиться. 🤷

Примеры стратегий для предварительных шагов

Это будет сильно зависеть от того, как вы развёртываете систему, как запускаете программы, обрабатываете перезапуски и т.д.

Некоторые возможные идеи:

  • «Init Container» в Kubernetes, который запускается перед контейнером с приложением
  • Bash‑скрипт, который выполняет предварительные шаги, а затем запускает приложение
    • При этом всё равно нужен способ запускать/перезапускать этот bash‑скрипт, обнаруживать ошибки и т.п.

Совет

Я приведу более конкретные примеры с контейнерами в следующей главе: FastAPI внутри контейнеров — Docker.

Использование ресурсов

Ваш сервер(а) — это ресурс, который ваши программы могут потреблять или использовать: время вычислений на CPU и доступную оперативную память (RAM).

Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, скорее всего, захотите потреблять максимум без падений.

Если вы платите за 3 сервера, но используете лишь малую часть их RAM и CPU, вы, вероятно, тратите деньги впустую 💸 и электроэнергию серверов 🌎 и т.п.

В таком случае лучше иметь 2 сервера и использовать более высокий процент их ресурсов (CPU, память, диск, сетевую полосу и т.д.).

С другой стороны, если у вас 2 сервера и вы используете 100% их CPU и RAM, в какой‑то момент один процесс попросит больше памяти, и сервер начнёт использовать диск как «память» (что в тысячи раз медленнее) или даже упадёт. Или процессу понадобятся вычисления, но ему придётся ждать освобождения CPU.

В таком случае лучше добавить ещё один сервер и запустить часть процессов на нём, чтобы у всех было достаточно RAM и времени CPU.

Также возможен всплеск использования вашего API: он мог «взорваться» по популярности, или какие‑то сервисы/боты начали его активно использовать. На такие случаи стоит иметь запас ресурсов.

Можно задать целевое значение, например между 50% и 90% использования ресурсов. Скорее всего, именно эти вещи вы будете измерять и на их основе настраивать развёртывание.

Можно использовать простые инструменты вроде htop, чтобы смотреть загрузку CPU и RAM на сервере или по процессам. Или более сложные распределённые системы мониторинга.

Резюме

Здесь вы прочитали о некоторых основных концепциях, которые, вероятно, стоит учитывать при выборе способа развёртывания приложения:

  • Безопасность — HTTPS
  • Запуск при старте
  • Перезапуски
  • Репликация (количество запущенных процессов)
  • Память
  • Предварительные шаги перед запуском

Понимание этих идей и того, как их применять, даст вам интуицию, необходимую для принятия решений при настройке и доработке ваших развёртываний. 🤓

В следующих разделах я приведу более конкретные примеры возможных стратегий. 🚀