Деплой Phoenix-приложений через Edeliver. Часть 2
В предыдущей части мы составили план деплоя приложений, написанных на Elixir с использованием Phoenix. Теперь пришло время подробно раскрыть каждый из шагов и ответить на основные вопросы.
Шаг 1. Создание нового Phoenix-приложения
Чтобы создать новое приложение, следуйте этой инструкции или просто запустите соответствующую команду в терминале:
mix phoenix.new edelivered_app
Теперь Phoenix-приложение должно появиться в директории edelivered_app
. Следуйте указаниям команды mix phoenix.new
, и убедитесь, что приложение запускается на вашей системе.
Укажите реквизиты базы данных в файле config/prod.secret.exs
. Можно смело использовать ту же базу данных, что и для dev-окружения, т.к. этот файл не добавляется в систему контроля версий, и на сервере будет его другая копия. Чтобы можно было создать релиз для продакшна, локальное приложение должно корректно работать в режиме «prod».
Убедитесь, что команды корректно выполняются:
MIX_ENV=prod mix ecto.create # create prod database if not the same as dev database.
MIX_ENV=prod mix phoenix.server # run phoenix server in prod mode.
Инициализируйте пустой Git-репозиторий и сделайте начальный коммит.
git init
git commit -am "Initial commit. First!"
Шаг 2. Настройка distillery для создания релизов
Как гласит документация distillery:
Релиз — это пакет, состоящий из файлов с расширением .beam, включающих зависимости приложения, sys.config, vm.args, сценарий загрузки, а также различные утилиты и файлы метаданных, предназначенные для управления релизом после его установки. Кроме этого, релиз может также включать копию ERTS.
Фактически релиз представляет собой архив (*.tar.gz
) со скомпилированным приложением и всеми его зависимостями, включая саму среду выполнения.
Созданный релиз помещается на сервер, и затем происходит его развёртывание с помощью Edeliver (подробнее здесь).
Добавьте в mix.exs
следующий код:
defp deps do
[
#...,
{:distillery, "~> 1.0"}
]
end
и запустите
mix deps.get
Создайте файл конфигурации релиза:
mix release.init
Откройте созданный файл rel/config.exs
. По умолчанию релиз будет сконфигурирован для двух окружений: dev
и prod
. В конфигурацию можно добавлять и другие релизы и окружения, однако пока остановимся на конфигурации по умолчанию, но с одной оговоркой.
Создавать и запускать distillery-релиз в режиме dev — бессмысленно, так как это всё равно не сработает (https://github.com/bitwalker/distillery/issues/25).
Поэтому нужно поставить окружение prod
в настройках по умолчанию. Замените default_environment
в rel/config.exs
на «prod».
use Mix.Releases.Config,
# This sets the default release built by `mix release`
default_release: :default,
# This sets the default environment used by `mix release`
default_environment: :prod # <------ SET THIS TO :prod
Обязательно загляните на страницу терминологии distillery. Это поможет избежать суеты при возникновении ошибок и поиска в интернете решений по их исправлению.
Измените точку входа Endpoint в файле config/prod.exs
на следующую:
config :edelivered_app, EdeliveredApp.Endpoint,
http: [port: {:system, "PORT"}],
url: [host: "localhost", port: {:system, "PORT"}],
cache_static_manifest: "priv/static/manifest.json",
server: true,
root: ".",
version: Mix.Project.config[:version]
Важно, чтобы порты, указанные в опциях http
и url
, совпадали. Подробнее об этих опциях читайте здесь.
Теперь всё готово для создания первого релиза.
MIX_ENV=prod mix release
Даже если установить prod-окружение по умолчанию, distillery всё равно будет работать в dev-окружении Mix. Поэтому измените MIX_ENV
на «prod» и только после этого выполните команду mix release
.
Вот вы и создали свой релиз для продакшна!
Шаг 3. Локальное тестирование релиза distillery
По умолчанию релизы располагаются в директории _build/<env>/rel/<app-name>
, и можно запускать релиз прямо оттуда. Однако, для демонстрации переносимости релизов на Elixir, извлечём архив с релизом в отдельную папку и проведём запуск из неё.
- Создайте директорию для своего приложения:
mkdir ~/Downloads/edelivered_app
- Поместите копию релиза в эту директорию:
cp _build/prod/rel/edelivered_app/releases/0.0.1/edelivered_app.tar.gz ~/Downloads/edelivered_app/
- Откройте директорию назначения:
cd ~/Downloads/edelivered_app/
- Извлеките файлы из архива
tar -zxvf edelivered_app.tar.gz
Никогда не мог запомнить эти переключателиtar
, так как не особо часто извлекаю файлы типа*.tar.gz
. Специально для этого я создал документ в Evernote.Подсказка: создайте простенький сценарий и назовите егоuntar
. - Запустите приложение:
PORT=8080 bin/edelivered_app foreground
. Убедитесь, что обязательная переменная среды PORT соответствует необходимому порту.
Воспользуемся командой «foreground», чтобы для начала запустить приложение в обычном режиме и выявить существующие на данном этапе ошибки.
Если всё прошло нормально, то можно запустить приложение в фоновом режиме (в качестве демона):
PORT=8080 bin/edelivered_app start
Используйте этот же скрипт для остановки и перезапуска приложения, а также для подключения к нему. Наиболее полезные команды:
bin/edelivered_app
выводит список доступных команд.bin/edelivered_app stop
останавливает приложение.bin/edelivered_app ping
дает обратную связь «pong», если в приложении нет ошибок.bin/edelivered_app remote_console
подключается к запущенному релизу через консоль IEx. В отличие от «console» и «attach», «remote_console» не завершает запущенный релиз при выходе из консоли.
После развёртывания можно использовать те же команды на продакшн сервере/серверах.
Сделайте коммит внесённых изменений: git commit -am "Add distillery dependency"
Более подробную информацию о создании релизов можно найти в инструкции по установке на странице distillery в GitHub или вот в этом чудесном гайде. Очень рекомендуется изучить документацию distillery полностью, так как в ней содержится достаточно много информации о релизах, а также рассказывается о способах устранения ошибок, к коим мы ещё вернёмся в конце данной статьи.
Шаг 4. Подготовка сервера в облаке
Нам понадобится сервер Ubuntu 16.04 с открытым 22 SSH-портом, IP адресом и root-доступом.
Для создания сервера можно пользоваться абсолютно любым провайдером облачных вычислений. Из российских хостингов отлично себя зарекомендовал Vscale. Ценовая политика у него вполне приемлема. Всё, что нам потребуется, — это минимум 1 Гб оперативной памяти. Можно, конечно, сэкономить и рассмотреть вариант с 512 Мб, но тогда придётся увеличить область подкачки, чтобы на таком сервере можно было создавать релизы, что, к слову сказать, будет происходить крайне медленно. Посмотреть на примере, как увеличить область подкачки, можно здесь. Надеюсь, выделенное красным предупреждение в самом начале поста сможет посеять в ваши мысли зерно сомнения.
Ещё один вариант — установить Ubuntu 16.04 LTS на виртуальную машину. VirtualBox вполне подойдёт. Настройте сеть виртуальной машины: выберите NAT (Network Address Translation), чтобы стало возможным подключение к ней по SSH и HTTP/HTTPS.
Дабы упростить себе жизнь, забудем про Chef, Ansible и другие DevOps инструменты.
Выполните следующее:
- Создайте нового пользователя «app» в домашней директории:
adduser app
. - Поместите свой публичный RSA-ключ в
/home/app/.ssh/authorized_keys
для подключения к серверу по SSH без необходимости ввода пароля. Создайте директорию.ssh
, если её не существует. - Установите следующие права доступа:
chmod 700 /home/app/.ssh
chmod 644 /home/app/.ssh/authorized_keys
chown -R app:app /home/app/.ssh
- Для удобства добавьте пользователя «app» в группу
sudo
: внесите правки в файл/etc/group
и поместите app в конец строки «sudo». - Подключитесь к серверу по SSH в качестве пользователя
app
; пароль запрашиваться не должен. - Следуйте инструкции по установке Ubuntu для Elixir. Установите Erlang и Elixir.
- Следуйте указаниям по установке NodeJs и NPM в Ubuntu.
- Установите git:
sudo apt-get install git
- Установите сервер базы данных Postgres:
sudo apt-get install postgresql postgresql-contrib
. - Настройте роль Postgres для данного пользователя:
- Переключитесь на linux-пользователя «postgres»:
sudo su - postgres
- Запустите консольный клиент Postgres:
psql
CREATE ROLE app WITH superuser;
ALTER ROLE app WITH login;
ALTER ROLE app WITH createdb;
ALTER USER app WITH PASSWORD 'coolpass';
Надеюсь, вы не будете использовать этот пароль для своих реальных серверов :)
- Закройте консольный клиент Postres: Ctrd+D.
- Выполните выход пользователя postgres: exit или Ctrl+D.
- Переключитесь на linux-пользователя «postgres»:
- Создайте базу данных Postgres для своего приложения:
createdb edelivered_app_prod
- Создайте директорию приложения и настроек:
mkdir /home/app/mysite.com
- Создайте директорию хранения релизов:
mkdir /home/app/mysite.com/edeliver_release_store
- Добавьте глобальную переменную окружения PORT:
echo "PORT=8080" | sudo tee -a /etc/environment
- Добавьте ещё одну глобальную переменную окружения MIX_ENV:
echo "MIX_ENV=prod" | sudo tee -a /etc/environment
Шаг 5. Развёртывание distillery-релиза в продакшн с помощью Edeliver
Edeliver — это набор полезных сценариев для создания релиза при помощи distillery
и его развёртывания на некоторое количество серверов. В отличие от Rails и Capistrano, Edeliver подгружает код (используя Git) только на ОДИН сервер (билд-сервер), там же компилирует его, собирает ассеты, а затем производит развёртывание готового пакета на все серверы. Capistrano по умолчанию производит развёртывание кода и компиляцию ассетов на ВСЕХ серверах.
Добавьте edeliver
в список зависимостей проекта и в список приложений:
def application, do: [
applications: [
...
# Add edeliver to the END of the list
:edeliver
]
]
defp deps do
[
...
{:edeliver, "~> 1.4.0"}
]
end
и запустите
mix deps.get
В директории проекта создайте файл .deliver/config
:
#!/usr/bin/env bash
APP="edelivered_app" # <--- THIS MUST MATCH THE NAME OF THE RELEASE IN rel/config.exs
# AND THE NAME OF THE APP IN config/mix.exs!!!!!!!!!!
# Configuration of where the releases would be built.
BUILD_HOST="138.197.37.15" # change to your server's IP address
BUILD_USER="app"
BUILD_AT="/home/app/mysite.com/edeliver_builds"
# The location where built releases are going to be stored.
RELEASE_STORE=app@138.197.37.15:/home/app/mysite.com/edeliver_release_store/
# Host and use of where the app would run.
PRODUCTION_HOSTS="138.197.37.15" # same host in our case.
PRODUCTION_USER="app"
DELIVER_TO="/home/app/mysite.com"
pre_erlang_get_and_update_deps() {
# copy it on the build host to the build directory when building
local _secret_config_file_on_build_host="/home/app/mysite.com/prod.secret.exs"
status "Linking '$_secret_config_file_on_build_host' to build config dir"
__sync_remote "
ln -sfn '$_secret_config_file_on_build_host' '$BUILD_AT/config/prod.secret.exs'
"
}
pre_erlang_clean_compile() {
status "Installing nodejs dependencies"
__sync_remote "
[ -f ~/.profile ] && source ~/.profile
set -e
cd '$BUILD_AT'
APP='$APP' MIX_ENV='$TARGET_MIX_ENV' npm install
"
status "Building brunch assets"
__sync_remote "
[ -f ~/.profile ] && source ~/.profile
set -e
cd '$BUILD_AT'
mkdir -p priv/static
APP='$APP' MIX_ENV='$TARGET_MIX_ENV' npm run deploy
"
status "Compiling code"
__sync_remote "
[ -f ~/.profile ] && source ~/.profile
set -e #
cd '$BUILD_AT'
APP='$APP' MIX_ENV='$TARGET_MIX_ENV' $MIX_CMD do deps.get, compile
"
status "Running phoenix.digest"
__sync_remote "
[ -f ~/.profile ] && source ~/.profile
set -e #
cd '$BUILD_AT'
APP='$APP' MIX_ENV='$TARGET_MIX_ENV' $MIX_CMD phoenix.digest $SILENCE
"
}
Вышеуказанная конфигурация сгенерирует на сервере следующее дерево каталогов:
/home/app/mysite.com # Your entire app is in one place: configuration, builds and releases.
├── edeliver_builds # This is where edeliver builds all releases: your local repo is pushed here.
│ ├── brunch-config.js
│ ├── _build
│ ├── config
│ ├── deps
│ ├── lib
│ ├── mix.exs
│ ├── mix.lock
│ ├── node_modules
│ ├── package.json
│ ├── priv
│ ├── README.md
│ ├── rel
│ ├── test
│ └── web
├── edelivered_app # Your app is deployed here: this is where it runs, from "bin" directory.
│ ├── bin
│ ├── erl_crash.dump
│ ├── erts-8.2
│ ├── lib
│ ├── releases
│ └── var
├── edeliver_release_store # Edeliver stores all built releases here to then distribute them to servers.
│ └── edelivered_app_0.0.1.release.tar.gz
└── prod.secret.exs # Your app's secrets: production database connection parameters.
Подтвердите изменения и пометьте коммит «0.0.1» — версией, совпадающей с версией вашего проекта в mix.exs
.
git commit -am "Add edeliver configuration"
git tag 0.0.1
Теги впоследствии понадобятся для обновления релизов в edeliver.
Подключитесь к серверу по SSH и создайте файл /home/app/mysite.com/prod.secret.exs
со следующим содержимым:
use Mix.Config
config :edelivered_app, EdeliveredApp.Endpoint,
secret_key_base: "Xt317VM159wCrVgKhatAAbJcz3/yYewpbuXEpBeUpiIEOBVrTWEW878d6vADJU2u"
config :edelivered_app, EdeliveredApp.Repo,
adapter: Ecto.Adapters.Postgres,
username: "app",
password: "coolpass",
database: "edelivered_app_prod",
pool_size: 20
Создайте релиз, произведите развёртывание и запустите его:
env MIX_ENV=prod mix edeliver build release
— создаёт релиз и помещает его в директорию релизов на сервере.mix edeliver deploy release to production --version=0.0.1
производит развёртывание релиза на все серверы, но не запускает его!- Попробуйте на всякий случай после первого развёртывания запустить релиз в обычном режиме:
- Подключитесь к серверу по SSH в качестве пользователя
app
. cd ~/mysite.com/edelivered_app
bin/edelivered_app foreground
- Убедитесь, что сервер работает и откройте его в браузере: [http://138.197.37.15:8080][http://138.197.37.15:8080] (ваш IP-адрес, естественно, будет другим)
- Выполните выход: Ctrl+C
- Подключитесь к серверу по SSH в качестве пользователя
mix edeliver start production
запускает приложение на сервере в виде демона!
Иногда команда для запуска релиза по какой-то неведомой мне причине не справляется со своими обязанностями. Если после выполнения mix edeliver start production
сайт недоступен, подключитесь к серверу по SSH и проверьте, запустился ли процесс Erlang: ps -ef | grep erl
. Если нет, то ищите решение в следующем пункте.
Шаг 6. Устранение ошибок
Последняя версия образца Phoenix-приложения с настроенными distillery и edeliver доступна по ссылке: https://github.com/alex-kovshovik/edelivered_app.
- Не забывайте прописывать
MIX_ENV=prod
во всех командах развёртывания. - Если что-то пошло не так, удалите директорию
_build
и попробуйте снова. - Не используйте автоматическую версию AUTO_VERSION с самого начала, попробуйте сперва настроить всё вручную. Мне пришлось пройти через многое в попытках сделать этот способ развёртывания таким же простым, как у Capistrano. Не забывайте увеличивать номер версии своего приложения и создавать тег с таким же именем.
- Значение переменной APP должно совпадать с названием релиза в
rel/config.exs
. В противном случае получим ошибку: «Failed to build release: :no_release». - Значение переменной APP должно совпадать с именем приложения, иначе оно не запустится! Я пытался изменить его на «current_release», чтобы оптимизировать дерево каталогов сервера, в результате чего столкнулся с непредвиденными ошибками.
- Полезные переключатели mix edeliver:
--verbose
— отображает результаты работы команд;--debug
— показывает все команды и их вывод.
- Внесите поправку в файл
rel/config.exs
, как указано в первом комментарии здесь distillery support broke with changed output_dir in distillery 1.0.0 — Issue #182.
Шаг 7. Что теперь?
В следующих статьях будет рассмотрено:
- Настройка nginx в качестве обратного прокси-сервера для виртуальной машины Erlang.
- Создание и настройка SSL-сертификата с помощью letsencrypt.
- Создание и развёртывание обновлённых релизов.
- Быстрое создание релизов в docker-контейнерах или на локальной виртуальной машине.
- Создание релизов на CI-сервере.