# Концепції розгортання { #deployments-concepts }

Під час розгортання застосунку **FastAPI** (або будь-якого веб-API) є кілька концепцій, які, ймовірно, вас цікавлять, і, спираючись на них, ви зможете знайти **найвідповідніший** спосіб **розгорнути ваш застосунок**.

Деякі важливі концепції:

- Безпека - HTTPS
- Запуск під час старту
- Перезапуски
- Реплікація (кількість запущених процесів)
- Пам'ять
- Попередні кроки перед стартом

Подивимось, як вони впливають на **розгортання**.

Зрештою головна мета - **обслуговувати клієнтів вашого API** так, щоб це було **безпечним**, з **мінімумом перерв у роботі**, і щоб **обчислювальні ресурси** (наприклад, віддалені сервери/віртуальні машини) використовувалися якомога ефективніше. 🚀

Нижче я трохи більше розповім про ці **концепції**, і, сподіваюся, це дасть вам потрібну **інтуїцію**, щоб вирішувати, як розгортати ваш API в дуже різних середовищах, можливо, навіть у **майбутніх**, яких ще не існує.

Враховуючи ці концепції, ви зможете **оцінювати та проєктувати** найкращий спосіб розгортання **ваших власних API**.

У наступних розділах я наведу більш **конкретні рецепти** розгортання застосунків FastAPI.

А поки перегляньмо ці важливі **концептуальні ідеї**. Вони також застосовні до будь-якого іншого типу веб-API. 💡

## Безпека - HTTPS { #security-https }

У [попередньому розділі про HTTPS](https.md) ми дізналися, як HTTPS забезпечує шифрування для вашого API.

Ми також бачили, що HTTPS зазвичай надається компонентом, **зовнішнім** щодо вашого серверного застосунку, - **TLS Termination Proxy**.

І має бути щось, що відповідає за **оновлення сертифікатів HTTPS** - це може бути той самий компонент або інший.

### Приклади інструментів для HTTPS { #example-tools-for-https }

Деякі інструменти, які можна використовувати як TLS Termination Proxy:

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

Ще один варіант - використати **хмарний сервіс**, який зробить більше роботи, зокрема налаштує HTTPS. Можуть бути обмеження або додаткова вартість тощо. Але у такому разі вам не потрібно самостійно налаштовувати TLS Termination Proxy.

У наступних розділах я покажу кілька конкретних прикладів.

---

Далі всі наступні концепції стосуються програми, яка запускає ваш фактичний API (наприклад, Uvicorn).

## Програма і процес { #program-and-process }

Ми багато говоритимемо про запущений «процес», тож корисно чітко розуміти, що це означає, і чим відрізняється від слова «програма».

### Що таке програма { #what-is-a-program }

Слово **програма** зазвичай вживають для опису багатьох речей:

- **Код**, який ви пишете, **файли Python**.
- **Файл**, який може бути **виконаний** операційною системою, наприклад: `python`, `python.exe` або `uvicorn`.
- Конкретна програма під час **виконання** в операційній системі, що використовує CPU та зберігає дані в пам'яті. Це також називають **процесом**.

### Що таке процес { #what-is-a-process }

Слово **процес** зазвичай використовують у більш специфічному значенні, маючи на увазі саме те, що виконується в операційній системі (як у попередньому пункті):

- Конкретна програма під час **виконання** в операційній системі.
    - Це не про файл і не про код, це **конкретно** про те, що **виконується** та керується операційною системою.
- Будь-яка програма, будь-який код **може щось робити** лише під час **виконання**. Тобто коли **процес запущений**.
- Процес може бути **завершений** (або «kill») вами чи операційною системою. У цей момент він припиняє виконання і **більше нічого не може робити**.
- Кожен застосунок, який працює на вашому комп'ютері, має певний процес за собою: кожна запущена програма, кожне вікно тощо. Зазвичай на комп'ютері одночасно працює **багато процесів**.
- **Кілька процесів** **однієї й тієї самої програми** можуть працювати одночасно.

Якщо ви відкриєте «диспетчер завдань» або «системний монітор» (чи подібні інструменти) в операційній системі, ви побачите багато таких процесів.

Наприклад, ви, ймовірно, побачите кілька процесів того самого браузера (Firefox, Chrome, Edge тощо). Зазвичай він запускає один процес на вкладку плюс деякі додаткові процеси.

<img class="shadow" src="/img/deployment/concepts/image01.png">

---

Тепер, коли ми знаємо різницю між термінами **процес** і **програма**, продовжимо говорити про розгортання.

