Heroku
Elixir
Phoenix

Phoenix Production デプロイへの道その2:Heroku編

More than 1 year has passed since last update.

前の記事では、

手元で production モードで動かすというのを試して、

「デーモンモードで動かした時 log どうすんの...」という問題があったのでした。

この辺り、 Heroku ではうまくやってくれそうな気がするのと、公式のドキュメントでも

Heroku での手順が詳しく書いてあるので、 Heroku でのデプロイを試してみます。

結論からいうと、概ね公式ドキュメントの通りにやったら、全く問題なく動きました!

一点だけ気をつける点として、公式ドキュメントだとやはり mix のコマンドが古いので、

phoenix.* ではなく phx.* に書き換えましょう。


前提


  • すでに heroku アカウントは持っている

  • 手元に phoenix アプリの git リポジトリがすでにある

  • 手元に heroku toolbelt をインストールしてある


heroku 上に buildpack を指定してアプリケーション作成

buildpack というのは、 heroku が標準では対応していない言語・フレームワークを

Heroku で動かせるようにするためのビルドスクリプトみたいなやつです。

$ heroku create --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git"

Creating app... done, ⬢ cryptic-waters-12345
Setting buildpack to https://github.com/HashNuke/heroku-buildpack-elixir.git... done
https://cryptic-waters-12345.herokuapp.com/ | https://git.heroku.com/cryptic-waters-12345.git

これで、ランダム(?)に生成された URL が割り当てられると同時に、

deploy 先の remote リポジトリの設定が行われました。無料でも一応動きますが、

料金プラン をよくご確認の上、利用しましょう。

では、push 先が設定されているか確認してみましょう。

$ git remote -v

heroku https://git.heroku.com/cryptic-waters-12345.git (fetch)
heroku https://git.heroku.com/cryptic-waters-12345.git (push)

ここに push するだけで自動的にビルドが行われ、アプリケーションが動き出しますが、

その手順は後で詳しくやります。


もう一つビルドパック追加

$ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git

Buildpack added. Next release on cryptic-waters-12345 will use:
1. https://github.com/HashNuke/heroku-buildpack-elixir.git
2. https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
Run git push heroku master to create a new release using these buildpacks.

名前から察するに、assets のコンパイルをするためのビルドパックですかね。


データベースを追加

$ heroku addons:create heroku-postgresql:hobby-dev

Creating heroku-postgresql:hobby-dev on ⬢ cryptic-waters-12345... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-round-23456 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

無料版である heroku-postgresql:hobby-dev というプランの DB を追加しました。

レコード数やデータ容量にかなり制限がありますので、詳しくは

heroku

で確認し、必要に応じて課金しましょう。


パスワードなどを環境変数から読み取るように変更


config/prod.exs

@@ -15,8 +15,17 @@ use Mix.Config

# which you typically run after static files are built.
config :blog, BlogWeb.Endpoint,
load_from_system_env: true,
- url: [host: "example.com", port: 80],
- cache_static_manifest: "priv/static/cache_manifest.json"
+ url: [scheme: "https", host: "cryptic-waters-12345.herokuapp.com", port: 443],
+ force_ssl: [rewrite_on: [:x_forwarded_proto]],
+ cache_static_manifest: "priv/static/cache_manifest.json",
+ secret_key_base: Map.fetch!(System.get_env(), "SECRET_KEY_BASE")
+
+# Configure your database
+config :blog, Blog.Repo,
+ adapter: Ecto.Adapters.Postgres,
+ url: System.get_env("DATABASE_URL"),
+ pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
+ ssl: true

# Do not print debug messages in production
config :logger, level: :info
@@ -61,4 +70,4 @@ config :logger, level: :info

# Finally import the config/prod.secret.exs
# which should be versioned separately.
-import_config "prod.secret.exs"
+#import_config "prod.secret.exs"


WebSocket の timeout を設定


