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