エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Phoenixが入ったDocker containerをHerokuに配備する

de・ploy | dɪplɔ́ɪ |

他動詞

1 〘軍〙 〈部隊など〉を展開させる, 〈兵器など〉を配置[配備]する.

ウィズダム英和辞典


いまさらなのですが。

Phoenix のドキュメントに Docker container を利用したリリースの手順が追加されていることに気がつきました。 今夏のアップデートで追加されたようです。

また Heroku のドキュメントにも Docker container を利用したデプロイの手順が掲載されています。

従来から Phoenix アプリケーションを Heroku にデプロイするには buildpack を利用する手順がドキュメントに記載されていますが、これらのドキュメントに従えば Docker container を利用したリリースが可能になるはずです。

と、いうわけで。 Phoenix アプリケーションを new するところから Heroku にデプロイするまでの手順をまとめてみました。

いずれも Phoenix あるいは Heroku のドキュメントに記載されている内容ですので難しいことはないと思いますが、細かなところで情報が分散していてつまづくところがあったので何かの参考になればと思います。

{:elixir, "~> 1.9"}

なお Config モジュールと mix release コマンドを利用するので Elixir は 1.9 以上が必要です。

プロジェクトを用意する

プロジェクトを作成します。

$ mix phx.new hoge
$ cd hoge

Heroku はリポジトリの設定を .git/config に記録するので、Git のリポジトリを作成しておいてください。 設定が記録できればよいのでコードの変更を commit しておく必要はないのですが、一般的に作業の巻き戻しなどに備えて適宜 commit しながら進めてください。

$ git init
$ git add .
$ git commit -m 'initial commit'

Config を変更する

config ファイルを書き換えます。

書き換えについては Phoenix のドキュメント「Deploying with Releases」のセクション「Runtime configuration」に解説があります。

ファイル名の変更

ディレクトconfig/ にある prod.secret.exs のファイル名を releases.exs に変更します。

releases.exs の編集

releases.exs の内容を編集します。

まず、モジュール Config を利用するように変更します。

use Mix.Configimport Config に書き換えます。

--- a/config/prod.secret.exs
+++ b/config/releases.exs
@@ -2,7 +2,7 @@
 # from environment variables. You can also hardcode secrets,
 # although such is generally not recommended and you have to
 # remember to add this file to your .gitignore.
-use Mix.Config
+import Config

次に Endpoint の設定に server: true を追加します。

--- a/config/prod.secret.exs
+++ b/config/releases.exs
@@ -25,7 +25,8 @@ secret_key_base =
 
 config :hoge, HogeWeb.Endpoint,
   http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
-  secret_key_base: secret_key_base
+  secret_key_base: secret_key_base,
+  server: true

これは Mix を利用せずに単独でサーバとして起動するための設定です。

  • :server - when true, starts the web server when the endpoint supervision tree starts. Defaults to false. The mix phx.server task automatically sets this to true

Runtime configuration / Phoenix.Endpoint

なお Heroku の制限として、HTTP のポートは Heroku が設定する環境変数 PORT の値を利用する必要があります。

  • The web process must listen for HTTP traffic on $PORT, which is set by Heroku.

Dockerfile commands and runtime / Container Registry & Runtime (Docker Deploys) | Heroku Dev Center

ただこれは自動生成される config に port: String.to_integer(System.get_env("PORT") || "4000") と最初から設定されていますので変更せずこのままで大丈夫です。

また config/releases.exs については、一つ前の記事に簡単にまとめましたので参考にしてみてください。

不要な設定の削除

prod.secret.exs が不要になったので prod.exs 内の prod.secret.exs を読み込んでいる行を削除します。

--- a/config/prod.exs
+++ b/config/prod.exs
@@ -52,4 +52,3 @@ config :logger, level: :info
 
 # Finally import the config/prod.secret.exs which loads secrets
 # and configuration from environment variables.
-import_config "prod.secret.exs"

Dockerfile を用意する

Dockerfile を用意します。 アプリケーション名が現れるのはリリース版のファイルをコピーする部分とサーバを起動する部分のみです。 この二つを書き換えるだけで他のプロジェクトでも基本的に同じ内容で対応できます。

########################################
# ビルド
########################################

FROM elixir:1.9-alpine as build

WORKDIR /app

# ビルドに必要なツールをインストールする
RUN apk add --update git build-base nodejs yarn npm

# Hex と rebar をインストールする
RUN mix local.hex --force && mix local.rebar

# ビルド時の環境変数を設定する
ENV MIX_ENV=prod

# 依存するパッケージをインストールする
COPY mix.exs ./
COPY mix.lock ./
COPY config ./config
RUN mix deps.get --only prod
RUN mix deps.compile

# assets をインストールする
COPY assets ./assets
RUN npm install --prefix ./assets && npm run deploy --prefix ./assets
RUN mix phx.digest

# ビルドする
COPY priv ./priv
COPY lib ./lib
RUN mix compile

# リリース版を構築する
#   リリースの設定を rel/ に用意しているばあいは、それのコピーもしておいてください
# COPY rel ./rel
RUN mix release

########################################
# リリースするイメージを構築する
########################################

FROM alpine:3.10 as web

WORKDIR /app

# 実行時に必要なツールをインストールする
RUN apk add --update bash openssl