## Запуск під час старту { #running-on-startup }

У більшості випадків, коли ви створюєте веб-API, ви хочете, щоб він **працював постійно**, без перерв, щоб клієнти завжди мали до нього доступ. Звісно, якщо немає особливих причин запускати його лише в певних ситуаціях. Але зазвичай ви хочете, щоб він постійно працював і був **доступний**.

### На віддаленому сервері { #in-a-remote-server }

Коли ви налаштовуєте віддалений сервер (хмарний сервер, віртуальну машину тощо), найпростіше - використовувати `fastapi run` (який використовує Uvicorn) або щось схоже, вручну, так само, як під час локальної розробки.

І це працюватиме та буде корисним **під час розробки**.

Але якщо ви втратите з'єднання з сервером, **запущений процес**, найімовірніше, завершиться.

І якщо сервер буде перезавантажено (наприклад, після оновлень або міграцій у хмарного провайдера), ви, ймовірно, **не помітите цього**. І через це ви навіть не знатимете, що треба вручну перезапустити процес. У результаті ваш API просто залишиться «мертвим». 😱

### Автоматичний запуск під час старту { #run-automatically-on-startup }

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

### Окрема програма { #separate-program }

Щоб цього досягти, зазвичай використовують **окрему програму**, яка гарантує запуск вашого застосунку під час старту. І в багатьох випадках вона також забезпечує запуск інших компонентів або застосунків, наприклад бази даних.

### Приклади інструментів для запуску під час старту { #example-tools-to-run-at-startup }

Приклади інструментів, які можуть це робити:

- Docker
- Kubernetes
- Docker Compose
- Docker у режимі Swarm
- Systemd
- Supervisor
- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів
- Інші...

У наступних розділах я наведу більш конкретні приклади.

## Перезапуски { #restarts }

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

### Ми помиляємося { #we-make-mistakes }

Ми, люди, постійно робимо **помилки**. Майже завжди у програмному забезпеченні є приховані **помилки**. 🐛

І ми як розробники постійно покращуємо код, знаходячи ці помилки та додаючи нові можливості (можливо, теж додаючи нові помилки 😅).

### Невеликі помилки обробляються автоматично { #small-errors-automatically-handled }

Створюючи веб-API з FastAPI, якщо в нашому коді є помилка, FastAPI зазвичай обмежує її одним запитом, який цю помилку спровокував. 🛡

Клієнт отримає **500 Internal Server Error** для цього запиту, але застосунок продовжить працювати для наступних запитів замість повного краху.

### Великі помилки - крахи { #bigger-errors-crashes }

Втім, бувають випадки, коли ми пишемо код, який **падає весь застосунок**, спричиняючи крах Uvicorn і Python. 💥

І все ж ви, ймовірно, не захочете, щоб застосунок залишався «мертвим» через помилку в одному місці - ви, напевно, хочете, щоб він **продовжував працювати** принаймні для тих *операцій шляху*, що не зламані.

### Перезапуск після краху { #restart-after-crash }

Але в таких випадках із серйозними помилками, що призводять до краху запущеного **процесу**, потрібен зовнішній компонент, відповідальний за **перезапуск** процесу, принаймні кілька разів...

/// tip | Порада

...Хоча якщо весь застосунок просто **миттєво падає**, безглуздо перезапускати його безкінечно. Але в таких випадках ви, ймовірно, помітите це під час розробки або принаймні відразу після розгортання.

Тож зосередьмося на основних випадках, коли у майбутньому він може повністю падати за певних обставин, і тоді все ще має сенс його перезапускати.

///

Ймовірно, ви захочете мати відокремлений **зовнішній компонент**, який відповідає за перезапуск застосунку, адже до того моменту сам застосунок з Uvicorn і Python уже впав, і нічого в тому ж коді цієї ж програми зробити не зможе.

### Приклади інструментів для автоматичного перезапуску { #example-tools-to-restart-automatically }

У більшості випадків той самий інструмент, який використовується для **запуску програми під час старту**, також використовується для автоматичних **перезапусків**.

Наприклад, це можуть забезпечувати:

- Docker
- Kubernetes
- Docker Compose
- Docker у режимі Swarm
- Systemd
- Supervisor
- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів
- Інші...

## Реплікація - процеси та пам'ять { #replication-processes-and-memory }

У застосунку FastAPI, використовуючи серверну програму, як-от команду `fastapi`, що запускає Uvicorn, один запуск в **одному процесі** може обслуговувати кількох клієнтів рівночасно.

