Browse Source
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>pull/9612/head
committed by
GitHub
2 changed files with 312 additions and 0 deletions
@ -0,0 +1,311 @@ |
|||
# Концепции развёртывания |
|||
|
|||
Существует несколько концепций, применяемых для развёртывания приложений **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 |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓 |
|||
|
|||
В следующих разделах я приведу более конкретные примеры возможных стратегий, которым Вы можете следовать. 🚀 |
Loading…
Reference in new issue