# ビルドしたリリース版のファイルをコピーする
#   アプリケーション名の部分 ( `hoge` ) は作成したアプリケーションの名前に合わせてください
COPY --from=build /app/_build/prod/rel/hoge ./

# ユーザを設定する
RUN chown -R nobody: /app
USER nobody

# 実行時の環境変数を設定する
ENV HOME=/app

# サーバを起動する
#   アプリケーション名の部分 ( `hoge` ) は作成したアプリケーションの名前に合わせてください
CMD bin/hoge start

Heroku にデプロイする

Heroku アプリケーションの作成

まず Heroku のコンテナリポジトリにログインします。

$ heroku container:login

次に Heroku に新しいアプリケーションを作成します。 heroku create コマンドを使って、あるいは Heroku のコンソールを利用して作成します。

$ heroku create hoge-with-docker

環境変数の設定

環境変数を設定します。

SECRET_KEY_BASE を設定します。 ここでは mix phx.gen.secret コマンドで生成した値を設定しています。 heroku config:set コマンドを使って、あるいは Heroku のコンソールを利用して設定します。

$ heroku config:set SECRET_KEY_BASE=`mix phx.gen.secret`

データベースの設定

データベースを利用するばあい、データベースの設定をします。 Heroku のアドオンを利用するばあい、heroku addons:create コマンドを使って、あるいは Hroku コンソールを利用して設定します。

$ heroku addons:create heroku-postgresql

アドオンを利用したばあいは接続先の URL が環境変数DATABASE_URL に設定されます。 config/releases.exs の初期状態では DATABASE_URL から接続先の情報を取得するようになっているので、この状態でデプロイすればデータベースに接続できるようになります。

Heroku のアドオン以外のデータベースを利用するばあいは、そのデータベースのURLを DATABASE_URL に設定してください。

$ heroku config:set DATABASE_URL=postgres://USER:PASSWORD@DOMAIN:PORT/DATABASE

Push the image and release

Docker イメージをビルドして Heroku に push します。

$ heroku container:push web

push したイメージをリリースします。

$ heroku container:release web

すべての準備が整ったので、デプロイしたアプリケーションを開きます。 ブラウザでアプリケーションの URL を入力して開くか、あるいは heroku open コマンドでページを開きます。

$ heroku open

データベースのマイグレーション

データベースを利用するばあい、データベースのマイグレーションを実行する必要があります。 通常は mix ecto.migrate コマンドを利用しますがリリース版は Mix を含まないためこのコマンドを実行できません。

このようなばあい、自分でマイグレーションの処理を用意し実行する必要があります。

これについても Phoenix のドキュメントに記載されています。

lib/hoge/マイグレーションを実行する関数を記述した次のような内容のファイル release.ex を追加します。

ファイル名、ファイルの格納ディレクトリは、マイグレーションを実行できれば任意でかまわないのですが、ここではドキュメントに合わせた構成にしています。

# lib/hoge/release.ex

defmodule Hoge.Release do
  @app :hoge

  def migrate do
    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  def rollback(repo, version) do
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  defp repos do
    Application.load(@app)
    Application.fetch_env!(@app, :ecto_repos)
  end
end

これをアプリケーションの eval コマンドで実行します。

Phoenix のドキュメントでは次のような実行例が記載されています。

$ _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate"

今回は Heroku にデプロイしているので Heroku 上でプロセスを実行するので heroku run コマンドを利用します。

またリリースは _build/prod/rel/hoge/ 以下の内容をデプロイしているので実行パスも bin/ 以降を指定すればよく bin/hoge eval という形になります。

全体として。次のようにマイグレーションを実行します。 アプリケーション名の部分 ( hoge ) 、モジュール名の部分 ( Hoge ) は作成されるアプリケーション、モジュールに置き換えて実行してください。

$ heroku run 'bin/hoge eval "Hoge.Release.migrate()"'

WebSocket を利用する

Phoenix.Channel や Phoenix.LiveView などで WebSocket を利用するばあい、 :check_origin を設定する必要があります。

  • :check_origin - configure transports to check origin header or not. May be false, true, a list of hosts that are allowed, or a function provided as MFA tuple. Hosts also support wildcards.

Runtime configuration/ Phoenix.Endpoint

config/releases.exs に :check_origin を追加します。 ここでは環境変数を利用した設定にしています。

# config/releases.exs

config :hoge, HogeWeb.Endpoint,
  http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
  secret_key_base: secret_key_base,
  check_origin: ["//#{System.get_env("HOST")}"],
  server: true

アプリケーションの環境変数に値を追加します。

$ heroku config:set HOST=hoge-with-docker.herokuapp.com

これで WebSocket のリクエストを受け付けることができるようになりました。

いつか読むはずっと読まない:古典

自分にとって長らくプログラミングスタイルの拠り所になっていたのは間違いなく「プログラム書法」でした。 さすがに現在ではそぐわない内容も多くなりましたが、プログラミングに対する姿勢はこの本で学びました。

「達人プログラマー」(翻訳旧版)を読んだとき、「現代の『プログラム書法』だ」と思ったものでした。

その「達人プログラマー」も現在ではすっかり古典となったようです。 古典であるからこそ、立ち返ってその考えをふたび吸収したいと思います。

プログラム書法 第2版

プログラム書法 第2版

新装版 達人プログラマー 職人から名匠への道

新装版 達人プログラマー 職人から名匠への道