О Docker Compose
2016-06-27
Docker вовсю обрастает собственной инфраструктурой.
Docker Engine, чтобы запускать из образов контейнеры, а из контейнеров делать новые образы.
Docker Machine, чтобы запускать Docker под Mac и Windows, в виртуалке.
Dockerfile, чтобы автоматизировать сборку образов из множества слоёв.
Docker Hub, он же Docker Registry, чтобы хранить все нужные образы в одном месте и всегда иметь к ним доступ.
Образ — это лишь набор файлов,
упакованных,
готовых для изолированного запуска,
и намерение
запустить этот набор файлов
ради получения какого-то сервиса.
Образ — это абстракция.
Собственно запуск,
т.е. команда docker run
,
требует дополнительных данных,
чтобы получить конкретную реализацию
данной абстрации.
Нужно указать порты,
как они мапятся на порты хоста.
Нужно указать тома (volumes),
как они связаны с файловой системой хоста.
Можно даже указать конкретную команду,
которая будет выполнена в контейнере.
И это всё для одного контейнера.
А если их несколько?
Хочется всё это тоже автоматизировать. И для этого есть штука Docker Compose.
Рекомендуемый способ установки Compose
выглядит странным для Linux.
Предлагается скачать исполняемый файл,
поместить в /usr/local/bin/
и сделать исполняемым.
Ну да,
Go позволяет не мучаться с зависимостями.
В пакетах Xenial тоже есть docker-compose,
правда, версии 1.5.2,
хотя актуальная уже 1.7.1.
В Docker всё меняется быстро,
так что имеет место необходимость
всё ставить помимо пакетов вашего дистрибутива.
Покомпозим.
Возьмем классический пример.
Допустим,
у нас есть какое-то 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. Можно взять кусочек 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.