Але часто ви захочете запускати кілька процесів-працівників одночасно.

### Кілька процесів - працівники { #multiple-processes-workers }

Якщо у вас більше клієнтів, ніж може обробити один процес (наприклад, якщо віртуальна машина не надто потужна) і на сервері є **кілька ядер** CPU, тоді ви можете запустити **кілька процесів** із тим самим застосунком паралельно і розподіляти запити між ними.

Коли ви запускаєте **кілька процесів** того самого програмного забезпечення API, їх зазвичай називають **працівниками** (workers).

### Процеси-працівники і порти { #worker-processes-and-ports }

Пам'ятаєте з документації [Про HTTPS](https.md), що на сервері лише один процес може слухати певну комбінацію порту та IP-адреси?

Это досі так.

Отже, щоб мати **кілька процесів** одночасно, має бути **єдиний процес, який слухає порт**, і який далі якимось чином передає комунікацію кожному процесу-працівнику.

### Пам'ять на процес { #memory-per-process }

Коли програма завантажує щось у пам'ять, наприклад модель машинного навчання в змінну або вміст великого файлу в змінну, все це **споживає частину пам'яті (RAM)** сервера.

І кілька процесів зазвичай **не діляться пам'яттю**. Це означає, що кожен запущений процес має власні речі, змінні та пам'ять. І якщо у вашому коді споживається багато пам'яті, **кожен процес** споживатиме еквівалентний обсяг пам'яті.

### Пам'ять сервера { #server-memory }

Наприклад, якщо ваш код завантажує модель машинного навчання розміром **1 GB**, то при запуску одного процесу з вашим API він споживатиме щонайменше 1 GB RAM. А якщо ви запустите **4 процеси** (4 працівники) - кожен споживатиме 1 GB RAM. Отже, загалом ваш API споживатиме **4 GB RAM**.

І якщо ваш віддалений сервер або віртуальна машина має лише 3 GB RAM, спроба використати понад 4 GB призведе до проблем. 🚨

### Кілька процесів - приклад { #multiple-processes-an-example }

У цьому прикладі є **керівний процес** (Manager Process), який запускає і контролює два **процеси-працівники**.

Цей керівний процес, імовірно, саме і слухатиме **порт** на IP. І він передаватиме всю комунікацію процесам-працівникам.

Ці процеси-працівники виконуватимуть ваш застосунок, здійснюватимуть основні обчислення, щоб отримати **запит** і повернути **відповідь**, і завантажуватимуть усе, що ви зберігаєте в змінних у RAM.

<img src="/img/deployment/concepts/process-ram.drawio.svg">

Звісно, на тій самій машині, окрім вашого застосунку, зазвичай працюватимуть **інші процеси**.

Цікавий момент: відсоток **використання CPU** кожним процесом може сильно **варіюватися** з часом, тоді як **пам'ять (RAM)** зазвичай залишається більш-менш **стабільною**.

Якщо у вас API, що виконує порівняний обсяг обчислень щоразу і у вас багато клієнтів, тоді **використання CPU** також, ймовірно, буде *стабільним* (замість постійних швидких коливань).

### Приклади інструментів і стратегій реплікації { #examples-of-replication-tools-and-strategies }

Є кілька підходів для цього, і про конкретні стратегії я розповім у наступних розділах, наприклад, коли говоритимемо про Docker і контейнери.

Головне обмеження: має бути **єдиний** компонент, що обробляє **порт** на **публічній IP-адресі**. А тоді він має мати спосіб **передавати** комунікацію реплікованим **процесам/працівникам**.

Ось кілька можливих комбінацій і стратегій:

- **Uvicorn** з `--workers`
    - Один **менеджер процесів** Uvicorn слухатиме **IP** і **порт** та запускатиме **кілька процесів-працівників Uvicorn**.
- **Kubernetes** та інші розподілені **системи контейнерів**
    - Щось на рівні **Kubernetes** слухатиме **IP** і **порт**. Реплікація відбуватиметься через **кілька контейнерів**, у кожному з яких запущено **один процес Uvicorn**.
- **Хмарні сервіси**, що роблять це за вас
    - Хмарний сервіс, ймовірно, **забезпечить реплікацію за вас**. Він може дозволяти визначити **процес для запуску** або **образ контейнера** для використання; у будь-якому разі це, найімовірніше, буде **один процес Uvicorn**, а сервіс відповідатиме за його реплікацію.

/// tip | Порада

Не хвилюйтеся, якщо деякі пункти про **контейнери**, Docker чи Kubernetes поки що не дуже зрозумілі.

