- PVSM.RU - https://www.pvsm.ru -
Эта статья — ознакомительное руководство по сборке Docker-образов приложений с помощью нашей Open Source-утилиты dapp [1] (подробнее о ней читайте в анонсе [2]). На примере двух простых приложений (с одним образом) рассмотрим, как могут быть задействованы некоторые из основных возможностей dapp и какой результат они дают.
Сразу оговорюсь, что dapp не задумывался как утилита, упрощающая локальную разработку приложения. Однако в последнее время у нас оформилось видение, как мы могли бы облегчить жизнь обычного разработчика, и об этом обязательно выйдет другая статья. А сейчас — про упрощение процесса CI/CD.
Какую часть занимает сборка в процессе CI/CD? Давайте ещё раз взглянем на схему из доклада [3] коллеги distol [4]:
Сборка превращает исходный код программ из Git-репозитория (стадия git) в Docker-образы, готовые к запуску в различных окружениях (стадия build). Встаёт вопрос, что тут упрощать, если уже есть Dockerfile
и docker build
? Действительно, есть масса статей про сборку Docker-образов для приложений на различных языках. Но в основном это статьи про сборку и запуск приложения из среза исходных текстов.
С какими проблемами придётся столкнуться, когда приложений будет несколько десятков? Когда окажется, что у процесса сборки есть много общих частей и нужно копировать кусочки Dockerfile
? Когда захочется ускорить сборку (ведь в приложении поменялось всего два файла)? Эти вопросы заранее не предскажешь, пока не встретишься с ними на практике. Наши ответы на такие вопросы и опыт их решения воплощён в утилите dapp.
Разберём на «живом» примере, что даёт dapp. В качестве первого испытуемого возьмём простое PHP-приложение symfony-demo [5].
Для самых нетерпеливых в нашем GitHub [6] уже добавлен Dappfile
и сделан Vagrantfile
, поэтому достаточно запустить vagrant up
, после чего можно собирать приложение без установки Docker и dapp в свою систему.
Для тех, кто не спешит и хотел бы «окунуться с головой», нужно установить dapp (см. документацию [7]) и склонировать себе репозиторий приложения:
$ git clone https://github.com/symfony/symfony-demo.git
$ cd symfony-demo
$ vi Dappfile
Описывающий сборку Dappfile
— файл с синтаксисом DSL на Ruby (в чём-то похоже на Vagrantfile
, но планируем перейти на YAML с сохранением поддержки старого формата). Его содержимое:
dimg 'symfony-demo-app' do
docker.from 'ubuntu:16.04'
git do
add '/' do
to '/demo'
end
end
shell do
before_install do
run 'apt-get update',
'apt-get install -y curl php7.0',
# пользователь phpapp
'groupadd -g 242 phpapp',
'useradd -m -d /home/phpapp -g 242 -u 242 phpapp'
end
install do
run 'apt-get install -y php7.0-sqlite3 php7.0-xml php7.0-zip',
# установка composer
'curl -LsS https://getcomposer.org/download/1.4.1/composer.phar -o /usr/local/bin/composer',
'chmod a+x /usr/local/bin/composer'
end
before_setup do
# исходным текстам нужно сменить права и запустить composer install
run 'chown phpapp:phpapp -R /demo && cd /demo',
"su -c 'composer install' phpapp"
end
setup do
# используем текущую дату как версию приложения
run 'echo `date` > /demo/version.txt',
'chown phpapp:phpapp /demo/version.txt'
end
end
# порт совпадает с портом, указанным в start.sh
docker.expose 8000
end
Также нужно добавить start.sh
и сделать его исполняемым (chmod +x start.sh
):
#!/bin/sh
cd /demo
su -c 'php bin/console server:run 0.0.0.0:8000' phpapp
Собрать образ приложения можно командой:
$ dapp dimg build
А запустить так:
$ dapp dimg run -d -p 8000:8000 -- /demo/start.sh
Сборка выдаёт длинный лог со всеми совершаемыми действиями. Листинг очень большой, поэтому его часть выложена отдельно на GitHub [8]. Если теперь запустить команду dapp dimg build
ещё раз, то эти действия выполняться повторно не будут, т.к. результат их работы закэширован. Лог повторного запуска можно увидеть здесь [9]. Его фрагмент:
Видны строки с именем стадии и результатом [USING CACHE]
— это означает, что dapp не стал выполнять описанные действия, создавая новый слой образа, а использовал уже существующий.
Теперь притворимся разработчиком и внесём правки — например, изменим текст ссылки в шапке страницы в шаблоне app/Resources/views/base.html.twig
. Сделаем коммит и попробуем собрать приложение. Видно [10], что наложился только git patch
, т.е. новый образ был создан на основе закэшированных слоёв, к которым добавились изменения в файлах проекта:
...
Setup [USING CACHE]
signature: dimgstage-symfony-demo:3705edf770dd88ac714a7001fd24f395c87b2110005025eff48019d5973846ce
date: 2017-08-16 04:16:46 +0000
difference: 0.0 MB
Git artifacts dependencies [USING CACHE]
signature: dimgstage-symfony-demo:f3f1c3e1ce5f0f5b880b1ec693b194d7e6a841a4166b29982d11b4e4c4cbe360
date: 2017-08-16 04:16:49 +0000
difference: 0.0 MB
Git artifacts: apply patches (after setup) [USING CACHE]
signature: dimgstage-symfony-demo:15e56865dd8b2a1cc55d5381a4e6f2cbcdc3a718509de29b15df02e8279b42c3
date: 2017-08-16 04:16:52 +0000
difference: 0.0 MB
Git artifacts: latest patch ... [OK] 3.22 sec
signature: dimgstage-symfony-demo:a9c21d0e36218563c8fd34b51969ed2f3b6662ca7775acae49488c5ebbbf25e1
Docker instructions ... [OK] 3.16 sec
signature: dimgstage-symfony-demo:2eae4537c4210aaf4a153c7b8d3036343abf98b4ac4a3b99a2eb1967bea61378
instructions:
EXPOSE 8000
Это ускорение сборки хорошо работает для файлов, которым не нужна какая-то особая обработка. Что же делать, если поменяется, например, composer.json
и нужно при сборке вызывать composer install
?
На этот случай dapp поддерживает 4 пользовательских стадии сборки (подробно они описаны в документации [11]). Первая стадия — before_install
, во время которой недоступны исходники. Обычно это установка редко меняющихся пакетов и настройки ОС. Дальнейшие 3 стадии: install
, before_setup
и setup
— уже имеют доступ к исходным текстам. Как же управлять наложением git patch
?
Для того, чтобы указать, что изменения в файлах должны приводить к перезапуску сборки с определённой стадии, нужно указать директиву stage_dependencies
в директиве git
. В случае нашего приложения изменение файлов composer.json
или composer.lock
должно приводить к пересборке начиная со стадии before_setup
, на которой запускается composer install
. Поэтому директива git
будет выглядеть так:
git do
add '/' do
to '/demo'
stage_dependencies.before_setup 'composer.json', 'composer.lock'
end
end
Лог сборки здесь [12]. Видно, что git patсh
применился после стадии install
и образ был пересобран со стадии before_setup
:
...
Install [USING CACHE]
signature: dimgstage-symfony-demo:a112d1abf364602c3595990c3f043d88e041a2a6f3cbcf13b6fc77a9fb3fd190
date: 2017-08-16 04:14:19 +0000
difference: 5.0 MB
Git artifacts dependencies ... [OK] 2.75 sec
signature: dimgstage-symfony-demo:9f0600ab6fb99356110c50454fc31e5fdc6ac3028e4ba8f200e789d140514bf9
Git artifacts: apply patches (after install) ... [OK] 2.18 sec
signature: dimgstage-symfony-demo:f139188f9b0662d8177d41689b57c700e2276d997139673c3384731f6851d72e
Before setup [BUILDING]
...
Такие связи между изменениями файлов в репозитории и стадиями позволяют уменьшить общее время сборки. В большинстве случаев зависимости приложения добавляются или изменяются не очень часто. Например, разработчик реализует новую фичу, из-за которой потребовалось добавить зависимость в composer.json
. Первым коммитом, в котором будет новая зависимость, образ пересоберётся со стадии before_setup
— это займёт какое-то время. Но последующие коммиты, в которых composer.json
уже не будет изменяться, выполняются быстро. Именно это позволяет в нашей конфигурации CI/CD автоматически запускать сборку для каждого коммита в ветках разработчиков и DevOps-инженеров (см. «GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн [13]»).
Не так давно [14] в Dockerfile
появилась возможность собирать части итогового образа с помощью других образов (multi-stage builds). Сделано это для того, чтобы не тащить в итоговый образ golang, webpack, gcc или другие инструменты, которые нужны только для сборки и совершенно не нужны во время работы приложения. Dapp поддерживает такую сборку изначально, с помощью секций artifact
.
Возьмём для следующего примера веб-приложение на golang [15]. Как и с первым приложением, готовый для экспериментов репозиторий с Dappfile
и Vagrantfile
можно склонировать из нашего GitHub [16].
По шагам:
$ git clone https://github.com/revel/examples revel-examples
$ cd revel-examples/booking
$ vi Dappfile
Dappfile
будет такой:
dimg_group do
artifact do
docker.from 'golang:1.8'
git do
add '/' do
to '/go/src/github.com/revel/examples'
end
end
shell.before_install do
run 'apt-get update',
'apt-get install -y sqlite3 libsqlite3-dev tree'
end
shell.install do
run 'go get -v github.com/revel/revel',
'go get -v github.com/revel/cmd/revel'
end
shell.build_artifact do
run '(go get -v github.com/revel/examples/booking/... ; true)'
run 'revel build github.com/revel/examples/booking /app'
end
export '/app' do
to '/app'
after 'install'
end
end
dimg 'booking-app' do
docker.from 'ubuntu:16.04'
end
end
Собрать и запустить можно всё теми же командами:
$ dapp dimg build
$ dapp dimg run -p 9000:9000 --rm -d -- /app/run.sh
Чтобы не потеряться среди вывода docker images
, протегируем итоговый образ:
$ dapp dimg tag booking-app:v1.0
Теперь можно уведить, что размер итогового образа не зависит от размера образа с инструментами сборки golang:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
booking-app v1.0 57d564633ecb 4 minutes ago 136MB
ubuntu 16.04 ccc7a11d65b1 7 days ago 120MB
golang 1.8 6ce094895555 3 weeks ago 699MB
Теперь подробнее опишу Dappfile
с применением artifact
(особенности сборки приложения revel сознательно опускаю — если интересует, можно обсудить в комментариях). В общем случае структура будет такая:
dimg_group
artifact do
docker.from
git
команды стадий
export
end
dimg 'dimg_name' do
docker.from
команды стадий
end
end
artifact
указывает, что часть приложения нужно собрать в отдельном образе, а директивой git
внутри artifact
можно указать, куда положить исходные тексты.export
указывает, что нужно из отдельного образа скопировать в итоговый образ после выполнения команд стадии build_artifact
.export
также можно указать, на какой стадии итоговому образу нужны результаты работы артефакта. В нашем случае указано after 'install'
, т.е. после команд стадии install
в итоговый образ будет скопирована директория /app
из образа артефакта.
По первому впечатлению всё это аналогично Dockerfile
с применением multi-stage, но преимуществом артефакта является то, что к нему применяются другие возможности dapp — кэширование и зависимости стадий от изменений в Git (эти зависимости можно описать в секции git
).
Описанные возможности dapp: разбиение на стадии, зависимость стадий от изменений в файлах в Git-репозитории, использование artifact-образов — могут упростить и ускорить сборку практически любого приложения как в процессе CI/CD, так и для локальной разработки.
Но это только начало. В Dappfile
можно описать сразу несколько образов и dapp dimg build
их соберёт, а dapp dimg push --tag
проставит тег и за'push'ит кэш-образы и итоговый образ в Registry. Эти возможности будут лучше проиллюстрированы в следующих статьях — в связке с описанием недавно появившейся в dapp поддержки деплоя в Kubernetes.
Автор: diafour
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/continuous-integration/262622
Ссылки в тексте:
[1] dapp: https://github.com/flant/dapp
[2] анонсе: https://habrahabr.ru/company/flant/blog/333682/
[3] доклада: https://habrahabr.ru/company/flant/blog/331188/
[4] distol: https://habrahabr.ru/users/distol/
[5] symfony-demo: https://github.com/symfony/symfony-demo
[6] нашем GitHub: https://github.com/flant/symfony-demo/blob/dapp_build/README-Vagrant.md
[7] см. документацию: http://flant.github.io/dapp/installation.html
[8] GitHub: https://gist.github.com/diafour/7987f4685de606e50ca2b50e1085009c#file-symfony-demo-build-first-log
[9] здесь: https://gist.github.com/diafour/7987f4685de606e50ca2b50e1085009c#file-symfony-demo-build-second
[10] Видно: https://gist.github.com/diafour/a0d97b8fad3055b867aa4416bad0f026
[11] документации: http://flant.github.io/dapp/stages_for_build.html
[12] здесь: https://gist.github.com/diafour/5d8fe1622d6c9f43e522508897eac0e4
[13] GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн: https://habrahabr.ru/company/flant/blog/332712/
[14] Не так давно: https://habrahabr.ru/company/flant/blog/332160/
[15] веб-приложение на golang: https://revel.github.io/examples/booking.html
[16] нашего GitHub: https://github.com/flant/examples/blob/dapp_build/README-Vagrant.md
[17] Источник: https://habrahabr.ru/post/336212/
Нажмите здесь для печати.