Контейнеризация в Python. Часть 2


Часть 1, Часть 2

Это вторая статья серии, посвящённой контейнеризации разработки в Python. В Части 1 мы уже разобрали лучшие практики контейнеризации Python-сервиса. Здесь мы изучим настройку и привязывание других компонентов. Я покажу хороший способ организации файлов и данных проекта и расскажу, как управлять его конфигурацией при помощи Docker Compose. Наконец, я покажу лучшие подходы к написанию Compose-файлов для ускорения разработки с применением контейнеров.

Управление конфигурацией с Docker Compose

Возьмём в качестве примера приложение и разделим для него функциональность на три уровня, следуя архитектуре микросервисов — распространённой архитектуре многосервисных приложений. Наше приложение состоит из:

  • UI, работающего на сервисе nginx;
  • Логики  —  центрального в статьях компонента на Python.
  • Данных в БД MySQL. 

Мы разделяем приложение на уровни: так можно с лёгкостью изменять или добавлять новые уровни без необходимости перерабатывать весь проект.

Изолирование файла и конфигурации каждого сервиса  —  хороший вариант структуризации файлов проекта. Это легко сделать, создав внутри проекта отдельную директорию для каждого сервиса. Очень полезно иметь чистое представление компонентов, чтобы без проблем поместить все сервисы в контейнеры. Кроме того, контейнеризация помогает избежать случайного изменения файлов не того сервиса. Наше приложение содержит следующие директории:

Project ├─── web └─── app └─── db

В первой части мы увидели контейнеризацию компонента на Python. То же самое относится и к другим компонентам проекта, но мы пропустим эти детали, поскольку можем обратиться к шаблонам, реализующим необходимую нам структуру. Пример  —  nginx-flask-mysql из репозитория awesome-compose. Ниже вы видите обновлённую структуру проекта с файлом Dockerfile. Предположим, что у веб- и db-компонентов похожие структуры: 

Project ├─── web ├─── app │ ├─── Dockerfile │ ├─── requirements.txt │ └─── src │ └─── server.py └─── db

Теперь мы могли бы вручную запустить контейнеры для всех контейнеризованных компонентов нашего проекта. Однако для их взаимодействия нам нужно самостоятельно организовать сеть и прикрепить к ней контейнеры, что довольно сложно и может занять много драгоценного времени. 

Docker Compose и предлагает очень простой способ координации контейнеров, их запуска и остановки сервисов в локальной IDE. Для этого всего лишь нужно написать Compose-файл с конфигурацией сервисов проекта. После этого проект запускается одной командой.

Compose-файл

Посмотрим на файл Compose и разберёмся, как с его помощью можно управлять сервисами проекта. 

Ниже  —  пример для нашего случая. В нём мы определяем список сервисов. В разделе db указывается базовый образ непосредственно, поскольку для него не применяется никакой конкретной конфигурации, а веб-сервиса и сервиса приложения будут содержать образ, собранный на основе их Dockerfile. Мы можем настроить поля build  —  сборка или image  —  образ в зависимости от того, откуда будем получать образ сервиса. В поле build указывается путь к Dockerfile:

docker-compose.yaml version: "3.7" services: db: image: mysql:8.0.19 command: '--default-authentication-plugin=mysql_native_password' restart: always environment: - MYSQL_DATABASE=example - MYSQL_ROOT_PASSWORD=password app: build: app restart: always web: build: web restart: always ports: - 80:80

Для инициализации БД мы передаем переменные среды с именем этой БД и паролем, а для веб-сервиса  —  отображаем порт контейнера в localhost, чтобы получить возможность обращаться к веб-интерфейсу проекта. 

Теперь разберём развёртывание проекта при помощи Docker Compose. Нам осталось поместить docker-compose.yaml в корневую директорию и назначить команду для развёртывания:

Project ├─── docker-compose.yaml ├─── web ├─── app └─── db

Docker Compose позаботится об извлечении образа MySQL из Docker Hub и запуске контейнера db, а для веб-сервиса и сервиса приложения он соберёт образы локально, запустив контейнеры из них. Он также берёт на себя создание предустановленной сети по умолчанию и помещение в неё контейнеров, предоставляя возможность коммуникации между сервисами.

Да, всё это запускается одной командой:

