- PVSM.RU - https://www.pvsm.ru -

Практика с dapp. Часть 1: Сборка простых приложений

Эта статья — ознакомительное руководство по сборке Docker-образов приложений с помощью нашей Open Source-утилиты dapp [1] (подробнее о ней читайте в анонсе [2]). На примере двух простых приложений (с одним образом) рассмотрим, как могут быть задействованы некоторые из основных возможностей dapp и какой результат они дают.

Практика с dapp. Часть 1: Сборка простых приложений - 1

Сразу оговорюсь, что dapp не задумывался как утилита, упрощающая локальную разработку приложения. Однако в последнее время у нас оформилось видение, как мы могли бы облегчить жизнь обычного разработчика, и об этом обязательно выйдет другая статья. А сейчас — про упрощение процесса CI/CD.

Сборка приложения

Какую часть занимает сборка в процессе CI/CD? Давайте ещё раз взглянем на схему из доклада [3] коллеги distol [4]:

image

Сборка превращает исходный код программ из Git-репозитория (стадия git) в Docker-образы, готовые к запуску в различных окружениях (стадия build). Встаёт вопрос, что тут упрощать, если уже есть Dockerfile и docker build? Действительно, есть масса статей про сборку Docker-образов для приложений на различных языках. Но в основном это статьи про сборку и запуск приложения из среза исходных текстов.

С какими проблемами придётся столкнуться, когда приложений будет несколько десятков? Когда окажется, что у процесса сборки есть много общих частей и нужно копировать кусочки Dockerfile? Когда захочется ускорить сборку (ведь в приложении поменялось всего два файла)? Эти вопросы заранее не предскажешь, пока не встретишься с ними на практике. Наши ответы на такие вопросы и опыт их решения воплощён в утилите dapp.

Сборка с dapp и Dappfile

Разберём на «живом» примере, что даёт 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

Особенности: кэш, git patch, стадии сборки

Сборка выдаёт длинный лог со всеми совершаемыми действиями. Листинг очень большой, поэтому его часть выложена отдельно на GitHub [8]. Если теперь запустить команду dapp dimg build ещё раз, то эти действия выполняться повторно не будут, т.к. результат их работы закэширован. Лог повторного запуска можно увидеть здесь [9]. Его фрагмент:

Практика с dapp. Часть 1: Сборка простых приложений - 3

Видны строки с именем стадии и результатом [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]»).

Особенности: multi-stage или artifact-образ?

Не так давно [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/