Я розповім більше про образи контейнерів, Docker, Kubernetes тощо в майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md).

///

## Попередні кроки перед стартом { #previous-steps-before-starting }

Є багато випадків, коли потрібно виконати деякі кроки **перед стартом** вашого застосунку.

Наприклад, ви можете захотіти запустити **міграції бази даних**.

Але найчастіше ці кроки потрібно виконувати лише **один раз**.

Отже, ви захочете мати **єдиний процес**, який виконає ці **попередні кроки** перед запуском застосунку.

І потрібно переконатися, що їх виконує саме один процес навіть якщо потім ви запускаєте **кілька процесів** (кілька працівників) для самого застосунку. Якщо ці кроки виконуватимуться **кількома процесами**, вони **подвоюватимуть** роботу, виконуючи її **паралельно**, і якщо кроки делікатні, як-от міграції бази даних, це може призвести до конфліктів.

Звісно, бувають випадки, коли немає проблеми запускати попередні кроки кілька разів - тоді все набагато простіше.

/// tip | Порада

Також майте на увазі, що залежно від вашого налаштування інколи **попередні кроки взагалі не потрібні** перед запуском застосунку.

У такому разі про це можна не турбуватися. 🤷

///

### Приклади стратегій попередніх кроків { #examples-of-previous-steps-strategies }

Це **значною мірою залежить** від способу **розгортання вашої системи** і, ймовірно, буде пов'язано зі способом запуску програм, обробки перезапусків тощо.

Ось кілька можливих ідей:

- «Init Container» у Kubernetes, який виконується перед вашим контейнером застосунку
- bash-скрипт, який виконує попередні кроки, а потім запускає ваш застосунок
    - Вам усе одно потрібен спосіб запускати/перезапускати цей bash-скрипт, виявляти помилки тощо.

/// tip | Порада

Я наведу більш конкретні приклади для цього з контейнерами у майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md).

///

## Використання ресурсів { #resource-utilization }

Ваш сервер(и) - це **ресурс**, який ви можете споживати/**використовувати** вашими програмами: час обчислень на CPU та доступну RAM.

Скільки системних ресурсів ви хочете споживати/використовувати? Легко подумати «небагато», але насправді ви, ймовірно, захочете споживати **настільки багато, наскільки можливо без краху**.

Якщо ви платите за 3 сервери, але використовуєте лише трохи їх RAM і CPU, ви, ймовірно, **марнуєте гроші** 💸 і, можливо, **електроенергію серверів** 🌎 тощо.

У такому разі може бути краще мати лише 2 сервери й використовувати більший відсоток їхніх ресурсів (CPU, пам'ять, диск, пропускну здатність мережі тощо).

З іншого боку, якщо у вас 2 сервери і ви використовуєте **100% їхнього CPU та RAM**, у певний момент якийсь процес попросить більше пам'яті, і сервер муситиме використати диск як «пам'ять» (що може бути у тисячі разів повільнішим) або навіть **впасти**. Або процесу знадобляться обчислення, і він чекатиме, доки CPU знову звільниться.

У цьому випадку краще додати **ще один сервер** і запустити частину процесів на ньому, щоб у всіх було **достатньо RAM та часу CPU**.

Також можлива ситуація, коли з певної причини ви отримаєте **стрибок** використання вашого API. Можливо, він став вірусним або його почали використовувати інші сервіси чи боти. І ви можете захотіти мати додаткові ресурси на випадок таких ситуацій.

Ви можете встановити **довільний цільовий** рівень, наприклад, **між 50% і 90%** використання ресурсів. Суть у тому, що це, ймовірно, головні речі, які ви захочете вимірювати й використовувати для налаштування розгортань.

Ви можете використати прості інструменти, як-от `htop`, щоб побачити використання CPU і RAM на сервері або кількість, спожиту кожним процесом. Або складніші засоби моніторингу, розподілені між серверами тощо.

## Підсумок { #recap }

Тут ви прочитали про основні концепції, які, ймовірно, потрібно тримати в голові, вирішуючи, як розгортати ваш застосунок:

- Безпека - HTTPS
- Запуск під час старту
- Перезапуски
- Реплікація (кількість запущених процесів)
- Пам'ять
- Попередні кроки перед стартом

Розуміння цих ідей і того, як їх застосовувати, має дати вам інтуїцію, необхідну для прийняття рішень під час конфігурування і тонкого налаштування ваших розгортань. 🤓

У наступних розділах я наведу більше конкретних прикладів можливих стратегій, якими ви можете скористатися. 🚀
