mirror of https://github.com/tiangolo/fastapi.git
324 lines
32 KiB
Markdown
324 lines
32 KiB
Markdown
# Концепции развёртывания
|
||
|
||
Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых вы можете выбрать **наиболее подходящий** способ.
|
||
|
||
Самые важные из них:
|
||
|
||
* Использование более безопасного протокола HTTPS
|
||
* Настройки запуска приложения
|
||
* Перезагрузка приложения
|
||
* Запуск нескольких экземпляров приложения
|
||
* Управление памятью
|
||
* Использование перечисленных функций перед запуском приложения.
|
||
|
||
Рассмотрим ниже влияние каждого из них на процесс **развёртывания**.
|
||
|
||
Наша конечная цель - **обслуживать клиентов вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀
|
||
|
||
Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у вас сложится **интуитивное понимание**, какой способ выбрать при развертывании вашего API в различных окружениях, возможно, даже **ещё не существующих**.
|
||
|
||
Ознакомившись с этими концепциями, вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**.
|
||
|
||
В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI.
|
||
|
||
А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡
|
||
|
||
## Использование более безопасного протокола HTTPS
|
||
|
||
В [предыдущей главе об HTTPS](https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для вашего API.
|
||
|
||
Также мы заметили, что обычно для работы с HTTPS вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**.
|
||
|
||
И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия.
|
||
|
||
### Примеры инструментов для работы с HTTPS
|
||
|
||
Вот некоторые инструменты, которые вы можете применять как прокси-серверы:
|
||
|
||
* Traefik
|
||
* С автоматическим обновлением сертификатов ✨
|
||
* Caddy
|
||
* С автоматическим обновлением сертификатов ✨
|
||
* Nginx
|
||
* С дополнительным компонентом типа Certbot для обновления сертификатов
|
||
* HAProxy
|
||
* С дополнительным компонентом типа Certbot для обновления сертификатов
|
||
* Kubernetes с Ingress Controller похожим на Nginx
|
||
* С дополнительным компонентом типа cert-manager для обновления сертификатов
|
||
* Использование услуг облачного провайдера (читайте ниже 👇)
|
||
|
||
В последнем варианте вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера.
|
||
|
||
В дальнейшем я покажу Вам некоторые конкретные примеры их применения.
|
||
|
||
---
|
||
|
||
Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn).
|
||
|
||
## Программа и процесс
|
||
|
||
Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними.
|
||
|
||
### Что такое программа
|
||
|
||
Термином **программа** обычно описывают множество вещей:
|
||
|
||
* **Код**, который вы написали, в нашем случае **Python-файлы**.
|
||
* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`.
|
||
* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**.
|
||
|
||
### Что такое процесс
|
||
|
||
Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца):
|
||
|
||
* Конкретная программа, **запущенная** операционной системой.
|
||
* Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой.
|
||
* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**.
|
||
* Процесс может быть **прерван** (или "убит") Вами или вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**.
|
||
* Каждое приложение, которое вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов.
|
||
* И **одна программа** может запустить **несколько параллельных процессов**.
|
||
|
||
Если вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) вашей операционной системы, то увидите множество работающих процессов.
|
||
|
||
Вполне вероятно, что вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы.
|
||
|
||
<img class="shadow" src="/img/deployment/concepts/image01.png">
|
||
|
||
---
|
||
|
||
Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания.
|
||
|
||
## Настройки запуска приложения
|
||
|
||
В большинстве случаев когда вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у вас могут быть причины, чтоб оно запускалось только при определённых условиях.
|
||
|
||
### Удалённый сервер
|
||
|
||
Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как вы делаете при локальной разработке.
|
||
|
||
Это рабочий способ и он полезен **во время разработки**.
|
||
|
||
Но если вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**.
|
||
|
||
И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱
|
||
|
||
### Автоматический запуск программ
|
||
|
||
Вероятно вы захотите, чтоб Ваша серверная программа (такая, как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI).
|
||
|
||
### Отдельная программа
|
||
|
||
Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных
|
||
|
||
### Примеры инструментов, управляющих запуском программ
|
||
|
||
Вот несколько примеров, которые могут справиться с такой задачей:
|
||
|
||
* Docker
|
||
* Kubernetes
|
||
* Docker Compose
|
||
* Docker в режиме Swarm
|
||
* Systemd
|
||
* Supervisor
|
||
* Использование услуг облачного провайдера
|
||
* Прочие...
|
||
|
||
Я покажу Вам некоторые примеры их использования в следующих главах.
|
||
|
||
## Перезапуск
|
||
|
||
Вы, вероятно, также захотите, чтоб ваше приложение **перезапускалось**, если в нём произошёл сбой.
|
||
|
||
### Мы ошибаемся
|
||
|
||
Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛
|
||
|
||
И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅).
|
||
|
||
### Небольшие ошибки обрабатываются автоматически
|
||
|
||
Когда вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡
|
||
|
||
Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами.
|
||
|
||
### Большие ошибки - Падение приложений
|
||
|
||
Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥
|
||
|
||
Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок.
|
||
|
||
### Перезапуск после падения
|
||
|
||
Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз...
|
||
|
||
/// tip | "Заметка"
|
||
|
||
... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания.
|
||
|
||
Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново.
|
||
|
||
///
|
||
|
||
Возможно вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в вашем коде внутри приложения, не может быть выполнено в принципе.
|
||
|
||
### Примеры инструментов для автоматического перезапуска
|
||
|
||
В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы.
|
||
|
||
В качестве примера можно взять те же:
|
||
|
||
* Docker
|
||
* Kubernetes
|
||
* Docker Compose
|
||
* Docker в режиме Swarm
|
||
* Systemd
|
||
* Supervisor
|
||
* Использование услуг облачного провайдера
|
||
* Прочие...
|
||
|
||
## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память
|
||
|
||
Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно.
|
||
|
||
Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов.
|
||
|
||
### Множество процессов - Воркеры (Workers)
|
||
|
||
Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами.
|
||
|
||
**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**.
|
||
|
||
### Процессы и порты́
|
||
|
||
Помните ли Вы, как на странице [Об HTTPS](https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта?
|
||
|
||
С тех пор ничего не изменилось.
|
||
|
||
Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу.
|
||
|
||
### У каждого процесса своя память
|
||
|
||
Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера.
|
||
|
||
Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти.
|
||
|
||
### Память сервера
|
||
|
||
Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате вашему API потребуется **4 ГБ оперативной памяти (RAM)**.
|
||
|
||
И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨
|
||
|
||
### Множество процессов - Пример
|
||
|
||
В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**.
|
||
|
||
Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам.
|
||
|
||
Каждый из этих процессов будет запускать ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память.
|
||
|
||
<img src="/img/deployment/concepts/process-ram.svg">
|
||
|
||
Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к вашему приложению.
|
||
|
||
Интересная деталь заключается в том, что процент **использования центрального процессора (CPU)** каждым процессом может сильно меняться с течением времени, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**.
|
||
|
||
Если у вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться).
|
||
|
||
### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения
|
||
|
||
Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах.
|
||
|
||
Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**.
|
||
|
||
Вот некоторые возможные комбинации и стратегии:
|
||
|
||
* **Gunicorn** управляющий **воркерами Uvicorn**
|
||
* Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**.
|
||
* **Uvicorn** управляющий **воркерами Uvicorn**
|
||
* Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**.
|
||
* **Kubernetes** и аналогичные **контейнерные системы**
|
||
* Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**.
|
||
* **Облачные сервисы**, которые позаботятся обо всём за Вас
|
||
* Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости.
|
||
|
||
/// tip | "Заметка"
|
||
|
||
Если вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте.
|
||
|
||
Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}.
|
||
|
||
///
|
||
|
||
## Шаги, предшествующие запуску
|
||
|
||
Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения.
|
||
|
||
Например, запустить **миграции базы данных**.
|
||
|
||
Но в большинстве случаев такие действия достаточно произвести **однократно**.
|
||
|
||
Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения.
|
||
|
||
Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними.
|
||
|
||
Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще.
|
||
|
||
/// tip | "Заметка"
|
||
|
||
Имейте в виду, что в некоторых случаях запуск вашего приложения **может не требовать каких-либо предварительных шагов вовсе**.
|
||
|
||
Что ж, тогда Вам не нужно беспокоиться об этом. 🤷
|
||
|
||
///
|
||
|
||
### Примеры стратегий запуска предварительных шагов
|
||
|
||
Существует **сильная зависимость** от того, как вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д.
|
||
|
||
Вот некоторые возможные идеи:
|
||
|
||
* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением.
|
||
* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение.
|
||
* При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п.
|
||
|
||
/// tip | "Заметка"
|
||
|
||
Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}.
|
||
|
||
///
|
||
|
||
## Утилизация ресурсов
|
||
|
||
Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти.
|
||
|
||
Как много системных ресурсов вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, захотите использовать **максимально возможное количество**.
|
||
|
||
Если вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п.
|
||
|
||
В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д).
|
||
|
||
С другой стороны, если вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится.
|
||
|
||
В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**.
|
||
|
||
Также есть вероятность, что по какой-то причине возник **всплеск** запросов к вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий вы можете захотеть иметь дополнительные ресурсы.
|
||
|
||
При настройке логики развёртываний, вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют.
|
||
|
||
Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов.
|
||
|
||
## Резюме
|
||
|
||
Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений:
|
||
|
||
* Использование более безопасного протокола HTTPS
|
||
* Настройки запуска приложения
|
||
* Перезагрузка приложения
|
||
* Запуск нескольких экземпляров приложения
|
||
* Управление памятью
|
||
* Использование перечисленных функций перед запуском приложения.
|
||
|
||
Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓
|
||
|
||
В следующих разделах я приведу более конкретные примеры возможных стратегий, которым вы можете следовать. 🚀
|