Pull to refresh
Флант
DevOps-as-a-Service, Kubernetes, обслуживание 24×7

Сборка проектов с dapp. Часть 2: JavaScript (frontend)

Reading time 6 min
Views 4.8K


Этот материал продолжает цикл о сборке Docker-образов для приложений на различных языках программирования с помощью утилиты dapp. Предыдущая статья была о сборке приложений на Java — теперь же поговорим о приложениях на JavaScript. Для начала это будет frontend-приложение, а в следующей части планируется рассказать о сборке backend'а и запуске всего в Kubernetes.

Обновлено 13 августа 2019 г.: в настоящее время проект dapp переименован в werf, его код переписан на Go, а документация значительно улучшена.

В качестве иллюстрации будут использованы приложения nodejs-pool и poolui. Да-да, подготовим к запуску в Kubernetes свой майнинг-пул с блокчейном и выплатами!



Пул для майнинга — это приложение для координации программ-майнеров. Пул раздаёт майнерам задания и собирает ответы от них. Если общими усилиями удалось найти блок, который сеть признаёт валидным, то вознаграждение за этот блок делится между участниками пула по той или иной стратегии. Nodejs-pool — это серверная часть пула, с которой общаются программы-майнеры. Poolui — frontend-приложение, с помощью которого участники взаимодействуют с пулом: регистрируются, видят общую и свою статистику по блокам, майнерам, выплатам.

Сборка приложения poolui «как есть»


Первое описание сборки может быть повторением готового Dockerfile (как в статье про Java), но если такого нет, то достаточно начать с запуска приложения в контейнере по рекомендациям разработчиков. В нашем случае в README написано, что для сборки и запуска достаточно выполнить команду npm start — вот с этого и начнём.

Возьмём официальный образ node, в котором уже есть npm, — например, версию 9.11-alpine. В образ нужно добавить исходный код приложения, а контейнер будет запущен с командой npm start с помощью dapp dimg run. Исходники обычно добавляются в какую-нибудь директорию — например, /app, — поэтому нужна директива docker.WORKDIR, чтобы команда запустилась в нужной директории, а не в корне образа. Получился такой простейший dappfile.yaml:

dimg: poolui
from: node:9.11-alpine
git:
  - add: /
    to: /app
docker:
  WORKDIR: /app

Команда сборки:

dapp dimg build

(Подробнее о некоторых удобствах при работе с этой командой сборки см. в статье от коллеги.)

Команда запуска приложения:

dapp dimg run poolui -p 8080:8080 -ti --rm -- npm start

После такого старта приложение в браузере будет выглядеть примерно так:



Улучшенный dappfile.yaml


По виду страницы можно понять, что одной команды npm start не хватает для полноценного запуска приложения в виде контейнера.

Подобный запуск «как есть» работает не для всех проектов, но его можно использовать, чтобы «пронаблюдать» за стартом незнакомого приложения в контейнере. Наличие в проекте Dockerfile упрощает работу по созданию dappfile.yaml, т.к. сразу видно все команды для создания образа.

Чтобы создать dappfile.yaml для проекта без Dockerfile, нужно определить из описания сборки приложения, какие команды запускать. Инженеры нашей компании проводят такую работу либо на основе своих знаний утилит в разных языках (npm, gulp, maven, composer, и т.д.), либо в некоторых случаях совместно с разработчиками заказчика изменяют файлы описания (package.json, gulpfile.js, pom.xml, composer.json), чтобы сборка приложения описывала все нужные зависимости.

Обычно всё начинается со стадии beforeInstall, где описывается установка системных пакетов, например, если каких-то инструментов не хватает в выбранном базовом образе. Затем добавляется стадия install, где уже доступны исходные тексты приложения и можно запускать инструменты сборки. После успешной сборки образа с двумя стадиями можно пойти дальше и какие-то команды выделить в другие стадии (beforeSetup, setup), а также описать наборы директорий и файлов, изменения в которых приведут к пересборке.

Подобный запуск «как есть» для проектов, в составе которых нет Dockerfile, обычно не применим в production-окружении. Для выката приложения в production нужно провести работу по определению команд для каждой стадии сборки образа и по определению файлов-зависимостей для этих стадий.

Рассматриваемый проект имеет три файла описания:

  • package.json — описание зависимостей и действий для npm;
  • bower.json — описание зависимостей приложения;
  • gulpfile.js — описание задач.

Из package.json видно, что для сборки понадобятся gulp и bower — эти утилиты можно установить на стадии beforeInstall, т.к. их версия не будет часто изменяться. На этой же стадии добавится git, т.к. он нужен для скачивания зависимостей. В этом же файле видно команды, которых не хватило: npm install, bower install. Эти команды уйдут на стадию install, где происходит скачивание зависимостей. Отличие небольшое — для простоты bower install выполняется с ключом --allow-root. Пересборка стадии install зависит от изменений в файлах описаниях package.json и bower.json.

