Вступление

Docker  —  это популярное движение в мире технологий. Неоспоримо его влияние на рост эффективности CI/CD и упрощение сборки, совместного использования и развертывания приложений в любом масштабе и любой среде. Покажем, насколько благодаря ему облегчается наша жизнь.

Что будем создавать?

Boto3, cреду Python с изолированными контейнерами разработки и продакшена.

Понадобится:

  1. 3 файла Dockerfile для сборки 3 образов Docker.
  2. 3 контейнера Docker: 1 для разработки и 2 для продакшена.
  3. 2 виртуальные сети для разработки и продакшена: доступ контейнера разработки к продакшену/взаимодействие с ним исключается.
  4. Файл Docker Compose, где все это оркестрируется одной командой.

Необходимые условия

  1. Учетная запись Docker Hub.
  2. Знание файловых систем и команд Linux.
  3. Базовые знания о контейнерах, образах Docker, командах CLI.
  4. Доступ к инструменту командной строки.
  5. 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. Представьте всю мощь этого метода в более сложной среде разработки и продакшена, и сколько времени, денег и ресурсов экономится.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Aalok Trivedi: Building an AWS Boto3 Python Environment with Docker Compose

Предыдущая статья8 лучших техник программирования в Kotlin
Следующая статья10 лайфхаков JavaScript, которые сделают из вас профессионала