Источник: Nuances of Programming
Вступление
Docker — это популярное движение в мире технологий. Неоспоримо его влияние на рост эффективности CI/CD и упрощение сборки, совместного использования и развертывания приложений в любом масштабе и любой среде. Покажем, насколько благодаря ему облегчается наша жизнь.
Что будем создавать?
Boto3, cреду Python с изолированными контейнерами разработки и продакшена.
Понадобится:
- 3 файла Dockerfile для сборки 3 образов Docker.
- 3 контейнера Docker: 1 для разработки и 2 для продакшена.
- 2 виртуальные сети для разработки и продакшена: доступ контейнера разработки к продакшену/взаимодействие с ним исключается.
- Файл Docker Compose, где все это оркестрируется одной командой.
Необходимые условия
- Учетная запись Docker Hub.
- Знание файловых систем и команд Linux.
- Базовые знания о контейнерах, образах Docker, командах CLI.
- Доступ к инструменту командной строки.
- IDE, например VS Code или Cloud9.
Создание учетной записи Docker Hub и установка Docker
Создаем учетную запись
Docker Hub — это платформа с открытым исходным кодом для управления всеми приложениями и ресурсами Docker, в том числе образами. Когда создаются контейнеры и определяется рабочий образ/операционная система, в Docker образы извлекаются из Docker Hub. Берутся надежные официальные образы Centos, Ubuntu, NGINX, Alpine и т. д. или на основе любого из них собирается собственный образ и загружается на Hub для легкого доступа и совместного использования.
Устанавливаем Docker
Простейший способ установить Docker на локальный компьютер с Mac, Windows или Linux — приложение Docker Desktop. Пошагово устанавливаем его и авторизуемся со своим именем пользователя/паролем в Docker Hub.
Docker Desktop — очень удобный инструмент для запуска/остановки Docker и управления всеми контейнерами, образами и томами. В приложении уже установлен Docker Compose.
Чтобы установить Docker на сервер Linux, установите через командную строку Docker engine. Инструкции по установке — в официальной документации Docker.
Расширения VS Code
Чтобы упростить распознавание файлов Dockerfile/Docker Compose и подсветку синтаксиса, установите эти расширения:
- YAML.
Установив Docker на хост-машине, находим зеленый логотип внизу приложения и запускаем команду docker --version:
Docker готов к работе.
Настройка файла
Прежде чем перейти к самому интересному, сделаем корректную настройку. Настройте структуру, как вам привычнее. Вот моя настройка:
Я создал папку boto3_env, которая будет корневым каталогом хоста и клонированным репозиторием из Github. Внутри нее три подкаталога: development, productionA и productionB.
В development добавил Python-скрипты, связанные с AWS Boto3.
Файлы Dockerfile и docker-compose создадим потом.
Файл требований (необязательно)
Упакуем все необходимые зависимости Python в файл requirements.txt. Затем, чтобы установить их в образе Docker, укажем его в Dockerfile.
Чтобы создать этот файл, устанавливаем на компьютере python и pip, с помощью cd переходим в корневой каталог boto_env и запускаем:
pip freeze > requirements.text
Boto3 включен в этот файл.
Создание образов
Образ Docker — это набор корневых файловых систем, зависимостей, двоичных файлов приложения и метаданных о его запуске.
У сотен официальных образов на Docker Hub может не оказаться зависимостей и дополнительных функций для запуска приложения. Docker хорош тем, что к этим образам можно добавить файлы, пакеты и набор инструкций под задачи приложения.
Этот спецнабор инструкций берется из Dockerfile. Создадим три отдельных Dockerfile для образов сред разработки и продакшена.
В файлах Dockerfile имеются команды-слои, ими в Docker указывается, как собирать образ. Рассмотрим типичные команды.
- FROM: базовый образ, например Ubuntu, Alpine, Centos, NGINX и т. д.
- RUN: серия команд оболочки, например установка/обновление пакетов, создание каталогов и т. д., запускаемых во время сборки.
- COPY/ADD: копирование файлов из каталога хоста в образ/контейнер.
- WORKDIR: создание/изменение рабочего каталога образа/контейнера.
- CMD: команда(-ы) времени выполнения, исполняемая(-ые) при запуске контейнера, т. е. запуске веб-сервера/приложения.
Образ среды разработки
Создадим в каталоге хоста development новый файл Dockerfile. Расширения не требуются. Docker «умеет» находить это имя файла, и в Docker выполнится сборка образа.
Начнем с определения базового образа как основы всего. Создавая среду Python, имеет смысл использовать удобный официальный образ Python на Docker Hub.
FROM python:3.9-alpine3.17
Выбираю образ Alpine, сверхлегкого — всего 5 Мб — дистрибутива Linux. Выбирайте версию. Все версии — в разделе tags на странице образа.
Если просто использовать python, в Docker автоматически возьмется последняя версия, что не всегда идеально. Лучше определить конкретную версию, предотвращая таким образом сбои и упрощая отладку.
Для сохранения легковесности образы Docker обычно урезаются, поэтому в них могут отсутствовать ожидаемые базовые пакеты. Командой RUN запустим update и установим нужные нам пакеты curl, bash, git, unzip и iputils:
RUN apk update \
&& apk add bash \
&& apk add curl zip git unzip iputils
Прежде чем выполнять следующую команду, убеждаемся в успешном выполнении предыдущей, разделяя их символом \ и применяя оператор &&. При переходе от одного образа к другому в зависимости от базового дистрибутива Linux эти команды отличаются: yum, apt-get, apk, npm и т. д.
Запустим команду для установки AWS CLI:
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip awscliv2.zip \
&& ./aws/install -i /usr/local/aws-cli -b /usr/local/bin \
&& rm awscliv2.zip
Все эти команды можно поместить в один слой RUN, но у меня это не получилось. В Docker CLI отличная обратная связь с указанием ошибок и слоя, в котором они случились. Так что в разделении команд есть польза. Но будьте осторожны: от добавления слоев увеличиваются размер образа и время сборки.
Теперь создадим/поменяем рабочий каталог образа и скопируем requirements.txt из каталога хоста в образ:
COPY <host_directory_filepath> <image_directory_path>
WORKDIR /home/app
COPY requirements.txt .
Здесь символом . указывается текущий рабочий каталог /home/app.
Создадим другой слой RUN и с помощью pip установим зависимости из requirements.txt. Затем скопируем все файлы из папки development в рабочий каталог образа:
RUN pip install -r requirements.txt
COPY ./development .
Дальше создаем слой CMD для исполнения определенной команды/команд во время выполнения при создании или запуске контейнера. Используется это в основном для выполнения процессов при запуске приложения. Но в нашей среде нет процессов для «выполнения», поэтому нужно включить эту команду для продолжения работы контейнера при запуске. Если этого не сделать, контейнер остановится сразу после своего запуска.
CMD ["tail", "-f", "/dev/null"]
Подробнее о Dockerfile — в официальной документации.
Вот весь Dockerfile:
FROM python:3.9-alpine3.17
#обновляемся, устанавливаем curl, git, zip/unzip
RUN apk update \
&& apk add bash \
&& apk add curl zip git unzip iputils
#устанавливаем aws cli
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip awscliv2.zip \
&& ./aws/install -i /usr/local/aws-cli -b /usr/local/bin \
&& rm awscliv2.zip
#создаем/меняем рабочий каталог
WORKDIR /home/app
#копируем файл требований
COPY requirements.txt .
#устанавливаем зависимости
RUN pip install -r requirements.txt
#копируем все файлы
COPY ./development .
CMD ["tail", "-f", "/dev/null"]
Протестируем: таков ли образ, как ожидалось. В командной строке переходим в корневой каталог boto3_env и собираем образ test-dev командой docker image build:
docker image build -t test-dev -f ./development/Dockerfile .
Dockerfile нет в корневой папке, поэтому путь к файлу указываем тегом -f:
В Docker CLI отличная обратная связь относительно хода выполнения: мы четко знаем, что и когда случается. Каждый слой становится «этапом» в процессе сборки.
Проверим, что все файлы и зависимости скопированы в файловую систему образа корректно. Для этого протестируем образ, создавая из него контейнер и подключаясь к нему:
docker container run -d --name dev test_dev
docker exec -it dev bash
Теперь все как надо: рабочий каталог корректно задан как /home/app, все файлы из папки хоста development перенесены.
В корневом каталоге контейнера видим, что AWS CLI установлен:
Образы среды продакшена
Создадим два образа среды продакшена. Этапы те же, но вместо копирования файлов — клонирование репозитория GitHub прямо в образ.
В каталоге хоста productionA создаем другой Dockerfile:
FROM python:3.9-alpine3.17
#обновляемся, устанавливаем curl, git, zip/unzip
RUN apk update \
&& apk add bash \
&& apk add curl zip git unzip iputils
#устанавливаем aws cli
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip awscliv2.zip \
&& ./aws/install -i /usr/local/aws-cli -b /usr/local/bin \
&& rm awscliv2.zip
WORKDIR /home/app
COPY requirements.txt .
RUN pip install -r requirements.txt \
&& git clone https://github.com/aalokt89/LUIT_python
CMD ["tail", "-f", "/dev/null"]
Файл почти идентичен, только вместо копирования файлов хоста — клонирование репозитория GitHub в рабочий каталог. Делаем все в демонстрационных целях, поэтому выбираем любой репозиторий.
Собираем образ, указав корректный путь к Dockerfile:
docker image build -t test_prod_a -f ./productionA/Dockerfile .
Снова создаем контейнер с этим образом и подключаемся для проверки его работоспособности:
Повторяем этот этап для финального образа среды productionB, но с другим репозиторием GitHub.
Оркестрируем и автоматизируем
С файлами Dockerfile создавать образы легко. Но что если у нас несколько и даже десятки контейнеров? Делать это вручную из командной строки — сущий кошмар.
Но есть же Docker Compose, с ним среды для многоконтейнерных приложений создаются одной командой.
Ею создается docker-compose.yml — практически конфигурационный файл, написанный на YAML. В нем для Docker указывается, какие контейнеры создавать: с какими образами, сетями, томами, переменными среды и т. д. — причем четко, организованно и очень быстро.
Создание файла Compose
В корневом каталоге хоста boto3-env создаем файл docker-compose.yml.
Вот базовая структура файла Docker Compose:
Version: "3"
services:
service_name1:
image: image_name
ports: 3000:80
volumes:
service_name2:
image: image_name
service_name2:
image: image_name
volumes:
volume1:
volume2:
networks:
network1:
network2:
Не все атрибуты обязательны, нужны как минимум service_name и image.
Определим каждую службу-контейнер и зададим каждый атрибут, если нужно. Поскольку мы собираем с файлами Dockerfile новые образы, синтаксис немного отличается.
Service 1: dev
Начнем с контейнера, в котором размещается среда разработки dev:
Version: "3"
services:
dev:
build:
context: .
dockerfile: ./development/Dockerfile
image: boto3_dev
environment:
ENVIRONMENT: dev
volumes:
- type: bind
source: ./development
target: /home/app
networks:
- dev_net
networks:
dev_net:
prod_net:
Разберемся, что здесь происходит:
- dev: название контейнера.
- build: собираем из Dockerfile новый образ, поэтому указываем context, которым задается начальный каталог — а начинаем мы с корневого каталога хоста, поэтому определяем его как ., — и путь к Dockerfile. Если создавать контейнер из уже имеющегося образа, этот атрибут build не нужен.
- image: название нового образа или название и версия имеющегося образа.
- environment: (необязательно) контейнерам присваиваются переменные среды́, присвоим этому переменную dev.
- volumes: (необязательно) определяем bind-монтирование каталогов с хоста для контейнера dev. Хотя при создании образа мы скопировали все из каталога разработки хоста, для обновления содержимого приходилось бы каждый раз пересобирать этот образ. Монтирование каталогов с хоста — отличный способ получать на уровне контейнера постоянно хранимые данные. После выбора источника-хоста source и целевого назначения target каждый раз, когда обновляется файл или каталог на хосте, автоматически обновляются данные в контейнере. Это здорово — и работать в средах dev локально и знать, что изменения отражаются в контейнере.
- networks: (необязательно) в Docker создаются виртуальные сети, к которым подключаются контейнеры. Если ни одна сеть не определена, все контейнеры находятся в стандартной мостовой сети. Не забываем, что доступ контейнера разработки к контейнерам продакшена и взаимодействие с ними исключаются. Поэтому для разработки и продакшена создадим отдельные сети, которые определяются в разделе верхнего уровня networks, а затем указываться на уровне service. При желании настраиваются IP, конкретные подсети и многое другое, но пока достаточно DNS-имени. Остальное в Docker создается за нас.
Service 2 и 3: Prod
То же, кроме монтирования каталогов с хоста, делаем для двух контейнеров продакшена:
prodA:
build:
context: .
dockerfile: ./productionA/Dockerfile
image: boto3_prod_a
environment:
ENVIRONMENT: prod
networks:
- prod_net
prodB:
build:
context: .
dockerfile: ./productionB/Dockerfile
image: boto3_prod_b
environment:
ENVIRONMENT: prod
networks:
- prod_net
Оркестрируем
Вот весь yaml-файл docker-compose:
version: '3'
services:
dev:
build:
context: .
dockerfile: ./development/Dockerfile
image: boto3_dev
environment:
ENVIRONMENT: dev
volumes:
- type: bind
source: ./development
target: /home/app
networks:
- dev_net
prodA:
build:
context: .
dockerfile: ./productionA/Dockerfile
image: boto3_prod_a
environment:
ENVIRONMENT: prod
networks:
- prod_net
prodB:
build:
context: .
dockerfile: ./productionB/Dockerfile
image: boto3_prod_b
environment:
ENVIRONMENT: prod
networks:
- prod_net
networks:
prod_net:
dev_net:
Подробнее о Docker Compose — в официальной документации.
Запускаем docker-compose.yml:
docker compose up -d
… и надеемся, что ошибок не будет.
Судя по последним строчкам, две сети и три контейнера готовы к работе.
Запуская docker container ls, видим все запущенные контейнеры, и проведем заключительный тест сети:
Сначала убедимся, что контейнер разработки Dev не взаимодействует с контейнерами продакшена Prod.
Запустим ping из контейнера Dev командой exec, записывая название целиком — для контейнера назначения — или только первые три цифры/буквы идентификатора:
docker exec -it 552 ping boto3_env-prodA-1
Теперь пропингуем друг с другом контейнеры продакшена Prod и убедимся, что взаимодействие между ними имеется:
docker exec -it a24 ping boto3_env-prodB-1
Поздравляю с успешным развертыванием среды Boto3 в Docker на Python из одного файла Docker Compose. Представьте всю мощь этого метода в более сложной среде разработки и продакшена, и сколько времени, денег и ресурсов экономится.
Читайте также:
Перевод статьи Aalok Trivedi: Building an AWS Boto3 Python Environment with Docker Compose