npm start запускает команду gulp, которая выполнит задачу по умолчанию из gulpfile.js. Эта задача последовательно запускает три других: build, connect, watch. Т.е. задачи сборки и запуска объединены. Это удобно для быстрого старта, но для сборки образа придётся на стадии setup явно вызвать gulp build и установить пересборку setup в зависимости от изменений в директории app и файла gulpfile.js.

Для запуска приложения теперь используется не npm start, а gulp connect. Запускать gulp watch не нужно, т.к. это команда для упрощения разработки.

Итоговый dappfile.yaml выглядит так:

dimg: poolui
from: node:9.11-alpine
git:
  - add: /
    to: /app
    stageDependencies:
      install:
        - package.json
        - bower.json
      beforeSetup:
        - app
        - gulpfile.js
shell:
  beforeInstall:
  - apk update
  - apk add git
  - npm install --global bower
  - npm install --global gulp
  install:
  - cd /app
  - npm install
  - bower install --allow-root
  beforeSetup:
  - cd /app
  - gulp build
docker:
  WORKDIR: "/app"
  CMD: ["gulp", "connect"]

Лог сборки приложения можно увидеть в этом asciicast'е:



Для проверки, если запустить сборку повторно, то всё соберётся из кэша:



Теперь можно запустить приложение командой dapp dimg run poolui -p 8080:8080 -ti --rm:



В выводе запуска заметна такая строка: Server started http://localhost:8080.
Это значит, что подключиться к серверу из браузера на хост-машине не получится. Нужно поправить gulpfile.js, чтобы сервер слушал на адресе 0.0.0.0. Изменения нужно закоммитить и запустить dapp dimg build, чтобы собрать новый образ. Т.к. изменится gulpfile.js, то должна пересобраться только стадия beforeSetup. Результат можно увидеть в этом asciicast'е:



Повторный запуск с командой dapp dimg run poolui -p 8080:8080 -ti --rm:



Если подключиться к приложению браузером (зайти на http://localhost:8080), то можно увидеть подобную страницу:



Готово! Фронтенд пула запущен и выглядит как нужно. Однако gulp connect — это модуль, предназначенный для разработки, а для production хотелось бы запаковать приложение «правильно». Таким вариантом может стать образ с nginx, куда будет скопировано содержимое директории /app/build. В dapp есть артефакты и промежуточные или инструментальные образы — эта возможность и будет использована далее.

Упаковка в образ с nginx


Приложение собирается в директорию /app/build и достаточно превратить описанный dimg в artifact. Стоит отметить, что artifact не может содержать директивы Docker, поэтому их нужно удалить, а в остальном отличий от dimg нет. Теперь нужно добавить второй dimg на основе, например, nginx:stable-ansible. Готовый образ nginx настроен на отдачу статических файлов из /usr/share/nginx/html — в эту директорию нужно импортировать /app/build. Готовый dappfile.yml выглядит так:

artifact: poolui-builder
from: node:9.11-alpine
git:
  - add: /
    to: /app
    stageDependencies:
      install:
        - package.json
        - bower.json
      beforeSetup:
        - app
        - gulpfile.js
shell:
  beforeInstall:
  - apk update
  - apk add git
  - npm install --global bower
  - npm install --global gulp
  install:
  - cd /app
  - npm install
  - bower install --allow-root
  beforeSetup:
  - cd /app
  - gulp build
---
dimg: poolui
from: nginx:stable-ansible
import:
  - artifact: poolui-builder
    add: /app/build
    to: /usr/share/nginx/html
    after: install

Команда сборки не меняется:

dapp dimg build

А вот команду запуска надо поменять — nginx слушает на 80-м порту.

dapp dimg run poolui -p 8080:80 -ti --rm

При обращении к localhost:8080 результат такой же, как и с запуском gulp connect: страница показывается полностью.



В логе сборки можно подсмотреть id стадий и оценить размер промежуточного и итогового образов.

# Итоговый образ
dimgstage-poolui  0e27eaebff1f23312d18f83370ee30000a97139311fa141b86aa3f34b15e544d   6a1a7fc1aa98        8 minutes ago       25.1MB
# Промежуточный образ poolui-builder
dimgstage-poolui  f000dbe70d25a5abb998997e6c0530db52f228fee4cdd0390046657bdb3a6d32   8f300d169e5e        17 minutes ago      241MB

Теперь frontend-часть точно готова! Далее нужно собрать и запустить backend, что несколько сложнее и будет описано в следующей статье.

Заключение


Приложение довольно простое, но даже на таком примере можно показать, что контейнеризация приложений, изначально написанных без оглядки на Docker, требует некоего вдумчивого анализа. Зато в результате может получиться dappfile.yaml, который будет пересобирать только нужные стадии, ускоряя инкрементальные сборки.

Специфика контейнеризации приложений на JavaScript именно в этом — в определении того, что нужно запускать на этапе сборки, т.к. инструментов много и у каждого заказчика свой сформированный разработчиками набор.

P.S.


Читайте также в нашем блоге:

Tags:
Hubs:
+17
Comments 2
Comments Comments 2

Articles

Information

Website
flant.ru
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Тимур Тукаев