$ docker-compose up -d Creating network "project_default" with the default driver Pulling db (mysql:8.0.19)… … Status: Downloaded newer image for mysql:8.0.19 Building app Step 1/6 : FROM python:3.8 ---> 7f5b6ccd03e9 Step 2/6 : WORKDIR /code ---> Using cache ---> c347603a917d Step 3/6 : COPY requirements.txt . ---> fa9a504e43ac Step 4/6 : RUN pip install -r requirements.txt ---> Running in f0e93a88adb1 Collecting Flask==1.1.1 … Successfully tagged project_app:latest WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build. Building web Step 1/3 : FROM nginx:1.13-alpine 1.13-alpine: Pulling from library/nginx … Status: Downloaded newer image for nginx:1.13-alpine ---> ebe2c7c61055 Step 2/3 : COPY nginx.conf /etc/nginx/nginx.conf ---> a3b2a7c8853c Step 3/3 : COPY index.html /usr/share/nginx/html/index.html ---> 9a0713a65fd6 Successfully built 9a0713a65fd6 Successfully tagged project_web:latest Creating project_web_1 … done Creating project_db_1 … done Creating project_app_1 … done

Проверка выполнения контейнеров:

$ docker-compose ps Name Command State Ports ------------------------------------------------------------------------- project_app_1 /bin/sh -c python server.py Up project_db_1 docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp project_web_1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp

Для остановки и удаления всех контейнеров выполните:

$ docker-compose down Stopping project_db_1 ... done Stopping project_web_1 ... done Stopping project_app_1 ... done Removing project_db_1 ... done Removing project_web_1 ... done Removing project_app_1 ... done Removing network project-default

Для повторной сборки можно сначала запустить саму сборку, а затем команду Up для обновления состояния контейнеров:

$ docker-compose build $ docker-compose up -d

Как видите, docker-compose позволяет с лёгкостью управлять жизненным циклом проекта.

Лучшие подходы в Docker Compose

Давайте проанализируем файл и посмотрим, как можно его оптимизировать, применяя такие подходы:

Разделение сети

Когда у нас есть несколько контейнеров, нам нужно контролировать их соединение. Необходимо помнить, что поскольку мы не устанавливаем сеть в Compose-файле, все контейнеры в итоге оказываются в одной предустановленной сети.

Однако, если нужно, чтобы обращаться к БД мог только Python-сервис, нам нужно описать в Compose-файле отдельную сеть для каждой пары компонентов. Тогда веб-компонент к БД обращаться не сможет.

Тома в Docker

При каждой остановке контейнеров мы удаляем их и теряем данные, хранившиеся в предыдущих сеансах. Чтобы избежать потери даннных и сохранить данные БД между разными контейнерами, мы можем использовать именованные тома  —  docker volumes. Для этого нужно просто определить такой том в Compose-файле и указать точку его монтирования в сервисе db:

version: "3.7" services: db: image: mysql:8.0.19 command: '--default-authentication-plugin=mysql_native_password' restart: always volumes: - db-data:/var/lib/mysql networks: - backend-network environment: - MYSQL_DATABASE=example - MYSQL_ROOT_PASSWORD=password app: build: app restart: always networks: - backend-network - frontend-network web: build: web restart: always ports: - 80:80 networks: - frontend-network volumes: db-data: networks: backend-network: frontend-network:

В docker-compose при желании можно удалять именованные тома.

Docker Secrets

Как видно из Сompose-файла, мы храним пароль дляdb в виде простого текста. Такой подход небезопасен. Можно использовать секреты Docker, которые хранят пароль, а при необходимости безопасно предоставлять его сервисам. Ниже показано, как можно определить секреты и организовать на них ссылку в сервисах. Пароль в нашем случае хранится локально в файле project/db/password.txt и монтируется в контейнеры так: /run/secrets/<secret-name>.

version: "3.7" services: db: image: mysql:8.0.19 command: '--default-authentication-plugin=mysql_native_password' restart: always secrets: - db-password volumes: - db-data:/var/lib/mysql networks: - backend-network environment: - MYSQL_DATABASE=example - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password app: build: app restart: always secrets: - db-password networks: - backend-network - frontend-network web: build: web restart: always ports: - 80:80 networks: - frontend-network volumes: db-data: secrets: db-password: file: db/password.txt networks: backend-network: frontend-network:

Теперь у нас есть грамотно определённый Compose-файл, созданный с применением лучших подходов. Образец приложения, в котором отрабатывается всё сказанное, вы найдёте здесь.

Что дальше?

В этой статье мы поговорили о настройке контейнерного многосервисного проекта, где Python-сервис связан с другими сервисами, а также научились развёртывать сервисы локально при помощи Docker Compose. В заключительной части серии мы научимся обновлять помещённый в контейнер Python-компонент и отлаживать его.


Перевод статьи ANCA IORDACHE: Containerized Python Development — Part 2


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.