[recovery mode] Еще один способ использования docker-compose
По следам статьи Docker + Laravel = ? хочу рассказать о довольно необычном способе использования утилиты docker-compose.
Для начала, для тех кто не знает, зачем нужен docker-compose. Это утилита, которая позволяет запускать на отдельном хосте набор связанных сервисов, запакованных в docker-контейнеры. Изначальная версия была написана на python и могла быть установлена двумя способами:
- через пакетный менеджер операционной системы (
apt install docker-compose
для Ubuntu иyum install docker-compose.noarch
для Centos) - через менеджер зависимостей python (
pip install docker-compose
)
Проблемой первого способа является то, что обычно в репозиториях операционной системы docker-compose старой версии. Это является проблемой, если необходимо использовать свежую версию демона docker или используются специфические для определенной версии формата файла docker-compose.yaml возможности (матрицу поддерживаемых фич по версиям формата и версиям утилиты docker-compose можно найти на официальном сайте docker).
Сейчас же разработчики docker переписали утилиту на go и предоставляют ее в виде бинарного файла, что позволяет ее устанавливать следующим способом (это текущий рекомендуемый способ):
-
смотрим последнюю версию на https://github.com/docker/compose/releases и скачиваем ее
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
-
устанавливаем права для запуска приложения
$ sudo chmod +x /usr/local/bin/docker-compose
-
дополнительно можно установить автодополнение для командных интепретаторов bash и zsh
-
проверяем установку
$ docker-compose --version docker-compose version 1.22.0, build 1719ceb
Я считаю, что единый бинарник это очень здорово, т.к. нам не нужно тянуть зависимости python. Да, и вообще — может у нас python окружение совсем сломано на целевой машине, которую мы хотим настроить!!!
Пример путаницы в python-окружении
Но есть еще 4-й путь, о котором я и хотел рассказать. Это возможность запускать docker-compose через docker. Действительно, есть уже собранные официальные образы на Docker Hub (https://hub.docker.com/r/docker/compose/). Зачем они могут понадобиться?
- если мы хотим работать с несколькими версиями docker-compose одновременно (хотя обычно достаточно последней стабильной)
- если у нас нет python или мы не хотим его использовать (например, у нас облегченный дистрибутив CoreOS Container Linux)
- использование в CI/CD пайплайнах.
Давайте попробуем!
Как мы делали обычно запуск контейнеров:
$ docker-compose up -d
Через утилиту, запакованную в docker-контейнер:
$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/rootfs/$PWD" -w="/rootfs/$PWD" docker/compose:1.13.0 up -d
Слишком многословно, да? Мозг можно сломать запоминать все эти параметры. Поэтому попробуем облегчить себе жизнь и напишем обертку (wrapper) на языке оболочки. Но сначала разберемся в передаваемых параметрах:
--rm
— удаляет временный контейнер после остановки, т.е. мы не оставляем мусора в системе-v /var/run/docker.sock:/var/run/docker.sock
— без этого docker-compose не сможет соединиться с docker-демоном на хосте-v "$PWD:/rootfs/$PWD" -w="/rootfs/$PWD"
— позволяет пробросить текущий каталог внутрь контейнера, чтобы утилита увидела docker-compose файл
Нам еще не хватает возможности интерполяции значений в docker-compose файле. Это процесс, при котором утилита подставляет переменные окружения в YAML-файл. Например, во фрагменте
version: "2.1"
services:
pg:
image: postgres:9.6
environment:
POSTGRES_USER: ${POSTGRES_DB_USER}
POSTGRES_PASSWORD: ${POSTGRES_DB_PASSWORD}
переменные POSTGRES_DB_USER
и POSTGRES_DB_PASSWORD
будут считаны из окружения. Это позволяет с определенной степенью удобности шаблонизировать docker-compose файлы. Т.е. нам нужно захватить окружение с хост-машины и передать внутрь контейнера.
Решим проблему написанием bash-скрипта.
#!/bin/sh
# создадим временный файл с уникальным именем
TMPFILE=$(mktemp)
# захватим окружение и запишем в файл
env > "${TMPFILE}"
# сохраним версию в отдельную переменную для удобства
VERSION="1.13.0"
# запустим docker-compose
docker run \
--rm \
-e PWD="$PWD" \
--env-file "${TMPFILE}" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/rootfs/$PWD" \
-w="/rootfs/$PWD" \
docker/compose:"${VERSION}" \
"$@"
# удаляем временный файл с захваченным списком переменных окружения
rm "{$TMPFILE}"
Появились дополнительные строчки:
-e PWD="$PWD"
— на всякий случай пробросим текущий каталог--env-file "${TMPFILE}"
— здесь передаются все остальные переменные окружения с хост-машиныdocker/compose:"${VERSION}"
— имя образа, версию возьмем из переменной"$@"
— эта конструкция позволяет использовать скрипт как будто он и есть утилита docker-compose, т.е. «прозрачно» передает свои аргументы в docker-контейнер.
Скрипт можем сохранить, например, в /usr/local/bin/docker-compose
, установить на него флаг eXecute и использовать. Приведенный скрипт не претендует на 100% отсутствие ошибок или недоработок и скорее является иллюстрацией метода.
Сами мы таким способом пользуемся в CI/CD пайплайнах. Это даже до некоторой степени позволяет экономить трафик, т.к. целевой образ docker берется из локального кэша.