mirror of https://github.com/tiangolo/fastapi.git
🌐 Add Russian translation for `docs/ru/docs/deployment/docker.md` (#9971)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Yois4101 <119609381+Yois4101@users.noreply.github.com>
This commit is contained in:
parent
1f0d9086b3
commit
fe3eaf63e6
|
|
@ -0,0 +1,700 @@
|
|||
# FastAPI и Docker-контейнеры
|
||||
|
||||
При развёртывании приложений FastAPI, часто начинают с создания **образа контейнера на основе Linux**. Обычно для этого используют <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем можно развернуть такой контейнер на сервере одним из нескольких способов.
|
||||
|
||||
Использование контейнеров на основе Linux имеет ряд преимуществ, включая **безопасность**, **воспроизводимость**, **простоту** и прочие.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Торопитесь или уже знакомы с этой технологией? Перепрыгните на раздел [Создать Docker-образ для FastAPI 👇](#docker-fastapi)
|
||||
|
||||
<details>
|
||||
<summary>Развернуть Dockerfile 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.9
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
|
||||
# Если используете прокси-сервер, такой как Nginx или Traefik, добавьте --proxy-headers
|
||||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Что такое "контейнер"
|
||||
|
||||
Контейнеризация - это **легковесный** способ упаковать приложение, включая все его зависимости и необходимые файлы, чтобы изолировать его от других контейнеров (других приложений и компонентов) работающих на этой же системе.
|
||||
|
||||
Контейнеры, основанные на Linux, запускаются используя ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это значит, что они очень легковесные (по сравнению с полноценными виртуальными машинами, полностью эмулирующими работу операционной системы).
|
||||
|
||||
Благодаря этому, контейнеры потребляют **малое количество ресурсов**, сравнимое с процессом запущенным напрямую (виртуальная машина потребует гораздо больше ресурсов).
|
||||
|
||||
Контейнеры также имеют собственные запущенные **изолированные** процессы (но часто только один процесс), файловую систему и сеть, что упрощает развёртывание, разработку, управление доступом и т.п.
|
||||
|
||||
## Что такое "образ контейнера"
|
||||
|
||||
Для запуска **контейнера** нужен **образ контейнера**.
|
||||
|
||||
Образ контейнера - это **замороженная** версия всех файлов, переменных окружения, программ и команд по умолчанию, необходимых для работы приложения. **Замороженный** - означает, что **образ** не запущен и не выполняется, это всего лишь упакованные вместе файлы и метаданные.
|
||||
|
||||
В отличие от **образа контейнера**, хранящего неизменное содержимое, под термином **контейнер** подразумевают запущенный образ, то есть объёкт, который **исполняется**.
|
||||
|
||||
Когда **контейнер** запущен (на основании **образа**), он может создавать и изменять файлы, переменные окружения и т.д. Эти изменения будут существовать только внутри контейнера, но не будут сохраняться в образе контейнера (не будут сохранены на диск).
|
||||
|
||||
Образ контейнера можно сравнить с файлом, содержащем **программу**, например, как файл `main.py`.
|
||||
|
||||
И **контейнер** (в отличие от **образа**) - это на самом деле выполняемый экземпляр образа, примерно как **процесс**. По факту, контейнер запущен только когда запущены его процессы (чаще, всего один процесс) и остановлен, когда запущенных процессов нет.
|
||||
|
||||
## Образы контейнеров
|
||||
|
||||
Docker является одним оз основных инструментов для создания **образов** и **контейнеров** и управления ими.
|
||||
|
||||
Существует общедоступный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений.
|
||||
|
||||
К примеру, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
|
||||
|
||||
Также там представлены и другие полезные образы, такие как базы данных:
|
||||
|
||||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
|
||||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
|
||||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>
|
||||
|
||||
и т.п.
|
||||
|
||||
Использование подготовленных образов значительно упрощает **комбинирование** и использование разных инструментов. Например, Вы можете попытаться использовать новую базу данных. В большинстве случаев можно использовать **официальный образ** и всего лишь указать переменные окружения.
|
||||
|
||||
Таким образом, Вы можете изучить, что такое контейнеризация и Docker, и использовать полученные знания с разными инструментами и компонентами.
|
||||
|
||||
Так, Вы можете запустить одновременно **множество контейнеров** с базой данных, Python-приложением, веб-сервером, React-приложением и соединить их вместе через внутреннюю сеть.
|
||||
|
||||
Все системы управления контейнерами (такие, как Docker или Kubernetes) имеют встроенные возможности для организации такого сетевого взаимодействия.
|
||||
|
||||
## Контейнеры и процессы
|
||||
|
||||
Обычно **образ контейнера** содержит метаданные предустановленной программы или команду, которую следует выполнить при запуске **контейнера**. Также он может содержать параметры, передаваемые предустановленной программе. Похоже на то, как если бы Вы запускали такую программу через терминал.
|
||||
|
||||
Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но Вы можете изменить его так, чтоб он выполнял другие команды и программы.
|
||||
|
||||
Контейнер буде работать до тех пор, пока выполняется его **главный процесс** (команда или программа).
|
||||
|
||||
В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**.
|
||||
|
||||
Контейнер не считается запущенным, если в нём **не выполняется хотя бы один процесс**. Если главный процесс остановлен, значит и контейнер остановлен.
|
||||
|
||||
## Создать Docker-образ для FastAPI
|
||||
|
||||
Что ж, давайте ужё создадим что-нибудь! 🚀
|
||||
|
||||
Я покажу Вам, как собирать **Docker-образ** для FastAPI **с нуля**, основываясь на **официальном образе Python**.
|
||||
|
||||
Такой подход сгодится для **большинства случаев**, например:
|
||||
|
||||
* Использование с **Kubernetes** или аналогичным инструментом
|
||||
* Запуск в **Raspberry Pi**
|
||||
* Использование в облачных сервисах, запускающих образы контейнеров для Вас и т.п.
|
||||
|
||||
### Установить зависимости
|
||||
|
||||
Обычно Вашему приложению необходимы **дополнительные библиотеки**, список которых находится в отдельном файле.
|
||||
|
||||
На название и содержание такого файла влияет выбранный Вами инструмент **установки** этих библиотек (зависимостей).
|
||||
|
||||
Чаще всего это простой файл `requirements.txt` с построчным перечислением библиотек и их версий.
|
||||
|
||||
При этом Вы, для выбора версий, будете использовать те же идеи, что упомянуты на странице [О версиях FastAPI](./versions.md){.internal-link target=_blank}.
|
||||
|
||||
Ваш файл `requirements.txt` может выглядеть как-то так:
|
||||
|
||||
```
|
||||
fastapi>=0.68.0,<0.69.0
|
||||
pydantic>=1.8.0,<2.0.0
|
||||
uvicorn>=0.15.0,<0.16.0
|
||||
```
|
||||
|
||||
Устанавливать зависимости проще всего с помощью `pip`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
Successfully installed fastapi pydantic uvicorn
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! info "Информация"
|
||||
Существуют и другие инструменты управления зависимостями.
|
||||
|
||||
В этом же разделе, но позже, я покажу Вам пример использования Poetry. 👇
|
||||
|
||||
### Создать приложение **FastAPI**
|
||||
|
||||
* Создайте директорию `app` и перейдите в неё.
|
||||
* Создайте пустой файл `__init__.py`.
|
||||
* Создайте файл `main.py` и заполните его:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
### Dockerfile
|
||||
|
||||
В этой же директории создайте файл `Dockerfile` и заполните его:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9
|
||||
|
||||
# (2)
|
||||
WORKDIR /code
|
||||
|
||||
# (3)
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# (4)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (5)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (6)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Начните с официального образа Python, который будет основой для образа приложения.
|
||||
|
||||
2. Укажите, что в дальнейшем команды запускаемые в контейнере, будут выполняться в директории `/code`.
|
||||
|
||||
Инструкция создаст эту директорию внутри контейнера и мы поместим в неё файл `requirements.txt` и директорию `app`.
|
||||
|
||||
3. Скопируете файл с зависимостями из текущей директории в `/code`.
|
||||
|
||||
Сначала копируйте **только** файл с зависимостями.
|
||||
|
||||
Этот файл **изменяется довольно редко**, Docker ищет изменения при постройке образа и если не находит, то использует **кэш**, в котором хранятся предыдущии версии сборки образа.
|
||||
|
||||
4. Установите библиотеки перечисленные в файле с зависимостями.
|
||||
|
||||
Опция `--no-cache-dir` указывает `pip` не сохранять загружаемые библиотеки на локальной машине для использования их в случае повторной загрузки. В контейнере, в случае пересборки этого шага, они всё равно будут удалены.
|
||||
|
||||
!!! note "Заметка"
|
||||
Опция `--no-cache-dir` нужна только для `pip`, она никак не влияет на Docker или контейнеры.
|
||||
|
||||
Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены.
|
||||
|
||||
Ка и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений.
|
||||
|
||||
Использрвание кэша, особенно на этом шаге, позволит Вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**.
|
||||
|
||||
5. Скопируйте директорию `./app` внутрь директории `/code` (в контейнере).
|
||||
|
||||
Так как в этой директории расположен код, который **часто изменяется**, то использование **кэша** на этом шаге будет наименее эффективно, а значит лучше поместить этот шаг **ближе к концу** `Dockerfile`, дабы не терять выгоду от оптимизации предыдущих шагов.
|
||||
|
||||
6. Укажите **команду**, запускающую сервер `uvicorn`.
|
||||
|
||||
`CMD` принимает список строк, разделённых запятыми, но при выполнении объединит их через пробел, собрав из них одну команду, которую Вы могли бы написать в терминале.
|
||||
|
||||
Эта команда будет выполнена в **текущей рабочей директории**, а именно в директории `/code`, котоая указана командой `WORKDIR /code`.
|
||||
|
||||
Так как команда выполняется внутрии директории `/code`, в которую мы поместили папку `./app` с приложением, то **Uvicorn** сможет найти и **импортировать** объект `app` из файла `app.main`.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Если ткнёте на кружок с плюсом, то увидите пояснения. 👆
|
||||
|
||||
На данном этапе структура проекта должны выглядеть так:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
#### Использование прокси-сервера
|
||||
|
||||
Если Вы запускаете контейнер за прокси-сервером завершения TLS (балансирующего нагрузку), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`, которая укажет Uvicorn, что он работает позади прокси-сервера и может доверять заголовкам отправляемым им.
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
#### Кэш Docker'а
|
||||
|
||||
В нашем `Dockerfile` использована полезная хитрость, когда сначала копируется **только файл с зависимостями**, а не вся папка с кодом приложения.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
```
|
||||
|
||||
Docker и подобные ему инструменты **создают** образы контейнеров **пошагово**, добавляя **один слой над другим**, начиная с первой строки `Dockerfile` и добавляя файлы, создаваемые при выполнении каждой инструкции из `Dockerfile`.
|
||||
|
||||
При создании образа используется **внутренний кэш** и если в файлах нет изменений с момента последней сборки образа, то будет **переиспользован** ранее созданный слой образа, а не повторное копирование файлов и создание слоя с нуля.
|
||||
Заметьте, что так как слой следующего шага зависит от слоя предыдущего, то изменения внесённые в промежуточный слой, также повлияют на последующие.
|
||||
|
||||
Избегание копирования файлов не обязательно улучшит ситуацию, но использование кэша на одном шаге, позволит **использовать кэш и на следующих шагах**. Например, можно использовать кэш при установке зависимостей:
|
||||
|
||||
```Dockerfile
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
```
|
||||
|
||||
Файл со списком зависимостей **изменяется довольно редко**. Так что выполнив команду копирования только этого файла, Docker сможет **использовать кэш** на этом шаге.
|
||||
|
||||
А затем **использовать кэш и на следующем шаге**, загружающем и устанавливающем зависимости. И вот тут-то мы и **сэкономим много времени**. ✨ ...а не будем томиться в тягостном ожидании. 😪😆
|
||||
|
||||
Для загрузки и установки необходимых библиотек **может понадобиться несколько минут**, но использование **кэша** занимает несколько **секунд** максимум.
|
||||
|
||||
И так как во время разработки Вы будете часто пересобирать контейнер для проверки работоспособности внесённых изменений, то сэкономленные минуты сложатся в часы, а то и дни.
|
||||
|
||||
Так как папка с кодом приложения **изменяется чаще всего**, то мы расположили её в конце `Dockerfile`, ведь после внесённых в код изменений кэш не будет использован на этом и следующих шагах.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./app /code/app
|
||||
```
|
||||
|
||||
### Создать Docker-образ
|
||||
|
||||
Теперь, когда все файлы на своих местах, давайте создадим образ контейнера.
|
||||
|
||||
* Перейдите в директорию проекта (в ту, где расположены `Dockerfile` и папка `app` с приложением).
|
||||
* Создай образ приложения FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker build -t myimage .
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Обратите внимание, что в конце написана точка - `.`, это то же самое что и `./`, тем самым мы указываем Docker директорию, из которой нужно выполнять сборку образа контейнера.
|
||||
|
||||
В данном случае это та же самая директория (`.`).
|
||||
|
||||
### Запуск Docker-контейнера
|
||||
|
||||
* Запустите контейнер, основанный на Вашем образе:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker run -d --name mycontainer -p 80:80 myimage
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Проверка
|
||||
|
||||
Вы можете проверить, что Ваш Docker-контейнер работает перейдя по ссылке: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> или <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
|
||||
Там Вы увидите:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
## Интерактивная документация API
|
||||
|
||||
Теперь перейдите по ссылке <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
|
||||
Здесь Вы увидите автоматическую интерактивную документацию API (предоставляемую <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
## Альтернативная документация API
|
||||
|
||||
Также Вы можете перейти по ссылке <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> or <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
|
||||
Здесь Вы увидите альтернативную автоматическую документацию API (предоставляемую <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Создание Docker-образа на основе однофайлового приложения FastAPI
|
||||
|
||||
Если Ваше приложение FastAPI помещено в один файл, например, `main.py` и структура Ваших файлов похожа на эту:
|
||||
|
||||
```
|
||||
.
|
||||
├── Dockerfile
|
||||
├── main.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
Вам нужно изменить в `Dockerfile` соответствующие пути копирования файлов:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.9
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (1)
|
||||
COPY ./main.py /code/
|
||||
|
||||
# (2)
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Скопируйте непосредственно файл `main.py` в директорию `/code` (не указывайте `./app`).
|
||||
|
||||
2. При запуске Uvicorn укажите ему, что объект `app` нужно импортировать из файла `main` (вместо импортирования из `app.main`).
|
||||
|
||||
Настройте Uvicorn на использование `main` вместо `app.main` для импорта объекта `app`.
|
||||
|
||||
## Концепции развёртывания
|
||||
|
||||
Давайте вспомним о [Концепциях развёртывания](./concepts.md){.internal-link target=_blank} и применим их к контейнерам.
|
||||
|
||||
Контейнеры - это, в основном, инструмент упрощающий **сборку и развёртывание** приложения и они не обязыают к применению какой-то определённой **концепции развёртывания**, а значит мы можем выбирать нужную стратегию.
|
||||
|
||||
**Хорошая новость** в том, что независимо от выбранной стратегии, мы всё равно можем покрыть все концепции развёртывания. 🎉
|
||||
|
||||
Рассмотрим эти **концепции развёртывания** применительно к контейнерам:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
|
||||
## Использование более безопасного протокола HTTPS
|
||||
|
||||
Если мы определимся, что **образ контейнера** будет содержать только приложение FastAPI, то работу с HTTPS можно организовать **снаружи** контейнера при помощи другого инструмента.
|
||||
|
||||
Это может быть другой контейнер, в котором есть, например, <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, работающий с **HTTPS** и **самостоятельно** обновляющий **сертификаты**.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Traefik совместим с Docker, Kubernetes и им подобными инструментами. Он очень прост в установке и настройке использования HTTPS для Ваших контейнеров.
|
||||
|
||||
В качестве альтернативы, работу с HTTPS можно доверить облачному провайдеру, если он предоставляет такую услугу.
|
||||
|
||||
## Настройки запуска и перезагрузки приложения
|
||||
|
||||
Обычно **запуском контейнера с приложением** занимается какой-то отдельный инструмент.
|
||||
|
||||
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный провайдер** и т.п.
|
||||
|
||||
В большинстве случаев это простейшие настройки запуска и перезагрузки приложения (при падении). Например, команде запуска Docker-контейнера можно добавить опцию `--restart`.
|
||||
|
||||
Управление запуском и перезагрузкой приложений без использования контейнеров - весьма затруднительно. Но при **работе с контейнерами** - это всего лишь функционал доступный по умолчанию. ✨
|
||||
|
||||
## Запуск нескольких экземпляров приложения - Указание количества процессов
|
||||
|
||||
Если у Вас есть <abbr title="Несколько серверов настроенных для совместной работы.">кластер</abbr> машин под управлением **Kubernetes**, Docker Swarm Mode, Nomad или аналогичной сложной системой оркестрации контейнеров, скорее всего, вместо использования менеджера процессов (типа Gunicorn и его воркеры) в каждом контейнере, Вы захотите **управлять количеством запущенных экземпляров приложения** на **уровне кластера**.
|
||||
|
||||
В любую из этих систем управления контейнерами обычно встроен способ управления **количеством запущенных контейнеров** для распределения **нагрузки** от входящих запросов на **уровне кластера**.
|
||||
|
||||
В такой ситуации Вы, вероятно, захотите создать **образ Docker**, как [описано выше](#dockerfile), с установленными зависимостями и запускающий **один процесс Uvicorn** вместо того, чтобы запускать Gunicorn управляющий несколькими воркерами Uvicorn.
|
||||
|
||||
### Балансировщик нагрузки
|
||||
|
||||
Обычно при использовании контейнеров один компонент **прослушивает главный порт**. Это может быть контейнер содержащий **прокси-сервер завершения работы TLS** для работы с **HTTPS** или что-то подобное.
|
||||
|
||||
Поскольку этот компонент **принимает запросы** и равномерно **распределяет** их между компонентами, его также называют **балансировщиком нагрузки**.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
**Прокси-сервер завершения работы TLS** одновременно может быть **балансировщиком нагрузки**.
|
||||
|
||||
Система оркестрации, которую Вы используете для запуска и управления контейнерами, имеет встроенный инструмент **сетевого взаимодействия** (например, для передачи HTTP-запросов) между контейнерами с Вашими приложениями и **балансировщиком нагрузки** (который также может быть **прокси-сервером**).
|
||||
|
||||
### Один балансировщик - Множество контейнеров
|
||||
|
||||
При работе с **Kubernetes** или аналогичными системами оркестрации использование их внутреннней сети позволяет иметь один **балансировщик нагрузки**, который прослушивает **главный** порт и передаёт запросы **множеству запущенных контейнеров** с Вашими приложениями.
|
||||
|
||||
В каждом из контейнеров обычно работает **только один процесс** (например, процесс Uvicorn управляющий Вашим приложением FastAPI). Контейнеры могут быть **идентичными**, запущенными на основе одного и того же образа, но у каждого будут свои отдельные процесс, память и т.п. Таким образом мы получаем преимущества **распараллеливания** работы по **разным ядрам** процессора или даже **разным машинам**.
|
||||
|
||||
Система управления контейнерами с **балансировщиком нагрузки** будет **распределять запросы** к контейнерам с приложениями **по очереди**. То есть каждый запрос будет обработан одним из множества **одинаковых контейнеров** с одним и тем же приложением.
|
||||
|
||||
**Балансировщик нагрузки** может обрабатывать запросы к *разным* приложениям, расположенным в Вашем кластере (например, если у них разные домены или префиксы пути) и передавать запросы нужному контейнеру с требуемым приложением.
|
||||
|
||||
### Один процесс на контейнер
|
||||
|
||||
В этом варианте **в одном контейнере будет запущен только один процесс (Uvicorn)**, а управление изменением количества запущенных копий приложения происходит на уровне кластера.
|
||||
|
||||
Здесь **не нужен** менеджер процессов типа Gunicorn, управляющий процессами Uvicorn, или же Uvicorn, управляющий другими процессами Uvicorn. Достаточно **только одного процесса Uvicorn** на контейнер (но запуск нескольких процессов не запрещён).
|
||||
|
||||
Использование менеджера процессов (Gunicorn или Uvicorn) внутри контейнера только добавляет **излишнее усложнение**, так как управление следует осуществлять системой оркестрации.
|
||||
|
||||
### <a name="special-cases"></a>Множество процессов внутри контейнера для особых случаев</a>
|
||||
|
||||
Безусловно, бывают **особые случаи**, когда может понадобится внутри контейнера запускать **менеджер процессов Gunicorn**, управляющий несколькими **процессами Uvicorn**.
|
||||
|
||||
Для таких случаев Вы можете использовать **официальный Docker-образ** (прим. пер: - *здесь и далее на этой странице, если Вы встретите сочетание "официальный Docker-образ" без уточнений, то автор имеет в виду именно предоставляемый им образ*), где в качестве менеджера процессов используется **Gunicorn**, запускающий несколько **процессов Uvicorn** и некоторые настройки по умолчанию, автоматически устанавливающие количество запущенных процессов в зависимости от количества ядер Вашего процессора. Я расскажу Вам об этом подробнее тут: [Официальный Docker-образ со встроенными Gunicorn и Uvicorn](#docker-gunicorn-uvicorn).
|
||||
|
||||
Некоторые примеры подобных случаев:
|
||||
|
||||
#### Простое приложение
|
||||
|
||||
Вы можете использовать менеджер процессов внутри контейнера, если Ваше приложение **настолько простое**, что у Вас нет необходимости (по крайней мере, пока нет) в тщательных настройках количества процессов и Вам достаточно имеющихся настроек по умолчанию (если используется официальный Docker-образ) для запуска приложения **только на одном сервере**, а не в кластере.
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
С помощью **Docker Compose** можно разворачивать несколько контейнеров на **одном сервере** (не кластере), но при это у Вас не будет простого способа управления количеством запущенных контейнеров с одновременным сохранением общей сети и **балансировки нагрузки**.
|
||||
|
||||
В этом случае можно использовать **менеджер процессов**, управляющий **несколькими процессами**, внутри **одного контейнера**.
|
||||
|
||||
#### Prometheus и прочие причины
|
||||
|
||||
У Вас могут быть и **другие причины**, когда использование **множества процессов** внутри **одного контейнера** будет проще, нежели запуск **нескольких контейнеров** с **единственным процессом** в каждом из них.
|
||||
|
||||
Например (в зависимости от конфигурации), у Вас могут быть инструменты подобные экспортёру Prometheus, которые должны иметь доступ к **каждому запросу** приходящему в контейнер.
|
||||
|
||||
Если у Вас будет **несколько контейнеров**, то Prometheus, по умолчанию, **при сборе метрик** получит их **только с одного контейнера**, который обрабатывает конкретный запрос, вместо **сбора метрик** со всех работающих контейнеров.
|
||||
|
||||
В таком случае может быть проще иметь **один контейнер** со **множеством процессов**, с нужным инструментом (таким как экспортёр Prometheus) в этом же контейнере и собирающем метрики со всех внутренних процессов этого контейнера.
|
||||
|
||||
---
|
||||
|
||||
Самое главное - **ни одно** из перечисленных правил не является **высеченным в камне** и Вы не обязаны слепо их повторять. Вы можете использовать эти идеи при **рассмотрении Вашего конкретного случая** и самостоятельно решать, какая из концепции подходит лучше:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
|
||||
## Управление памятью
|
||||
|
||||
При **запуске одного процесса на контейнер** Вы получаете относительно понятный, стабильный и ограниченный объём памяти, потребляемый одним контейнером.
|
||||
|
||||
Вы можете установить аналогичные ограничения по памяти при конфигурировании своей системы управления контейнерами (например, **Kubernetes**). Таким образом система сможет **изменять количество контейнеров** на **доступных ей машинах** приводя в соответствие количество памяти нужной контейнерам с количеством памяти доступной в кластере (наборе доступных машин).
|
||||
|
||||
Если у Вас **простенькое** приложение, вероятно у Вас не будет **необходимости** устанавливать жёсткие ограничения на выделяемую ему память. Но если приложение **использует много памяти** (например, оно использует модели **машинного обучения**), Вам следует проверить, как много памяти ему требуется и отрегулировать **количество контейнеров** запущенных на **каждой машине** (может быть даже добавить машин в кластер).
|
||||
|
||||
Если Вы запускаете **несколько процессов в контейнере**, то должны быть уверены, что эти процессы не **займут памяти больше**, чем доступно для контейнера.
|
||||
|
||||
## Подготовительные шаги при запуске контейнеров
|
||||
|
||||
Есть два основных подхода, которые Вы можете использовать при запуске контейнеров (Docker, Kubernetes и т.п.).
|
||||
|
||||
### Множество контейнеров
|
||||
|
||||
Когда Вы запускаете **множество контейнеров**, в каждом из которых работает **только один процесс** (например, в кластере **Kubernetes**), может возникнуть необходимость иметь **отдельный контейнер**, который осуществит **предварительные шаги перед запуском** остальных контейнеров (например, применяет миграции к базе данных).
|
||||
|
||||
!!! info "Информация"
|
||||
При использовании Kubernetes, это может быть <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Инициализирующий контейнер</a>.
|
||||
|
||||
При отсутствии такой необходимости (допустим, не нужно применять миграции к базе данных, а только проверить, что она готова принимать соединения), Вы можете проводить такую проверку в каждом контейнере перед запуском его основного процесса и запускать все контейнеры **одновременно**.
|
||||
|
||||
### Только один контейнер
|
||||
|
||||
Если у Вас несложное приложение для работы которого достаточно **одного контейнера**, но в котором работает **несколько процессов** (или один процесс), то прохождение предварительных шагов можно осуществить в этом же контейнере до запуска основного процесса. Официальный Docker-образ поддерживает такие действия.
|
||||
|
||||
## Официальный Docker-образ с Gunicorn и Uvicorn
|
||||
|
||||
Я подготовил для Вас Docker-образ, в который включён Gunicorn управляющий процессами (воркерами) Uvicorn, в соответствии с концепциями рассмотренными в предыдущей главе: [Рабочие процессы сервера (воркеры) - Gunicorn совместно с Uvicorn](./server-workers.md){.internal-link target=_blank}.
|
||||
|
||||
Этот образ может быть полезен для ситуаций описанных тут: [Множество процессов внутри контейнера для особых случаев](#special-cases).
|
||||
|
||||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
!!! warning "Предупреждение"
|
||||
Скорее всего у Вас **нет необходимости** в использовании этого образа или подобного ему и лучше создать свой образ с нуля как описано тут: [Создать Docker-образ для FastAPI](#docker-fastapi).
|
||||
|
||||
В этом образе есть **автоматический** механизм подстройки для запуска **необходимого количества процессов** в соответствии с доступным количеством ядер процессора.
|
||||
|
||||
В нём установлены **разумные значения по умолчанию**, но можно изменять и обновлять конфигурацию с помощью **переменных окружения** или конфигурационных файлов.
|
||||
|
||||
Он также поддерживает прохождение <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**Подготовительных шагов при запуске контейнеров**</a> при помощи скрипта.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Для просмотра всех возможных настроек перейдите на страницу этого Docker-образа: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
### Количество процессов в официальном Docker-образе
|
||||
|
||||
**Количество процессов** в этом образе **вычисляется автоматически** и зависит от доступного количества **ядер** центрального процессора.
|
||||
|
||||
Это означает, что он будет пытаться **выжать** из процессора как можно больше **производительности**.
|
||||
|
||||
Но Вы можете изменять и обновлять конфигурацию с помощью **переменных окружения** и т.п.
|
||||
|
||||
Поскольку количество процессов зависит от процессора, на котором работает контейнер, **объём потребляемой памяти** также будет зависеть от этого.
|
||||
|
||||
А значит, если Вашему приложению требуется много оперативной памяти (например, оно использует модели машинного обучения) и Ваш сервер имеет центральный процессор с большим количеством ядер, но **не слишком большим объёмом оперативной памяти**, то может дойти до того, что контейнер попытается занять памяти больше, чем доступно, из-за чего будет падение производительности (или сервер вовсе упадёт). 🚨
|
||||
|
||||
|
||||
### Написание `Dockerfile`
|
||||
|
||||
Итак, теперь мы можем написать `Dockerfile` основанный на этом официальном Docker-образе:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
### Большие приложения
|
||||
|
||||
Если Вы успели ознакомиться с разделом [Приложения содержащие много файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}, состоящие из множества файлов, Ваш Dockerfile может выглядеть так:
|
||||
|
||||
```Dockerfile hl_lines="7"
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
### Как им пользоваться
|
||||
|
||||
Если Вы используете **Kubernetes** (или что-то вроде того), скорее всего Вам **не нужно** использовать официальный Docker-образ (или другой похожий) в качестве основы, так как управление **количеством запущенных контейнеров** должно быть настроено на уровне кластера. В таком случае лучше **создать образ с нуля**, как описано в разделе Создать [Docker-образ для FastAPI](#docker-fastapi).
|
||||
|
||||
Официальный образ может быть полезен в отдельных случаях, описанных выше в разделе [Множество процессов внутри контейнера для особых случаев](#special-cases). Например, если Ваше приложение **достаточно простое**, не требует запуска в кластере и способно уместиться в один контейнер, то его настройки по умолчанию будут работать довольно хорошо. Или же Вы развертываете его с помощью **Docker Compose**, работаете на одном сервере и т. д
|
||||
|
||||
## Развёртывание образа контейнера
|
||||
|
||||
После создания образа контейнера существует несколько способов его развёртывания.
|
||||
|
||||
Например:
|
||||
|
||||
* С использованием **Docker Compose** при развёртывании на одном сервере
|
||||
* С использованием **Kubernetes** в кластере
|
||||
* С использованием режима Docker Swarm в кластере
|
||||
* С использованием других инструментов, таких как Nomad
|
||||
* С использованием облачного сервиса, который будет управлять разворачиванием Вашего контейнера
|
||||
|
||||
## Docker-образ и Poetry
|
||||
|
||||
Если Вы пользуетесь <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> для управления зависимостями Вашего проекта, то можете использовать многоэтапную сборку образа:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9 as requirements-stage
|
||||
|
||||
# (2)
|
||||
WORKDIR /tmp
|
||||
|
||||
# (3)
|
||||
RUN pip install poetry
|
||||
|
||||
# (4)
|
||||
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
||||
|
||||
# (5)
|
||||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
|
||||
# (6)
|
||||
FROM python:3.9
|
||||
|
||||
# (7)
|
||||
WORKDIR /code
|
||||
|
||||
# (8)
|
||||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
|
||||
|
||||
# (9)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (10)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (11)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Это первый этап, которому мы дадим имя `requirements-stage`.
|
||||
|
||||
2. Установите директорию `/tmp` в качестве рабочей директории.
|
||||
|
||||
В ней будет создан файл `requirements.txt`
|
||||
|
||||
3. На этом шаге установите Poetry.
|
||||
|
||||
4. Скопируйте файлы `pyproject.toml` и `poetry.lock` в директорию `/tmp`.
|
||||
|
||||
Поскольку название файла написано как `./poetry.lock*` (с `*` в конце), то ничего не сломается, если такой файл не будет найден.
|
||||
|
||||
5. Создайте файл `requirements.txt`.
|
||||
|
||||
6. Это второй (и последний) этап сборки, который и создаст окончательный образ контейнера.
|
||||
|
||||
7. Установите директорию `/code` в качестве рабочей.
|
||||
|
||||
8. Скопируйте файл `requirements.txt` в директорию `/code`.
|
||||
|
||||
Этот файл находится в образе, созданном на предыдущем этапе, которому мы дали имя requirements-stage, потому при копировании нужно написать `--from-requirements-stage`.
|
||||
|
||||
9. Установите зависимости, указанные в файле `requirements.txt`.
|
||||
|
||||
10. Скопируйте папку `app` в папку `/code`.
|
||||
|
||||
11. Запустите `uvicorn`, указав ему использовать объект `app`, расположенный в `app.main`.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Если ткнёте на кружок с плюсом, то увидите объяснения, что происходит в этой строке.
|
||||
|
||||
**Этапы сборки Docker-образа** являются частью `Dockerfile` и работают как **временные образы контейнеров**. Они нужны только для создания файлов, используемых в дальнейших этапах.
|
||||
|
||||
Первый этап был нужен только для **установки Poetry** и **создания файла `requirements.txt`**, в которым прописаны зависимости Вашего проекта, взятые из файла `pyproject.toml`.
|
||||
|
||||
На **следующем этапе** `pip` будет использовать файл `requirements.txt`.
|
||||
|
||||
В итоговом образе будет содержаться **только последний этап сборки**, предыдущие этапы будут отброшены.
|
||||
|
||||
При использовании Poetry, имеет смысл использовать **многоэтапную сборку Docker-образа**, потому что на самом деле Вам не нужен Poetry и его зависимости в окончательном образе контейнера, Вам **нужен только** сгенерированный файл `requirements.txt` для установки зависимостей Вашего проекта.
|
||||
|
||||
А на последнем этапе, придерживаясь описанных ранее правил, создаётся итоговый образ
|
||||
|
||||
### Использование прокси-сервера завершения TLS и Poetry
|
||||
|
||||
И снова повторюсь, если используете прокси-сервер (балансировщик нагрузки), такой, как Nginx или Traefik, добавьте в команду запуска опцию `--proxy-headers`:
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
## Резюме
|
||||
|
||||
При помощи систем контейнеризации (таких, как **Docker** и **Kubernetes**), становится довольно просто обрабатывать все **концепции развертывания**:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
|
||||
В большинстве случаев Вам, вероятно, не нужно использовать какой-либо базовый образ, **лучше создать образ контейнера с нуля** на основе официального Docker-образа Python.
|
||||
|
||||
Позаботившись о **порядке написания** инструкций в `Dockerfile`, Вы сможете использовать **кэш Docker'а**, **минимизировав время сборки**, максимально повысив свою производительность (и избежать скуки). 😎
|
||||
|
||||
В некоторых особых случаях вы можете использовать официальный образ Docker для FastAPI. 🤓
|
||||
Loading…
Reference in New Issue