[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 и предоставляют ее в виде бинарного файла, что позволяет ее устанавливать следующим способом (это текущий рекомендуемый способ):


  1. смотрим последнюю версию на 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
    

  2. устанавливаем права для запуска приложения

    $ sudo chmod +x /usr/local/bin/docker-compose
    

  3. дополнительно можно установить автодополнение для командных интепретаторов bash и zsh


  4. проверяем установку

    $ docker-compose --version
    docker-compose version 1.22.0, build 1719ceb
    

Я считаю, что единый бинарник это очень здорово, т.к. нам не нужно тянуть зависимости python. Да, и вообще — может у нас python окружение совсем сломано на целевой машине, которую мы хотим настроить!!!

1tbjhmezttp2dgxb_wtiariskfm.png
Пример путаницы в 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 берется из локального кэша.

© Habrahabr.ru