О Docker Compose

2016-06-27

Docker вовсю обрастает собственной инфраструктурой.

Docker Engine, чтобы запускать из образов контейнеры, а из контейнеров делать новые образы.

Docker Machine, чтобы запускать Docker под Mac и Windows, в виртуалке.

Dockerfile, чтобы автоматизировать сборку образов из множества слоёв.

Docker Hub, он же Docker Registry, чтобы хранить все нужные образы в одном месте и всегда иметь к ним доступ.

Docker

Образ — это лишь набор файлов, упакованных, готовых для изолированного запуска, и намерение запустить этот набор файлов ради получения какого-то сервиса. Образ — это абстракция. Собственно запуск, т.е. команда docker run, требует дополнительных данных, чтобы получить конкретную реализацию данной абстрации. Нужно указать порты, как они мапятся на порты хоста. Нужно указать тома (volumes), как они связаны с файловой системой хоста. Можно даже указать конкретную команду, которая будет выполнена в контейнере. И это всё для одного контейнера. А если их несколько?

Хочется всё это тоже автоматизировать. И для этого есть штука Docker Compose.

Рекомендуемый способ установки Compose выглядит странным для Linux. Предлагается скачать исполняемый файл, поместить в /usr/local/bin/ и сделать исполняемым. Ну да, Go позволяет не мучаться с зависимостями. В пакетах Xenial тоже есть docker-compose, правда, версии 1.5.2, хотя актуальная уже 1.7.1. В Docker всё меняется быстро, так что имеет место необходимость всё ставить помимо пакетов вашего дистрибутива.

Docker Compose

Покомпозим. Возьмем классический пример. Допустим, у нас есть какое-то Java веб-приложение, которое мы уже собрали в target/mywebapp.jar. Приложение умеет веб само по себе, но ему нужна база данных, Postgres. Рассуём всё по контейнерам.

С точки зрения приложения важно, чтобы базу данных оно искало на хосте с именем postgres.

А потом делаем Dockerfile примерно такого содержания.

FROM java:openjdk-8-jre
COPY target/mywebapp.jar /opt/mywebapp/mywebapp.jar
EXPOSE 8080
CMD ["/usr/bin/java", "-jar", "/opt/mywebapp/mywebapp.jar"]

Можно уже и собрать этот образ и запустить. Но нам нужен еще Postgres.

Поэтому пишем docker-compose.yml. Файлик в формате YAML. В нём мы опишем наши «сервисы», и из каких образов они должны быть запущены.

В начале файла нужно указать его версию. Docker Compose умудрился родить уже вторую, не вполне совместимую с первой, версию своего конфига.

version: '2'

Начинаем список сервисов.

version: '2'
services:

Сервис первый — наше самое веб-приложение. Так как у нас тут же уже есть Dockerfile, попросим Compose собрать образ из текущего каталога.

services:
  mywebapp:
    build: .

Хотим, чтобы порт 8080 этого приложения был виден как порт 80 на хосте. Порт 80 должен быть свободен для прослушивания.

services:
  mywebapp:
    build: .
    ports:
      - '80:8080'

Ну и указываем, что этот сервис слинкован (по сети) с postgres.

  mywebapp:
    build: .
    ports:
      - '80:8080'
    links:
      - postgres

А postgres — это другой сервис, который запускается из готового образа.

  postgres:
    image: postgres:9.5

Этому образу нужно передать пароль от БД через переменную окружения.

  postgres:
    image: postgres:9.5
    environment:
      POSTGRES_PASSWORD: 'postgres'

А если мы хотим, чтобы данные хранились за пределами контейнера, то нужно еще определить тома.

  postgres:
    image: postgres:9.5
    environment:
      POSTGRES_PASSWORD: 'postgres'
    volumes:
      - ./data:/var/lib/postgresql/data

Теперь можно собрать образ нашего приложения командой docker-compose build. Или же просто выполнить docker-compose up. Важно: Compose не пересобирает образ при каждом вызове up, если у вас что-то поменялось, явно дёргайте build.

Compose запустит два контейнера с говорящими именами вида папкагдележитyml_имясервиса_1.

 % docker ps
CONTAINER ID   IMAGE             COMMAND                 CREATED        STATUS        PORTS                 NAMES
8dc2c02cba05   example_mywebapp  "/usr/bin/java -jar /"  3 minutes ago  Up 2 minutes  0.0.0.0:80->8080/tcp  example_mywebapp_1
a1af628ac4cd   postgres:9.5      "/docker-entrypoint.s"  4 minutes ago  Up 3 minutes  5432/tcp              example_postgres_1

Вообще-то надо бы создать всякую базу да таблицы в Postgres. Можно для этого создать отдельный образ, отпочкованный от официального postgres. А можно подключиться к уже запущенному Postgres. Вот только наружу у этого контейнера порты не торчат. Придётся запустить psql прямо внутри контейнера (последние Docker это позволяют).

% docker exec -it example_postgres_1 psql -U postgres
psql (9.5.3)
Type "help" for help.

postgres=# CREATE DATABASE ...

По умолчанию Docker Compose начинает плеваться в консоль логами всех контейнеров, красиво подсвечивая разными цветами разные сервисы. А при нажатии Ctrl+C грамотно зашатдаунит все сервисы.

Чтобы запустить всё в фоне, нужно добавить ключик -d.

 % docker-compose up -d
Starting example_postgres_1
Starting example_mywebapp_1

Чтобы остановить, теперь нужно сделать docker-compose stop.

HAProxy

Пойдем дальше, добавим лоадбалансер. С помощью HAProxy. Можно взять кусочек Docker Cloud в виде готового балансера.

Добавим ещё один сервис.

  balancer:
    image: dockercloud/haproxy
    links:
      - mywebapp
    environment:
      STATS_AUTH: 'stats:stats'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - '80:80'
      - '1936:1936'

Он собран из образа HAProxy для Docker Cloud. В линках нужно указать тот сервис, который мы собираемся балансировать. Контейнеру нужно передать сокет Docker демона через тома, чтобы он мог извлечь/подкрутить в Docker что ему нужно. Ну и балансер выдаёт 80 порт для приложения и 1936 порт для статистики, пароль на веб мордочку статистики можно передать через переменную окружения.

Немного поправим сервис нашего приложения. Теперь он не выпячивает порты наружу, но утверждает, что внутри у него запущен сервер на порту 8080.

  mywebapp:
    build: .
    expose:
      - '8080'
    links:
      - postgres

Запускаем как обычно docker-compose up. Должно получиться три контейнера, и запросы до нашего приложения должны ходить через балансер.

Теперь — магия.

 % docker-compose scale mywebapp=2
Creating and starting example_mywebapp_2 ... done

И вот у нас уже два наших веб-приложения. И конфигурация HAProxy изменилась, и он успешно перестартовал. И веб-приложения уютненько спрятались за балансером и успешно балансируются.

Конечно, в рамках одного хоста подобная балансировка почти лишена смысла. Но ведь у нас есть ещё Docker Swarm, чтобы распихать наши контейнеры по нескольким физическим хостам. Впрочем, это, как и UCP, — тема для отдельного разговора.

Кстати, штуку, подобную Docker Compose, чтобы запускать, балансировать и скейлить контейнеры из Docker образов, давненько уже имеет Amazon EC2 Container Service. Да, контейнеры Docker можно запускать напрямую в EC2.