В этой статье я хотел бы рассказать как можно создавать сценарии сборки имиджей для Docker контейнеров с помощью системы многоцелевых сценариев Sparrow*. (*) Примечание — для понимания некоторых технических нюансов данной статьи желательно иметь хотя бы поверхностное знакомство с системой многоцелевых сценариев Sparrow, краткое введение в которую ( помимо страниц документации ) можно найти в моей предыдущей статье на habrahabr.ru. Вначале немного проблематики. Имеем задачу описать сборку Docker имиджа с помощью Dockerfile. Если сценарий сборки нетривиален и содержит множество инструкций, нужно как-то выкручиваться. Помимо того, что Dockerfile не может содержать более 120 слоев ( насколько я правильно понял из документации по Docker ), иметь дело с развесистым Dockerfile не очень приятно. Что можно с этим поделать? Очевидные варианты — вынести код сборки в отдельные Bash скрипты в рабочую директорию и делать установку и настройку системы прямо из них. Другой способ — "прикручивать" сбоку какой-нибудь configuration management tool типа chef или ansible. Оставляю на откуп читателю оценку данных альтернатив ( IMHO у них есть свои плюсы и минусы ) и предлагаю третий способ — использование Sparrow. Прежде чем приводить детали реализации хочется сказать: Вариант со Sparrow чем-то то очень похож на использование Bash скриптов, с той лишь разницей, что вся логика установки выносится в Sparrow плагин, со своим исходным кодом, хранящимся в отдельном месте ( git репозитарий или центральное хранилище ). Таким образом, базовая настройка системы в контексте Docker описана в Dockerfile, а более тонкая и сложная внутри Sparrow плагина. Итак, покажем все на конкретной системе. Требуется собрать имидж с дистрибутивом CentOS и установить приложение, написанное на Ruby версии равной 2.3. После этого запустить основной скрипт приложения из под выделенного пользователя. Исходный код приложения скачивается с некого архивного сервера. Пример взят из реальной жизни, хотя некоторые детали намеренно опущены, дабы не перегружать статью материалом. Прежде чем писать код плагина, создадим Dockerfile. За базовый имидж я взял tutum/centos по причине его легковесности. По этой же причине приходится доставлять часть пакетов, но в целом это не является какой-то проблемой. Немного комментариев по Dockerfile . nano и git-core необходимы для разработки Sparrow плагина ( смотрите далее ) — мы будем редактировать код сценариев и коммитить изменения в удаленный git репозитарий. gcc, make потребуются для сборки RubyGems и CPAN пакетов. Первые потребуются при установки Ruby через rvm, последние для установки Sparrow. Установка многочисленных perl-* пакетов через yum необходима для оптимизации процесса сборки по скорости, можно было бы не делать этого, т.к. следующая инструкция cpanm -q Sparrow установила бы требуемые зависимости сама, но установка зависимостей через cpanm в общем случае требует гораздо больше времени, чем установка "нативных" для CentOS rpm-ок. Итак, попробуем создать имидж: Отлично, имидж с базовой инфраструктурой у нас есть, можно запустить Docker контейнер и начать разработку плагина прямо на нем. Вышеуказанными командами мы создали шаблон проекта для нашего плагина и закоммитили все в удаленный git репозитарий. URL репозитария мы запомним, он понадобится нам далее, когда мы будем проводить полноценную сборку имиджа командой docker build Теперь сделаем небольшой отступление. Вспомним нашу задачу. Попробуем для удобства разбить ее на независимые части: Для логически отдельных задач в Sparrow предусмотрен механизм модулей, воспользуемся им. Но прежде всего создадим основную историю, в которой будем делегировать выполнение задач разным модулям. Итак, все на том же запущенном Docker контейнере: Немного комментариев по коду. Мы имеем три второстепенных истории ( модули ) и одну основную, заданную хук файлом (hook.bash), для того, что показать как все это работает создадим заглушки для сценариев в модулях. Да, и дефолтное значение для входного параметра action должно быть задано в suite.ini файле. Создаем заглушки сценариев: А также проверочные файлы: Теперь запустим все через strun — консольный скрипт для выполнения Sparrow сценариев: Отлично. Мы видим, что все сценарии отработали успешно, это и будет скелетом нашего будущего плагина. Осталось только заимплиментить заглушки наших модулей. Будем исходить из того что имя пользователя является настраиваемым, дефолтное значение определяем в файле suite.ini : Теперь реализация сценария: И запуск ( обратите внимание, что здесь мы воспользовались возможностью запуска отдельного сценария с помощью параметра action ): Мы видим, что сценарий отработал и пользователь создался. Обратите внимание, что большинство Bash команд внутри сценария завершаются идиоматической конструкцией cmd || exit 1, strun проверяет код выполнения сценария и если он неуспешен, то соответствующий тест проваливается, например так — попробуем создать пользователя с невалидным для системы именем: Сделаю здесь еще небольшое отступление. Зададимся вопросом зачем нам нужно проверочные файлы, если по-сути проверки кода завершения сценария должно быть достаточно. Резонный вопрос. Мы можем думать о проверочных правилах фреймворка Sparrow как о некой альтернативном способе контроля или верификации выполнения наших скриптов. В идеологии Sparrow любой выполняемый сценарий является историей в том смысле, что это некий скрипт, который запускается и чаще всего "сообщает" что-то о своей работе — образно говоря "оставляя след в истории". Это след — стандартный выходной поток stdout, содержимое которого можно провалидировать. Почему это может быть полезно: Не всегда успешный код завершения означает, что все идет хорошо В качестве конкретного примера можно привести сценарий установки Ruby через rvm, который идет следующим по списку в нашем плане. Вот как будет выглядеть сценарий установки: А это — проверочный файл: Теперь запустим данный сценарий: Хочется обратить внимание, что для верификации версии установленного Ruby мы воспользовались проверочным правилом ввиде регулярного выражения: Конечно rvm позволяет устанавливать требуемую версию явно, просто хотелось здесь привести пример когда проверки, определенные в проверочных файлах позволяют добавить дополнительную верификацию работы сценария с минимальными усилиями. Теперь можно перейти к сценарию установки приложения. Напомню. Нам будет необходимо: На этом все. Конечно в реальном приложении, нам бы пришлось бы еще запустить какой-нибудь сервис или совершить еще какие-нибудь дополнительные операции, но для демонстрации работы плагина этого должно быть достаточно. Опять таки же для простоты примера пусть у нас есть Ruby приложение состоящее из: Пакуем все архив и выкладываем все на архивный сервер га локальный nginx, теперь дистрибутив будет доступен по URL: Обновим код сценария. Небольшие комментарии по сценарию: Установку делаем из-под пользователя заданного в конфигурации плагина suite.ini. Для этого нам нужен пакет sudo Последняя команда запускает скрипт приложения hello.rb  Итак, запустим сценарий: Как мы видим приложение действительно установилось и скрипт hello.rb запускается. Добавим еще один "параноидальный" ассерт в проверочный файл для демонстрации возможностей системы проверок Sparrow: И запустим сценарий заново.  В выводе получим: На этом создание плагина завершено. Закоммитим изменения и сделаем "push" в git репозитарий: Мы вышли из докер контейнера он нам больше не нужен, можно его удалить: Осталось чуть-чуть изменить Dockerfile, вспоминаем о том, что нам понадобится ссылка на удаленный git репозитарий, где мы разместили код нашего Sparrow плагина, окончательный вариант будет таким: Теперь мы можем осуществить полный цикл сборки имиджа, "проиграв" все заново: В итоге мы получим Docker имидж с требуемой системой. Применение системы многоцелевых сценариев Sparrow может быть эффективным средством построения Docker имиджей, т.к. позволяет строить сложные конфигурации, оставляя основной Dockerfile простым и лаконичным, а так же упрощая процесс разработки самих сценариев конфигурирования требуемой системы. Спасибо за внимание. Как обычно жду вопросов и конструктивной критики! :) Алексей devops, developer, open-source enthusiast