lib/blog_web/channels/user_socket.ex

@@ -5,7 +5,7 @@ defmodule BlogWeb.UserSocket do

# channel "room:*", BlogWeb.RoomChannel

## Transports
- transport :websocket, Phoenix.Transports.WebSocket
+ transport :websocket, Phoenix.Transports.WebSocket, timeout: 45_000
# transport :longpoll, Phoenix.Transports.LongPoll

# Socket params are passed from the client and can


まだ必要性を全く理解できてないですが、言われるがままにやります。


Procfile を追加


Procfile

web: MIX_ENV=prod mix phx.server


heroku にアプリケーションをどう起動してもらうかを指定するファイルです。

ここが公式ドキュメントでは古い mix コマンドになってますが、 phx.* を使うように変更しましょう。


環境変数の設定

DATABASE_URL は上記のDBの追加をしたときに heorku が自動で設定してくれてますので、

自分でやる必要はありません。なので、 SECRET_KEY_BASE だけ設定する必要があります。


SECRET_KEY_BASE

$ mix phx.gen.secret

WCYhVvNfvyzYGT4RLERivIr2VYQs8oWgyJ6E+jZwjNWmjJ/nFl4k0dgPU1q3udHP

ここも公式ドキュメントでは古いコマンドなので、 phx.* を使いましょう。

$ heroku config:set SECRET_KEY_BASE="WCYhVvNfvyzYGT4RLERivIr2VYQs8oWgyJ6E+jZwjNWmjJ/nFl4k0dgPU1q3udHP"


いよいよ push!

$ git push heroku master

手元の git の master を先ほど確認した heroku という remote リポジトリに push します。

これで、自動的に諸々のビルドが走り、 Procfile で指定したコマンドが起動します。

ログが非常に長いので、省略します。

$ git push heroku master

Counting objects: 117, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (104/104), done.
Writing objects: 100% (117/117), 59.54 KiB | 0 bytes/s, done.
Total 117 (delta 17), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
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 heroku-16
remote: * Erlang 19.3
remote: * Elixir 1.4.2
remote: -----> Will export the following config vars:
remote: DATABASE_URL
remote: POOL_SIZE
remote: SECRET_KEY_BASE
remote: * MIX_ENV=prod
remote: -----> Stack changed, will rebuild
remote: -----> Fetching Erlang 19.3
remote: -----> Installing Erlang 19.3 (changed)
remote:
remote: -----> Fetching Elixir v1.4.2 for OTP 19
remote: -----> Installing Elixir v1.4.2 (changed)
remote: -----> Installing Hex
remote: * creating /app/.mix/archives/hex-0.16.1
remote: -----> Installing rebar
remote: * creating /app/.mix/rebar
remote: * creating /app/.mix/rebar3
remote: -----> Fetching app dependencies with mix
remote: Running dependency resolution...
remote: Dependency resolution completed:
remote: connection 1.0.4
remote: cowboy 1.1.2
remote: cowlib 1.0.2
remote: db_connection 1.1.2
remote: decimal 1.4.0
remote: ecto 2.1.6
remote: gettext 0.13.1
remote: mime 1.1.0
remote: phoenix 1.3.0
remote: phoenix_ecto 3.2.3
remote: phoenix_html 2.10.4
remote: phoenix_pubsub 1.0.2
remote: plug 1.4.3
remote: poison 3.1.0
remote: poolboy 1.5.1
remote: postgrex 0.13.3
remote: ranch 1.3.2

(中略)

