Вступление
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 и подсветку синтаксиса, установите эти расширения:
Установив 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. Представьте всю мощь этого метода в более сложной среде разработки и продакшена, и сколько времени, денег и ресурсов экономится.
Читайте также:
- Как определить содержимое ZIP-файла без скачивания
- Разветвление на различные очереди SQS с помощью фильтрации сообщений SNS
- 10 ключевых команд Docker в арсенал фронтенд-разработчиков
Читайте нас в Telegram, VK и Дзен
Перевод статьи Aalok Trivedi: Building an AWS Boto3 Python Environment with Docker Compose