Управление контейнерами для разработки при помощи Docker Compose

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

Контейнеризация меняет не только архитектуру сервисов, но и структуру окружений, в которых они создаются. Теперь, когда программное обеспечение можно распространять в контейнерах, разработчики имеют полную свободу выбора приложений и инструментов. В результате - даже сложные системы, вроде серверов с базами данных и аналитической инфраструктурой могут быть запущены в течении секунд. Разработка программного обеспечения становится более простой и эффективной.

В то же время, эти изменения несут новые проблемы. Например, как разработчик, как я могу воссоздать архитектуру и взаимосвязь микросервисов у себя на рабочем компьютере? И как я могу быть уверен в том, что ничего не изменится в ходе непрерывного процесса разработки? И, в конце-концов, где мне взять уверенности в том, что я легко смогу воспроизвести сложное окружение для сборки/тестирования проекта?

Представляем Docker Compose

Ответом на все эти вопросы является Docker Compose: “инструмент для организации и запуска сложных приложений при помощи Docker-а. Вы описываете структуру многоконтейнерного приложения в одном файле, а затем запускаете приложение при помощи одной команды, которая выполняет все необходимые действия” (https://docs.docker.com/compose/)

И все это может быть сделано в масштабе одного компьютера, в этом отношении, концепт docker-compose очень похож на Kubernetes. Для многохостового решения придется использовать более серьезные решение, вроде Apache Mesos или полноценной Google Kubernetes архитектуры. В текущей статье мы рассмотрим возможности Docker Compose, в частности - сфокусируемся на управлении контейнерами в процессе разработки.

Функционал

Основная функция Docker Compose - создание архитектуры микросервисов, а точнее - контейнеров и связей между ними. Но функционал этого инструмента намного шире:

  • Создание образов (при наличии соответствующего Dockerfile)
docker-compose build
  • Масштабирование контейнеров для определенного сервиса
docker-compose scale SERVICE=3
  • Перезапуск остановленных контейнеров
docker-compose up --no-recreate

Весь этот функционал предоставляет утилита docker-compose, имеющая похожий на docker набор команд:

  build              Build or rebuild services
  config             Validate and view the compose file
  create             Create services
  down               Stop and remove containers, networks, images, and volumes
  events             Receive real time events from containers
  help               Get help on a command
  kill               Kill containers
  logs               View output from containers
  pause              Pause services
  port               Print the public port for a port binding
  ps                 List containers
  pull               Pulls service images
  restart            Restart services
  rm                 Remove stopped containers
  run                Run a one-off command
  scale              Set number of containers for a service
  start              Start services
  stop               Stop services
  unpause            Unpause services
  up                 Create and start containers
  version            Show the Docker-Compose version information

Перечисленные команды не только похожи на команды от docker, они и предоставляют аналогичный функционал. Едиственная разница состоит в том, что применяются они не к отдельному контейнеру, а ко всей группе, определенной в конфигурационном файле docker-compose.yml

Можно заметить, что некоторые команды, доступные для docker, отсутствуют для docker-compose. Их использование не имеет смысла в контексте многоконтейнероной системы, которую предоставляет docker-compose. Например:

  • Команды для работы с образами: save, search, images, import, export, tag, history...
  • Команды для взаимодействия с пользователем: attach, exec, run -i, login, wait

Отдельно стоит запомнить команду docker-compose up. Она представляет собой упрощенный вызов docker-compose build && docker-compose run

Рабочий процесс docker-compose

В самом базовом случае, работа с docker-compose состит из трех шагов:

  1. Определение Dockerfile для каждого из сервисов
  2. Определение связей между сервисами при помощи docker-compose.yml
  3. Запус системы при помощи docker-compose up
  4. Я продемонстрирую рабоичй процесс на примере двух сервисов. Сначала мы разберемся с синтаксисом docker-compose.yml и научимся связывать контейнеры между собой. Во втором примере я покажу как управлять конфигурацией сервиса в тестовом окружении и в среде для разработки.

Пример 1: Базовая структура

Синтаксис docker-compose.yml отражает операции, которые нужно произвести Docker-у для запуска сервиса. Для демонстрации этого, я создам контейнер Redis Commander и подключу его к базе данных Redis.

Реализация (исходный код)

Давайте создадим проект со следующей структурой:

example1
├── commander
│ └── Dockerfile
└── docker-compose.yml

Теперь, давайте будем следовать нашему списку. Определим Dockerfile для создания образа для Redis Commander (в исходниках), и займемся docker-compose.yml

backend:
  image: redis:3
  restart: always

frontend:
  build: commander
  links:
    - backend:redis
  ports:
    - 8081:8081
  environment:
    - VAR1=value
  restart: always

Теперь выполним:

docker-compose up -d

После этого, откроем в браузере http://localhost:8081/. Вы должны увидеть интерфейс Redis Commander, который подключен к базе данных

Обратите внимание:

  • Команда docker-compose up -d такой же эффект, как и набор следующих команд для docker:
docker build -t commander commander
docker run -d --name frontend -e VAR1=value -p 8081:8081
   --link backend:redis commander
  • Каждый сервис, указанный в docker-compose.yml должен содержать ссылку на образ или файл для сборки образа, все остальные ключевые слова (links, ports, environment, restart) относятся к docker.
  • Команда docker-compose up -d при необходимости выполняет сборку образов
  • docker-copose ps позволяет получить список запущенных контейнеров
$ docker-compose ps
     Name               State               Ports
---------------------------------------------------
example1_backend_1       Up               6379/tcp
example1_frontend_1 ...  Up               0.0.0.0:8081->8081/tcp
  • docker-compose stop && docker-compose rm -v останавливает и удаляет все контейнеры

Пример 2: Работа с конфигурацией

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

Реализация (исходный код)

Один из вариантов - разместить все зависящие от окружения файлы приложения в отдельных контейнерах и использовать их только при активации соответствующего окружения.

Проект будет иметь следующую структуру:

example2
├── common
│ └── docker-compose.yml
├── development
│ ├── content
│ │ ├── Dockerfile
│ │ └── index.html
│ └── docker-compose.yml
└── testing
│ ├── content
│ │ ├── Dockerfile
│ │ └── index.html
└─└── docker-compose.yml

И опять-таки, давайте придерживаться оговоренного в начале рабочего процесса.

Сначала давайте создадим конфигурационные файлы для Docker ({development,testing}/content/Dockerfile), которые будут содержать специфические для каждого окружения настройки. В данном случае, для иллюстрации механизма, все отличие будет заключаться в файле index.html: он будет содержать уникальную для каждого окружения строку.

FROM tianon/true

VOLUME ["/usr/share/nginx/html/"]
ADD index.html /usr/share/nginx/html/

Теперь давайте создадим docker-compose.yml файлы. common/docker-compose.yml будет содержать общие для всех окружений сервисы. В нашем случае это просто nginx сервер с пробросом портов:

web:
  image: nginx
  ports:
    - 8082:80

В дальнейшем мы будем наследовать конфигурацию из этого файла.

Для каждого окружения необходимо создать свой docker-compose.yml (development/docker-compose.yml и testing/docker-compose.yml) которые включают конфигурацию из созданного ранее основного файла.

web:
  extends:
    file: ../common/docker-compose.yml
    service: web
  volumes_from:
    - content

content:
  build: content

Выполним следующие команды:

cd development
docker-compose up -d

Откроем в браузере http://localhost:8082/. Если все предыдущие шаги были выполнены корректно, то на открытой странице должна быть строка "You are in development!". Чтобы переключиться в окружение для тестирования, выполните следующие команды:

docker-compose stop
cd ../testing
docker-compose up -d

Обновим страницу в браузере, строка должна измениться на "You are in testing!".

Обратите внимание:

  • Сервис web наследуется из common/docker-compose.yml. Его изначальное определение расширяется диретивой volumes_from, при помощи которой подключается раздел из контейнера content. Таким образом приложение получает доступ к спецефичной для окружения конфигурации.
  • После запуска, контейнер выполняет true команду и завершает свою работу, но его данные все еще доступна для web-контейнера, коорый остается активным.

Оригинал статьи: Orchestrate Containers for Development with Docker Compose