remote: sending incremental file list
remote: ./
remote:
remote: sent 63 bytes received 19 bytes 164.00 bytes/sec
remote: total size is 0 speedup is 0.00
remote: Running default compile
remote: 13:58:50 - info: compiled 6 files into 2 files, copied 3 in 2.4 sec
remote: Check your digested files at "priv/static"
remote: Clean complete for "priv/static"
remote: Caching assets
remote: sending incremental file list
remote: ./
remote: cache_manifest.json
remote: favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico
remote: favicon.ico
remote: robots-067185ba27a5d9139b10a759679045bf.txt
remote: robots-067185ba27a5d9139b10a759679045bf.txt.gz
remote: robots.txt
remote: robots.txt.gz
remote: css/
remote: css/app-833cc7e8eeed7a7953c5a02e28130dbd.css
remote: css/app-833cc7e8eeed7a7953c5a02e28130dbd.css.gz
remote: css/app.css
remote: css/app.css.gz
remote: images/
remote: images/phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png
remote: images/phoenix.png
remote: js/
remote: js/app-8f0317e89884de8b7b3a685928bee5e7.js
remote: js/app-8f0317e89884de8b7b3a685928bee5e7.js.gz
remote: js/app.js
remote: js/app.js.gz
remote:
remote: sent 372,139 bytes received 366 bytes 745,010.00 bytes/sec
remote: total size is 370,694 speedup is 1.00
remote:
remote: -----> Finalizing build
remote: Caching versions for future builds
remote: Creating runtime environment
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 86.7M
remote: -----> Launching...
remote: Released v6
remote: https://cryptic-waters-12345.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/cryptic-waters-12345.git
* [new branch] master -> master

Erlang 19.3 って出てますね。今最新版は20系だと思いますので、若干古いですね。

これで heroku にソースコードが転送されましたが、まだ migrate が終わってない状態ですので、

migrate します。


migrate

$ heroku run "POOL_SIZE=2 mix ecto.migrate"

Running POOL_SIZE=2 mix ecto.migrate on ⬢ cryptic-waters-12345... up, run.8159 (Free)

13:59:38.528 [info] == Running Blog.Repo.Migrations.CreateArticles.change/0 forward

13:59:38.530 [info] create table articles

13:59:38.595 [info] == Migrated in 0.0s

これでデプロイは完了です。ブラウザから https://cryptic-waters-12345.herokuapp.com/articles/ にアクセスすると、確かにアプリケーションが動いています!


ログの確認

さて、懸念だったログですが(-ttail -f みたいな意味です)、

$ heroku logs -t

