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



Книга Контейнеризация в 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-компонент и отлаживать его.




591   0  

Comments

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