Развертывание на Heroku
Исходный кодГлавная цель этого руководства — запустить приложение Phoenix на Heroku.
Что нам понадобится
Для этого руководства нам понадобится только работающее приложение Phoenix. Если вам нужно простое приложение для развертывания, следуйте руководству «Начало работы».
Ограничения
Heroku — отличная платформа, и Elixir хорошо на ней работает. Однако вы можете столкнуться с ограничениями, если планируете использовать расширенные возможности Elixir и Phoenix, например:
-
Количество подключений ограничено.
- Heroku ограничивает число одновременных подключений, а также длительность каждого подключения. Elixir часто используют для приложений реального времени, которым требуется множество одновременных постоянных подключений, а Phoenix способен обрабатывать более 2 миллионов подключений на одном сервере.
-
Распределенная кластеризация невозможна.
- Heroku изолирует dyno друг от друга с помощью брандмауэров. Это означает, что таким возможностям, как распределенные каналы Phoenix и распределенные задачи, придется полагаться, например, на Redis вместо встроенного механизма распределения Elixir.
-
Состояние в памяти, например в Agents, GenServers и ETS, будет теряться каждые 24 часа.
- Heroku перезапускает dyno каждые 24 часа, независимо от того, исправен ли узел.
-
Встроенный observer нельзя использовать с Heroku.
- Heroku позволяет подключаться к dyno, но использовать observer для наблюдения за состоянием dyno не получится.
Если вы только начинаете работу или не планируете использовать перечисленные выше возможности, Heroku должно быть достаточно. Например, если вы переносите на Phoenix существующее приложение, работающее на Heroku, и сохраняете примерно тот же набор функций, Elixir будет работать не хуже, а возможно, даже лучше вашего текущего стека.
Если вам нужна платформа как услуга без этих ограничений, альтернативы перечислены на боковой панели и доступны в других местах. Если вы предпочитаете развертывание на облачной платформе, например EC2, Google Cloud и т. д., рассмотрите возможность использования mix release.
Этапы
Разделим процесс на несколько этапов, чтобы отслеживать, на каком месте мы находимся.
- Инициализировать репозиторий Git
- Зарегистрироваться на Heroku
- Установить Heroku Toolbelt
- Создать приложение Heroku и настроить его
- Подготовить проект к работе на Heroku
- Развернуть приложение!
- Полезные команды Heroku
Инициализация репозитория Git
Git — популярная децентрализованная система управления версиями, которая также используется для развертывания приложений на Heroku.
Прежде чем отправлять проект на Heroku, нужно инициализировать локальный репозиторий Git и зафиксировать в нем файлы. Для этого выполним следующие команды в каталоге проекта:
$ git init $ git add . $ git commit -m "Initial commit"
Здесь Heroku рассказывает подробнее о том, как использует Git: здесь.
Регистрация на Heroku
Зарегистрироваться на Heroku очень просто: перейдите на https://signup.heroku.com/ и заполните форму.
Бесплатный тариф предоставит один веб-dyno и один рабочий dyno, а также бесплатные экземпляры PostgreSQL и Redis.
Эти ресурсы предназначены для тестирования и разработки и имеют некоторые ограничения. Для работы производственного приложения рассмотрите возможность перехода на платный тариф.
Установка Heroku Toolbelt
После регистрации скачайте подходящую для вашей системы версию Heroku Toolbelt здесь.
Heroku CLI, входящий в состав Toolbelt, пригодится для создания приложений Heroku, просмотра списка запущенных dyno существующего приложения, вывода журналов в реальном времени и выполнения разовых команд (например, задач mix).
Создание и настройка приложения Heroku
Есть два способа развернуть приложение Phoenix на Heroku: использовать buildpack Heroku или стек контейнеров. Различие между этими подходами заключается в том, как мы указываем Heroku обрабатывать сборку. При использовании buildpack нужно настроить приложение на Heroku, чтобы оно использовало buildpack для Phoenix/Elixir. При использовании контейнеров у нас больше возможностей контролировать настройку приложения, а образ контейнера можно определить с помощью Dockerfile и heroku.yml. В этом разделе мы рассмотрим подход с buildpack. Для использования Dockerfile часто рекомендуют преобразовать приложение, чтобы использовать релизы; об этом мы расскажем далее.
Создание приложения
Buildpack — удобный способ упаковать поддержку фреймворка и/или среды выполнения. Для работы Phoenix на Heroku требуются два buildpack: первый добавляет базовую поддержку Elixir, а второй — команды для Phoenix.
Установив Toolbelt, создадим приложение Heroku. Для этого воспользуемся последней доступной версией buildpack для Elixir:
$ heroku create --buildpack hashnuke/elixir Creating app... done, ⬢ mysterious-meadow-6277 Setting buildpack to hashnuke/elixir... done https://mysterious-meadow-6277.herokuapp.com/ | https://git.heroku.com/mysterious-meadow-6277.git
Примечание: при первом выполнении команды Heroku может появиться запрос на вход. В этом случае введите адрес электронной почты и пароль, указанные при регистрации.
Примечание: имя приложения Heroku — это случайная строка после «Creating» в приведенном выше выводе (mysterious-meadow-6277). Имя будет уникальным, поэтому у вас будет другое значение, а не «mysterious-meadow-6277».
Примечание: URL-адрес в выводе ведет на наше приложение. Если открыть его в браузере сейчас, отобразится приветственная страница Heroku по умолчанию.
Примечание: если бы мы не инициализировали репозиторий Git до выполнения команды
heroku create, удаленный репозиторий Heroku сейчас не был бы настроен должным образом. Это можно сделать вручную, выполнив:heroku git:remote -a [our-app-name].
В buildpack заданы версии Elixir и Erlang по умолчанию. Во избежание неожиданностей при развертывании лучше явно указать версии Elixir и Erlang, которые будут использоваться в production, чтобы они совпадали с версиями для разработки или на серверах непрерывной интеграции. Для этого создайте в корневом каталоге проекта файл конфигурации elixir_buildpack.config и укажите в нем нужные версии Elixir и Erlang:
# Elixir version elixir_version=1.15.0 # Erlang version # https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions erlang_version=25.3 # Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild # Note we nuke the esbuild executable from the image hook_post_compile="eval mix assets.deploy && rm -f _build/esbuild*"
Наконец, укажем buildpack, как запускать веб-сервер. Создайте файл Procfile в корневом каталоге проекта:
web: mix phx.server
Необязательно: Node, npm и Phoenix Static buildpack
По умолчанию Phoenix использует esbuild и управляет всеми ресурсами за вас. Однако, если вы используете node и npm, для их обработки потребуется установить Phoenix Static buildpack:
$ heroku buildpacks:add https://github.com/gigalixir/gigalixir-buildpack-phoenix-static.git Buildpack added. Next release on mysterious-meadow-6277 will use: 1. https://github.com/HashNuke/heroku-buildpack-elixir.git 2. https://github.com/gigalixir/gigalixir-heroku-buildpack-phoenix-static.git
При использовании этого buildpack всю сборку ресурсов нужно делегировать npm. Поэтому необходимо удалить конфигурацию hook_post_compile из elixir_buildpack.config и перенести ее в скрипт развертывания вашего assets/package.json. Например:
{
...
"scripts": {
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*"
}
...
}
В Phoenix Static buildpack задана версия Node.js по умолчанию. Во избежание неожиданностей при развертывании лучше явно указать версию Node.js для production, чтобы она совпадала с версией для разработки или на серверах непрерывной интеграции. Для этого создайте в корневом каталоге проекта файл конфигурации phoenix_static_buildpack.config и укажите в нем нужную версию Node.js:
# Node.js version node_version=10.20.1
Полное описание приведено в разделе о конфигурации. Можно создать собственный скрипт сборки, но пока воспользуемся скриптом по умолчанию.
Наконец, учтите, что при использовании нескольких buildpack может возникнуть проблема с неправильным порядком выполнения (buildpack для Elixir должен выполняться перед Phoenix Static buildpack). В документации Heroku это объясняется подробнее, но нужно убедиться, что Phoenix Static buildpack указан последним.
Подготовка проекта к работе на Heroku
В каждом новом проекте Phoenix есть файл конфигурации config/runtime.exs, который загружает конфигурацию и секреты из переменных окружения. Это хорошо согласуется с рекомендациями Heroku (приложения на основе 12 факторов), поэтому нам остается настроить URL-адреса и SSL.
Сначала укажем Phoenix, что сайту нужно использовать только версию с SSL. Найдите конфигурацию endpoint в файле config/prod.exs:
config :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: "https"],
... и добавьте force_ssl
config :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: "https"], force_ssl: [rewrite_on: [:x_forwarded_proto]],
Здесь нужно задать force_ssl, потому что это конфигурация времени компиляции. Она не будет работать, если задать ее в runtime.exs.
Затем в файле config/runtime.exs:
... добавьте host
config :scaffold, ScaffoldWeb.Endpoint, url: [host: host, port: 443, scheme: "https"]
и раскомментируйте строку # ssl: true, в конфигурации репозитория. Она будет выглядеть так:
config :hello, Hello.Repo,
ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
Наконец, если вы планируете использовать веб-сокеты, нужно уменьшить время ожидания для транспорта веб-сокетов в lib/hello_web/endpoint.ex. Если веб-сокеты использовать не планируется, можно оставить значение false. Подробнее о доступных параметрах см. в документации.
defmodule HelloWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :hello
socket "/socket", HelloWeb.UserSocket,
websocket: [timeout: 45_000]
...
end
Также задайте хост в Heroku:
$ heroku config:set PHX_HOST="mysterious-meadow-6277.herokuapp.com"
Это гарантирует, что Phoenix закроет все бездействующие подключения до истечения 55-секундного времени ожидания Heroku.
Создание переменных окружения в Heroku
Переменная конфигурации DATABASE_URL создается автоматически при добавлении дополнения Heroku Postgres. Базу данных можно создать с помощью Heroku Toolbelt:
$ heroku addons:create heroku-postgresql:mini
Теперь зададим переменную конфигурации POOL_SIZE:
$ heroku config:set POOL_SIZE=18
Это значение должно быть немного меньше числа доступных подключений, чтобы несколько подключений оставались свободными для миграций и задач mix. Мини-база данных допускает 20 подключений, поэтому зададим значение 18. Если к базе данных будут подключаться дополнительные dyno, уменьшите POOL_SIZE, чтобы равномерно распределить подключения между dyno.
При последующем запуске задачи mix (после отправки проекта на Heroku) также нужно будет ограничить размер ее пула, например так:
$ heroku run "POOL_SIZE=2 mix hello.task"
Это не позволит Ecto пытаться открыть больше подключений, чем доступно.
Нам также нужно создать конфигурацию SECRET_KEY_BASE на основе случайной строки. Сначала воспользуйтесь mix phx.gen.secret, чтобы сгенерировать новый секрет:
$ mix phx.gen.secret xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53
У вас будет другая случайная строка; не используйте значение из этого примера.
Теперь зададим ее в Heroku:
$ heroku config:set SECRET_KEY_BASE="xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53" Setting config vars and restarting mysterious-meadow-6277... done, v3 SECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53
Развертывание!
Теперь проект готов к развертыванию на Heroku.
Зафиксируем все изменения:
$ git add elixir_buildpack.config $ git commit -a -m "Use production config from Heroku ENV variables and decrease socket timeout"
И развернем приложение:
$ git push heroku main Counting objects: 55, done. Delta compression using up to 8 threads. Compressing objects: 100% (49/49), done. Writing objects: 100% (55/55), 48.48 KiB | 0 bytes/s, done. Total 55 (delta 1), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Multipack app detected remote: -----> Fetching custom git buildpack... done remote: -----> elixir app detected remote: -----> Checking Erlang and Elixir versions remote: WARNING: elixir_buildpack.config wasn't found in the app remote: Using default config from Elixir buildpack remote: Will use the following versions: remote: * Stack cedar-14 remote: * Erlang 17.5 remote: * Elixir 1.0.4 remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENV=prod remote: -----> Stack changed, will rebuild remote: -----> Fetching Erlang 17.5 remote: -----> Installing Erlang 17.5 (changed) remote: remote: -----> Fetching Elixir v1.0.4 remote: -----> Installing Elixir v1.0.4 (changed) remote: -----> Installing Hex remote: 2015-07-07 00:04:00 URL:https://s3.amazonaws.com/s3.hex.pm/installs/1.0.0/hex.ez [262010/262010] -> "/app/.mix/archives/hex.ez" [1] remote: * creating /app/.mix/archives/hex.ez remote: -----> Installing rebar remote: * creating /app/.mix/rebar remote: -----> Fetching app dependencies with mix remote: Running dependency resolution remote: Dependency resolution completed successfully remote: [...] remote: -----> Compiling remote: [...] remote: Generated phoenix_heroku app remote: [...] remote: Consolidated protocols written to _build/prod/consolidated remote: -----> Creating .profile.d with env vars remote: -----> Fetching custom git buildpack... done remote: -----> Phoenix app detected remote: remote: -----> Loading configuration and environment remote: Loading config... remote: [...] remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENV=prod remote: remote: -----> Compressing... done, 82.1MB remote: -----> Launching... done, v5 remote: https://mysterious-meadow-6277.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/mysterious-meadow-6277.git * [new branch] master -> master
Если ввести heroku open в терминале, откроется браузер со страницей приветствия Phoenix. Если для доступа к базе данных используется Ecto, после первого развертывания также нужно выполнить миграции:
$ heroku run "POOL_SIZE=2 mix ecto.migrate"
Вот и все!
Развертывание на Heroku с помощью стека контейнеров
Создание приложения Heroku
Установите для приложения стек container — это позволит использовать Dockerfile для настройки приложения.
$ heroku create Creating app... done, ⬢ mysterious-meadow-6277 $ heroku stack:set container
Добавьте новый файл heroku.yml в корневой каталог. В этом файле можно определить дополнения, используемые приложением, способ сборки образа и конфигурации, передаваемые образу. Подробнее о параметрах heroku.yml Heroku можно узнать здесь. Вот пример:
setup:
addons:
- plan: heroku-postgresql
as: DATABASE
build:
docker:
web: Dockerfile
config:
MIX_ENV: prod
SECRET_KEY_BASE: $SECRET_KEY_BASE
DATABASE_URL: $DATABASE_URL
Настройка релизов и Dockerfile
Теперь в корневом каталоге проекта с приложением нужно создать файл Dockerfile. Для этого рекомендуется использовать релизы: с их помощью можно собрать контейнер, содержащий только те компоненты Erlang и Elixir, которые действительно используются. Следуйте документации по релизам. В конце руководства приведен пример файла Dockerfile, который можно использовать.
После настройки определения образа можно отправить приложение в Heroku; начнется сборка образа и развертывание приложения.
Полезные команды Heroku
Просмотреть журналы приложения можно, выполнив следующую команду в каталоге проекта:
$ heroku logs # use --tail if you want to tail them
Также можно запустить сессию IEx, подключенную к терминалу, чтобы экспериментировать в окружении приложения:
$ heroku run "POOL_SIZE=2 iex -S mix"
На самом деле с помощью команды heroku run можно запускать любые команды, например упомянутую выше задачу миграции Ecto:
$ heroku run "POOL_SIZE=2 mix ecto.migrate"
Подключение к dyno
Heroku позволяет подключаться к dyno через оболочку IEx, в которой можно выполнять код Elixir, например запросы к базе данных.
-
Измените процесс
webв Procfile, чтобы запускать именованный узел:web: elixir --sname server -S mix phx.server
Повторно разверните приложение на Heroku
Подключитесь к dyno с помощью
heroku ps:exec(если в одном репозитории находится несколько приложений, укажите имя приложения или удаленного репозитория с помощью--app APP_NAMEили--remote REMOTE_NAME)Запустите сессию iex с помощью
iex --sname console --remsh server
Теперь у вас есть сессия iex в вашем dyno!
Устранение неполадок
Ошибка компиляции
Иногда приложение успешно компилируется локально, но не на Heroku. Ошибка компиляции на Heroku может выглядеть примерно так:
remote: == Compilation error on file lib/postgrex/connection.ex == remote: could not compile dependency :postgrex, "mix compile" failed. You can recompile this dependency with "mix deps.compile postgrex", update it with "mix deps.update postgrex" or clean it with "mix deps.clean postgrex" remote: ** (CompileError) lib/postgrex/connection.ex:207: Postgrex.Connection.__struct__/0 is undefined, cannot expand struct Postgrex.Connection remote: (elixir) src/elixir_map.erl:58: :elixir_map.translate_struct/4 remote: (stdlib) lists.erl:1353: :lists.mapfoldl/3 remote: (stdlib) lists.erl:1354: :lists.mapfoldl/3 remote: remote: remote: ! Push rejected, failed to compile elixir app remote: remote: Verifying deploy... remote: remote: ! Push rejected to mysterious-meadow-6277. remote: To https://git.heroku.com/mysterious-meadow-6277.git
Причина — устаревшие зависимости, которые не перекомпилируются должным образом. Чтобы решить эту проблему, можно заставить Heroku перекомпилировать все зависимости при каждом развертывании. Для этого создайте в корневом каталоге приложения файл elixir_buildpack.config. Он должен содержать следующую строку:
always_rebuild=true
Зафиксируйте этот файл в репозитории и попробуйте снова отправить проект на Heroku.
Ошибка ожидания подключения
Если при выполнении heroku run постоянно возникают тайм-ауты подключения, возможно, ваш интернет-провайдер заблокировал порт 5000:
heroku run "POOL_SIZE=2 mix myapp.task" Running POOL_SIZE=2 mix myapp.task on mysterious-meadow-6277... ! ETIMEDOUT: connect ETIMEDOUT 50.19.103.36:5000
Это можно исправить, добавив параметр detached в команду запуска:
heroku run:detached "POOL_SIZE=2 mix ecto.migrate" Running POOL_SIZE=2 mix ecto.migrate on mysterious-meadow-6277... done, run.8089 (Free)
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/heroku.html