Мы уже обсуждали сборку и развёртывание приложений Elixir(перев: с помощью exrm): как осуществлять миграции поверх релиза или как работать с переменными среды. Пришло время открыть для себя ещё один инструмент, который поможет развёртывать Elixir приложения.
Практика развёртывания Elixir приложений и дальнейшее отслеживание их работы на нодах с помощью Exrm позволяет нам чувствовать себя гораздо увереннее в вопросах управления релизами в production. Однако возникает следующий вопрос: как управлять самим процессом развёртывания? Конечно, мы можем воспользоваться Capistrano, особенно если в мир Elixir мы пришли из Rails. Но посмотрим на цитату из Edeliver README:
edeliver основан на доставке и предоставляет bash-скрипт для сборки и развёртывания Elixir и Erlang приложений, а так же позволяет совершать "горячее" обновление кода.
Пытаться организовать весь процесс развёртывания вручную — это жёсткая головная боль с кучей повторяющегося кода. А вот использование Edeliver для развёртывания оказалось очень простым с первой же попытки! В конце концов, весь процесс развёртывания уместился в один меленький bash-скрипт:
#!/bin/bash -ex
BRANCH=${1:-master};
mix edeliver build release --branch=BRANCH --verbose
mix edeliver deploy release to production --verbose
mix edeliver start production --verbose
mix edeliver migrate production up --verbose
Скорее всего Вам придётся подкрутить этот скрипт под собственные нужды. Мы используем его только для развёртывания в production, но Вы так же можете использовать его и для staging развёртываний. Описание того, как всё это работает — под катом.
Как это работает
Как мы видели в README, Edeliver работает в основном с помощью bash-скриптов. Команды Mix исполняются с помощью Elixir, но фактически они запускают bash-скрипты. Некоторые инструкции терминала выполняются локально: они создают новые инструкции, которые будут выполнятся на удалённых серверах с через RPC.
Давайте углубимся в некоторые аспекты работы библиотеки.
Окружение
Edeliver представляет собой отличную возможность запускать и доставлять релизы в различные окружения. Концепция окружений проста: build, stage и production. Тут всё понятно, разве что некоторые нюансы есть у окружения build.
Для того, чтобы релиз благополучно запускался на рабочем сервере, он должен быть собран на машине с такой же архитектурой. Всё дело в том, что Edeliver использует Exrm для сборки релизов. Exrm будет использовать свои NIFы (это функции, написанные на C для Erlang), которые могут различаться в зависимости от архитектуры машины, поэтому, к примеру, релиз собранный на OSX может не работать на Linux. Вы можете почитать подробнее об этом вот в это пичальке Phoenix, где люди обсуждают проблемы кросс-компиляции и всякие другие выкидоны Exrm.
Короче говоря, чтобы использовать build окружение на нашей машине разработчика, у неё должна быть такая же архитектура как и на staging и production машинах. Иначе работать не будет.
Для конфигурации наших окружений, нужно создать папку .deliver
в нашем проекте и добавить конфигурационный файл. Посмотрим, что авторы Edeliver советуют нам в него записать:
#!/usr/bin/env bash
APP="your-erlang-app" # name of your release
BUILD_HOST="build-system.acme.org" # host where to build the release
BUILD_USER="build" # local user at build host
BUILD_AT="/tmp/erlang/my-app/builds" # build directory on build host
STAGING_HOSTS="test1.acme.org test2.acme.org" # staging / test hosts separated by space
STAGING_USER="test" # local user at staging hosts
TEST_AT="/test/my-erlang-app" # deploy directory on staging hosts. default is DELIVER_TO
PRODUCTION_HOSTS="deploy1.acme.org deploy2.acme.org" # deploy / production hosts separated by space
PRODUCTION_USER="production" # local user at deploy hosts
DELIVER_TO="/opt/my-erlang-app" # deploy directory on production hosts
В общем всё просто и прозрачно, надо только удостовериться в наличии ssh доступа ко всем перечисленным сервакам. Дополнительной фичей является то, что как уже горилось ранее, вполне реально запулить релизы на несколько серверов.
Как мне добавить дополнительные задания для моих процессов развёртывания?
Edeliver делает по стандарту только общие штуки для всех Elixir или Erlang приложений. Но если, к примеру, мы используем Phoenix, нам нужно запустить ещё парочку команд до того, как мы сгенерируем релиз. Самые важные — это
$ branch build --production
и
$ mix phoenix.digest
чтобы статика работала в наших релизах.
Для этого определим хук в нашем .deliver/config
файле:
pre_erlang_clean_compile() {
status "Preparing assets with: brunch build and phoenix.digest"
__sync_remote "
# runs the commands on the build host
[ -f ~/.profile ] && source ~/.profile # load profile (optional)
# fail if any command fails (recommended)
set -e
# enter the build directory on the build host (required)
cd '$BUILD_AT'
mkdir -p priv/static # required by the phoenix.digest task
# installing npm dependencies
npm install
# building brunch
brunch build --production
# run your custom task
APP='$APP' MIX_ENV='$TARGET_MIX_ENV' $MIX_CMD phoenix.digest $SILENCE
"
}
Код вверху был нагло выдран из документации к Edeliver, которая объясняет как работать с хуками, и почему-то сразу заработал.
Что там насчёт моих переменных окружения?
Мы уже рассказывали как правильно себя вести с переменными окружения так, чтобы не экспортировать их в build окружение вот тут, и это всё ещё работает! Впрочем, ещё кое-что надо иметь в виду.
Чтобы можно было менять окружения, необходимо добавить RELX_REPLACE_OS_VARS=true
перед нашей командой запуска. Но это невозможно в Edeliver, потому что первая команда выполняется локально:
$ mix edeliver start production
Поэтому возможное решение — экспортировать RELX_REPLACE_OS_VARS
в ваше production окружение.
Перед стартом
Похоже, что Edeliver — классная штука для управления релизами и вообще процессом развёртывания. Мне она показалась очень простой в использовании. В этой статье я никак не вникал во внутренности её работы, так что прочитайте README и всякие доки — там очень хорошо всё расписано.
Я деплою так, а ты как? Пиши в комментариях!
Комментарии (22)
yamatoko
10.08.2016 08:57Где нужно хранить загруженные пользователем картинки и файлы, чтобы они не были задеплоины вместе с приложением? То есть, пользовательские картинки или файлы должные быть отдельно, не обязательно даже совсем в другой папке. Просто я хочу, чтобы они не были статически включены в само приложение, а были отдельно, как они и должны быть.
dzolotarev
10.08.2016 10:56+1Эх, вот не пошел у меня Elixir после Erlang'а. Какой-то он сложный, запутанный и инопланетный слишком. Как небо и земля прям.
thousandsofthem
Вечная головная боль в elixir/phoenix — управление конфигами. Дело в том, что при компиляции конфиг приложения превращается в статический файл с намертво прибитыми гвоздями значениями. Т е любое изменение требует пересборки, причем с предварительно подсунутыми правильными данными. Зачем такое извращение было добавлено в язык — понятия не имею.
Если кто-то знает как это можно адекватно обрабатывать — очень хочу услышать.
Virviil
Что насчёт
System.get_env
в конфиге?thousandsofthem
Использую. Работает, но фиксирует значение во время компиляции. Требуется рекомпиляция для изменения
azhi
Мы используем вот этот скрипт для перегенерации «статического» файла из эликсировского сырца в момент деплоя релиза. Вот статья автора, где он описывает как это работает.
Там же упомянута более тяжелая альтернатива — conform, но ее мы не пробовали.
nwalker
Конфиги в Elixir — небольшой сахар для application env в Erlang, в котором хранятся статические значения. Так что, ничего удивительного в этом нет.
Ну и вообще, вы что, хотели, чтобы в application env лежали thunk-и, которые вычислялись про каждом вызове Application.get_env?
Нет, из коробки ничего подобного нет и не будет. Хотя бы потому, что это невозможно нормально подружить с hot code reload как он реализован в BEAM.
Ну и вообще, моя обычная рекомендация — не пользуйтесь релизами, это непростая в использовании и очень специфичная фича, она категорически не для всех.
thousandsofthem
При каждом вызове — не надо. При каждом старте приложения — надо
Например
FOO=bar mix phoenix.server
nwalker
А вот это уже удивительно.
Вообще, конечно, разработчики Elixir меня удивляют все больше и больше.
thousandsofthem
Чуть выше написали причину (наследие erlang) — такое поведение только в конфиге, в любом другом месте кода все ок. Вообще конечно косяки такого рода в языке есть но на удивление мало, очень много всего из коробки сделано очень чисто и продуманно
nwalker
Нет, вот это интересное поведение — не наследие Erlang. Application env, конечно, статичен, но не настолько. В том комменте я говорил о другом.
Вообще говоря, ничего подобного происходить не должно, но я пока не могу найти, где там ошибка.
nwalker
А вот про продуманность я бы очень сильно поспорил, в языке, stdlib и plug есть достаточно просто абсурдных вещей, про которые авторы считают, что все ок. Я когда-нибудь напишу пост о них, когда не лень будет.
nwalker
Все там нормально работает. Там есть пара странностей в духе «если в конфиге nil, Application.get_env :app, :var_name, 123123 вернет nil, а не 123123», но никакого кеширования конфигов между запусками нет и не может быть by design.
Другой дело, если у вас значения из ENV через конфиг используются в макросах, тогда да, нужно будет перекомпилировать проект. Где в Phoenix макросы я точно не знаю, но в этом случае точно ничего удивительного нет. В этом случае рекомендую делать touch config/config.exs перед каждым запуском.
Вам лично — «двойка» за умение формулировать проблемы.
Virviil
А если не пользоваться релизами, тогда как? Каждый раз git pull?
erlyvideo
эта херня растет из той дури, что можно держать аппликейшн конфиг в файлах типа sys.config
При этом все скачут как дурни вокруг hot code reload, а на вопрос: как будете перечитывать конфиг, скакуны бодро отвечают: рестартить приложение с новым конфигом, который уже лежит на сервере под рутовыми правами и недоступный для редактирования.
Это всё растет из того, что на эликсире-эрланге редко пишут отчуждаемые приложения, которые пишутся одними, а деплоятся другими людьми.