2017-08-17T14:01:02.991565+00:00 app[web.1]: 14:01:02.988 request_id=05b00278-05e2-473b-8402-e3b479e416fe [info] GET /
2017-08-17T14:01:03.259420+00:00 heroku[router]: at=info method=GET path="/" host=cryptic-waters-12345.herokuapp.com request_id=05b00278-05e2-473b-8402-e3b47
9e416fe fwd="12.34.56.78" dyno=web.1 connect=0ms service=391ms status=200 bytes=2331 protocol=https
2017-08-17T14:01:03.260598+00:00 app[web.1]: 14:01:03.260 request_id=05b00278-05e2-473b-8402-e3b479e416fe [info] Sent 200 in 271ms
2017-08-17T14:01:04.394185+00:00 heroku[router]: at=info method=GET path="/css/app-833cc7e8eeed7a7953c5a02e28130dbd.css?vsn=d" host=cryptic-waters-12345.hero
kuapp.com request_id=be6993d1-1082-4a09-9cd3-6a4b9cdb839e fwd="12.34.56.78" dyno=web.1 connect=0ms service=48ms status=200 bytes=122822 protocol=https
2017-08-17T14:01:04.389238+00:00 heroku[router]: at=info method=GET path="/js/app-8f0317e89884de8b7b3a685928bee5e7.js?vsn=d" host=cryptic-waters-12345.heroku
app.com request_id=b4826012-39c2-4f42-a167-1eaf14efb6c1 fwd="12.34.56.78" dyno=web.1 connect=1ms service=42ms status=200 bytes=20753 protocol=https
2017-08-17T14:01:08.721150+00:00 heroku[router]: at=info method=GET path="/images/phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png?vsn=d" host=cryptic-waters-897
82.herokuapp.com request_id=0f5c2a7c-0294-4242-b682-d609e3143176 fwd="12.34.56.78" dyno=web.1 connect=0ms service=9ms status=200 bytes=14147 protocol=http
s
2017-08-17T14:01:09.467318+00:00 heroku[router]: at=info method=GET path="/favicon.ico" host=cryptic-waters-12345.herokuapp.com request_id=995ced3a-36a4-4f21
-a4c1-75b3a8d3afa8 fwd="12.34.56.78" dyno=web.1 connect=1ms service=5ms status=200 bytes=1516 protocol=https
2017-08-17T14:01:10.767205+00:00 app[web.1]: 14:01:10.766 request_id=bfaa0249-f9ef-4818-9f55-a35ffc407a88 [info] GET /articles/
2017-08-17T14:01:10.853987+00:00 app[web.1]: 14:01:10.853 request_id=bfaa0249-f9ef-4818-9f55-a35ffc407a88 [info] Sent 200 in 86ms
2017-08-17T14:01:10.853206+00:00 heroku[router]: at=info method=GET path="/articles/" host=cryptic-waters-12345.herokuapp.com request_id=bfaa0249-f9ef-4818-9
f55-a35ffc407a88 fwd="12.34.56.78" dyno=web.1 connect=1ms service=88ms status=200 bytes=1623 protocol=https
2017-08-17T14:01:13.050846+00:00 heroku[router]: at=info method=GET path="/articles/new" host=cryptic-waters-12345.herokuapp.com request_id=99e8a11d-8358-43d
5-a502-fef422812be9 fwd="12.34.56.78" dyno=web.1 connect=1ms service=60ms status=200 bytes=2316 protocol=https
2017-08-17T14:01:12.992630+00:00 app[web.1]: 14:01:12.992 request_id=99e8a11d-8358-43d5-a502-fef422812be9 [info] GET /articles/new
2017-08-17T14:01:13.047898+00:00 app[web.1]: 14:01:13.047 request_id=99e8a11d-8358-43d5-a502-fef422812be9 [info] Sent 200 in 55ms
2017-08-17T14:01:31.683374+00:00 app[web.1]: 14:01:31.677 request_id=0cd811cd-980f-40da-8b15-d6e72c2ba34c [info] POST /articles
2017-08-17T14:01:32.149167+00:00 app[web.1]: 14:01:32.146 request_id=0cd811cd-980f-40da-8b15-d6e72c2ba34c [info] Sent 302 in 466ms
2017-08-17T14:01:32.408685+00:00 app[web.1]: 14:01:32.403 request_id=2c56f9c5-339b-4515-ae4a-e11317c46248 [info] GET /articles/1
2017-08-17T14:01:32.479233+00:00 app[web.1]: 14:01:32.478 request_id=2c56f9c5-339b-4515-ae4a-e11317c46248 [info] Sent 200 in 74ms
2017-08-17T14:01:32.478391+00:00 heroku[router]: at=info method=GET path="/articles/1" host=cryptic-waters-12345.herokuapp.com request_id=2c56f9c5-339b-4515-
ae4a-e11317c46248 fwd="12.34.56.78" dyno=web.1 connect=0ms service=83ms status=200 bytes=1829 protocol=https

やはりちゃんと出ていますね。すばらしい。


まとめ


  • 公式ドキュメントにそって、Heroku へのデプロイをやってみました。

  • 全く問題なく動きましたが、一部コマンドが古いのでそこだけ気をつけましょう。

  • あと、ビルドパックの erlang vm が 19 系なので、20系で動かしたいときは自分でビルドパックをカスタマイズなどする必要があります。

  • ちょっとした Web アプリなら Heroku で動かすのが一番楽なんじゃないでしょうか。

次回は Distillery というのを試してみたいと思いますが、これの詳細は公式ドキュメントに

書いてないんですよね...