Help us understand the problem. What is going on with this article?

Phoenix Production デプロイへの道その1:生Phoenixを手元で動かす編

More than 1 year has passed since last update.

前回の記事 で、アプリケーションが作れそうな実感は得られたので、
今度はデプロイできる実感を得たいと思い、デプロイの手順を試してみました。

結構紆余曲折しましたので、急いで手順だけ知りたい方は、一番最後のまとめだけ読んでください。

あとですね...Elixir/ErlangにはMacでビルドしたやつはLinuxで動かないという問題があると噂に聞いていますので、手元で動くようになったとしても本番サーバーで動かせるようになるまではまだまだ遠いかもしれません。

公式ドキュメントを読んでみる

https://hexdocs.pm/phoenix/deployment.html

デプロイするには3つのステップがある。

  • Application secrets の処理
  • Assets のコンパイル
  • プロダクションモードでサーバーの起動

そして、これらがどのように行われるかは、インフラによって異なり、

  • Heroku を使う場合
  • Heroku を使わないなら Distillery を使うのをオススメ

の 2 つの方法が主にオススメされているようです。

まずは上記の 3 つのステップについて、もう少しドキュメントを
読んで手元で試してみましょう。

Application secrets の扱い

これは、DBのユーザー名やパスワードなどのことですね。

Phoenix ではこれらは config/prod.secret.exs というファイルに
書くことになっていて、git には入れないみたいです。

ちょっと中身を見てみましょう。

/blog/config/prod.secret.exs
use Mix.Config

# In this file, we keep production configuration that
# you'll likely want to automate and keep away from
# your version control system.
#
# You should document the content of this
# file or create a script for recreating it, since it's
# kept out of version control and might be hard to recover
# or recreate for your teammates (or yourself later on).
config :blog, BlogWeb.Endpoint,
  secret_key_base: "hogehogefugafuga+hogehogefugafuga+jugemjugemgokounosurikire"

# Configure your database
config :blog, Blog.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "hogehoge",
  password: "fugafuga",
  database: "blog_prod",
  pool_size: 15

Rails の database.yml とか secrets.yml 的なやつですね。

で、この情報をどうにかして production 環境に配置する必要があるが、

  • Heroku でやるように環境変数経由で渡す方法
  • デプロイで上書きされない領域 (/var/config.prod.exs など)に配置して、このパスから読み込むようにする方法(Capistrano の shared みたいなイメージですかね?)

などが考えられる、と。

Assets のコンパイル

Phoenix ではデフォルトでは brunch を使うよ、と。
なんでしょう?初耳なんですけど。 サイトを見ると、gulp と比較してるようなので、
gulp みたいなやつなんでしょう、とわかったつもりになったことにして、次へ。

で、ビルドはこうする、と。

# 必要なライブラリを入れて
$ mix deps.get --only prod

# コンパイルして
$ MIX_ENV=prod mix compile

# assets のコンパイルして
$ brunch build --production

# assets のファイル名にダイジェストを付与する
$ MIX_ENV=prod mix phoenix.digest

やってみましょう。

$ mix deps.get --only prod
Running dependency resolution...
Dependency resolution completed:
  connection 1.0.4
  cowboy 1.1.2
  cowlib 1.0.2
  db_connection 1.1.2
  decimal 1.4.0
  ecto 2.1.6
  gettext 0.13.1
  mime 1.1.0
  phoenix 1.3.0
  phoenix_ecto 3.2.3
  phoenix_html 2.10.4
  phoenix_pubsub 1.0.2
  plug 1.4.3
  poison 3.1.0
  poolboy 1.5.1
  postgrex 0.13.3
  ranch 1.3.2
All dependencies up to date

ふむ。

$ MIX_ENV=prod mix compile
==> connection
Compiling 1 file (.ex)
Generated connection app
==> gettext
Compiling 1 file (.erl)
Compiling 20 files (.ex)
warning: String.strip/1 is deprecated, use String.trim/1
  lib/gettext/po/parser.ex:47

warning: String.strip/1 is deprecated, use String.trim/1
  lib/gettext/po/parser.ex:55

warning: String.lstrip/1 is deprecated, use String.trim_leading/1
  lib/gettext/po/parser.ex:65

warning: String.to_char_list/1 is deprecated, use String.to_charlist/1
  lib/gettext/po/tokenizer.ex:147

Generated gettext app
===> Compiling ranch
===> Compiling poolboy
==> decimal
Compiling 1 file (.ex)
warning: Integer.to_char_list/1 is deprecated, use Integer.to_charlist/1
  lib/decimal.ex:944

warning: Integer.to_char_list/1 is deprecated, use Integer.to_charlist/1
  lib/decimal.ex:963

warning: Integer.to_char_list/1 is deprecated, use Integer.to_charlist/1
  lib/decimal.ex:986

Generated decimal app
warning: String.strip/1 is deprecated, use String.trim/1
  /Users/tmaeda/src/github.com/tmaeda/phenix-tut/blog/deps/poison/mix.exs:4

==> poison
Compiling 4 files (.ex)
warning: Integer.to_char_list/2 is deprecated, use Integer.to_charlist/2
  lib/poison/encoder.ex:173

Generated poison app
==> db_connection
Compiling 23 files (.ex)
Generated db_connection app
==> phoenix_pubsub
Compiling 12 files (.ex)
Generated phoenix_pubsub app
===> Compiling cowlib
src/cow_multipart.erl:392: Warning: crypto:rand_bytes/1 is deprecated and will be removed in a future release; use crypto:strong_rand_bytes/1

===> Compiling cowboy
==> mime
Compiling 1 file (.ex)
warning: String.strip/1 is deprecated, use String.trim/1
  lib/mime.ex:28

Generated mime app
==> plug
Compiling 1 file (.erl)
Compiling 44 files (.ex)
Generated plug app
==> phoenix_html
Compiling 8 files (.ex)
Generated phoenix_html app
==> phoenix
Compiling 74 files (.ex)
Generated phoenix app
==> postgrex
Compiling 62 files (.ex)
Generated postgrex app
==> ecto
Compiling 69 files (.ex)
Generated ecto app
==> phoenix_ecto
Compiling 4 files (.ex)
Generated phoenix_ecto app
==> blog
Compiling 17 files (.ex)
Generated blog app

Generated ほにゃらら app ってのがいっぱい出てますね。
いわゆるライブラリがひとつの小さなアプリケーションという考え方なんですかね?

$ brunch build --production
-bash: brunch: コマンドが見つかりません

なるほどw brew でもダメだったんで、仕方なくサイトを読んで、
npm で入れるらしいことがわかりました。が、yarn 推しなので yarn で入れますよ。

$ yarn global add brunch
yarn global v0.24.6
warning No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "0.27.5" while you're on "0.24.6".
success Installed "brunch@2.10.10" with binaries:
      - brunch
warning No license field
✨  Done in 11.45s.

気を取り直して。

$ brunch build --production
15:34:07 - error: The directory does not seem to be a Brunch project. Create brunch-config.js or run brunch from the correct directory. Cannot find module '/blog/brunch-config'
15:34:07 - error: Here's a minimal brunch-config.js to get you started:

module.exports = {
  files: {
    javascripts: {
      joinTo: 'app.js'
    }
  }
};

はいいい???

どこかに config あるんですかねぇ?

$ find . -name brunch-config.js
./assets/brunch-config.js
./deps/phoenix/brunch-config.js

ほほーう。じゃ、assets の下でやってみますか。

$ cd assets
$ brunch build --production
15:36:59 - info: compiled 6 files into 2 files, copied 3 in 2.6 sec

お、なんかできたっぽいですね。次。

$ MIX_ENV=prod mix phoenix.digest
** (Mix) The task "phoenix.digest" could not be found. Did you mean "phoenix.new"?

はいいいい???ディレクトリが違うんですかね?

$ cd ..
$ MIX_ENV=prod mix phoenix.digest
mix phoenix.digest is deprecated. Use phx.digest instead.
Check your digested files at "priv/static"

できたけど、deprecated だと。

$ MIX_ENV=prod mix phx.digest
Check your digested files at "priv/static"

ふぅ、やっと辿り着いた。 Check しろっていうんで、Check しますか。

$ ls -1 priv/static/
cache_manifest.json
css
favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico
favicon.ico
images
js
robots-067185ba27a5d9139b10a759679045bf.txt
robots-067185ba27a5d9139b10a759679045bf.txt.gz
robots.txt
robots.txt.gz

ふむふむ、なるほど。digest つける作業って、 brunch でやりそうな感じがしますけど、
mix でやるんですねぇ。

サーバーの起動

$ PORT=4001 MIX_ENV=prod mix phoenix.server

ですが、必要に応じて、この前に

$ MIX_ENV=prod mix ecto.migrate

もやらないといけない、と。

やってみましょう。

$ MIX_ENV=prod mix ecto.create
The database for Blog.Repo has been created

$ MIX_ENV=prod mix ecto.migrate

15:45:23.165 [info]  == Running Blog.Repo.Migrations.CreateArticles.change/0 forward

15:45:23.165 [info]  create table articles

15:45:23.171 [info]  == Migrated in 0.0s

で、

$ PORT=4001 MIX_ENV=prod mix phoenix.server
mix phoenix.server is deprecated. Use phx.server instead.
15:46:06.683 [info] Running BlogWeb.Endpoint with Cowboy using http://:::4001

ん?デーモンモードじゃないの???しかも、また deprecated って出てるし...

$ PORT=4001 MIX_ENV=prod mix phx.server
15:47:55.711 [info] Running BlogWeb.Endpoint with Cowboy using http://:::4001

んー、おんなじですねぇ...

一応、この状態で、ブラウザからアクセスしてみますか。

プロダクションモードでの Article 一覧

おー、出た出た。まぁ、当然ですけどね。心なしか、development モードより、
キビキビ動くような気がします。

ログはこんな感じ。

15:48:27.978 request_id=4jddi0sv4o928pnv33dq813r88cu1eht [info] GET /
15:48:28.023 request_id=4jddi0sv4o928pnv33dq813r88cu1eht [info] Sent 200 in 44ms
15:48:34.577 request_id=3drdgeaa3udj8uglvkhskb1sf0tfcrp8 [info] GET /articles
15:48:34.626 request_id=3drdgeaa3udj8uglvkhskb1sf0tfcrp8 [info] Sent 200 in 48ms
15:48:36.313 request_id=nohsc54o07271kmabhoppupuoj8sketn [info] GET /articles/new
15:48:36.361 request_id=nohsc54o07271kmabhoppupuoj8sketn [info] Sent 200 in 48ms
15:49:17.992 request_id=7i7esq0kfsn75us520gs62of0rkk3s4q [info] GET /articles
15:49:18.007 request_id=7i7esq0kfsn75us520gs62of0rkk3s4q [info] Sent 200 in 14ms
15:49:40.925 request_id=3ljqp8ke710rkj0hvc0ejk7k766k1lhi [info] GET /articles/new
15:49:40.926 request_id=3ljqp8ke710rkj0hvc0ejk7k766k1lhi [info] Sent 200 in 548µs
15:49:44.194 request_id=bslu70c0k8o8tp3ia96gu9iqru4ja59f [info] POST /articles
15:49:44.276 request_id=bslu70c0k8o8tp3ia96gu9iqru4ja59f [info] Sent 302 in 81ms
15:49:44.302 request_id=voludfcpgkl3u1f7gvntgqpf0509m6b7 [info] GET /articles/1
15:49:44.359 request_id=voludfcpgkl3u1f7gvntgqpf0509m6b7 [info] Sent 200 in 56ms

SQLとか全くでないんですね。噂に聞く「マイクロ秒」ってのがチラホラ出てますね。

エラーページはこんな感じ。当然、スタックトレースなどは出なくなってます。

プロダクションモードでのエラー画面

開発者コンソールのNetworkタブはこんな感じ。

プロダクションモードでのNetworkタブ

websocket が居ないですね。やはりあれは livereload 専用だったのか...

さて、デーモンモードの話に戻ります。

公式ドキュメントには別のコマンドも載っているので、そっちも試してみましょう。

$ PORT=4001 MIX_ENV=prod iex -S mix phx.server
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

17:17:44.362 [info] Running BlogWeb.Endpoint with Cowboy using http://:::4001
Interactive Elixir (1.5.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 17:18:40.549 request_id=1rbg8400df6nrk1jnokvoqq7hhua54sn [info] GET /
17:18:40.601 request_id=1rbg8400df6nrk1jnokvoqq7hhua54sn [info] Sent 200 in 52ms

nil
iex(2)> 17:18:56.882 request_id=cg6384a3pdm9gkn1vthouvfh2vd3nc0l [info] GET /articles
17:18:56.941 request_id=cg6384a3pdm9gkn1vthouvfh2vd3nc0l [info] Sent 200 in 59ms

nil
iex(3)>

おやぁ???何ですか、これはぁ? rails s と rails c を合わせたような...使い途がよくわかりません。

次、

Or run it detached from the iex console. This effectively daemonizes the process so it can run independently in the background

MIX_ENV=prod PORT=4001 elixir --detached -S mix do compile, phoenix.server

って書いてるので、これがデーモンなんでしょうね。しかし、このコマンド、絶対に違うような気がするので、気を利かせて以下のように実行してみます。

$ PORT=4001 MIX_ENV=prod elixir --detached -S mix phx.server

あー、動きました。動きましたけど、ロ、、ログファイルは、、、

findしてもそれっぽいものが見つからないので、ググると、

https://elixirforum.com/t/where-to-find-log-file-in-phoenix-detached-mode/3622

logger_file_backend を使え、と。なるほどー。うーん...

--detached というのは VM を console から切り離した状態で動かすよ、ということらしいです。

$ elixir --help
Usage: elixir [options] [.exs file] [data]

  -e COMMAND                  Evaluates the given command (*)
  -r FILE                     Requires the given files/patterns (*)
  -S SCRIPT                   Finds and executes the given script in PATH
  -pr FILE                    Requires the given files/patterns in parallel (*)
  -pa PATH                    Prepends the given path to Erlang code path (*)
  -pz PATH                    Appends the given path to Erlang code path (*)

  --app APP                   Starts the given app and its dependencies (*)
  --cookie COOKIE             Sets a cookie for this distributed node
  --detached                  Starts the Erlang VM detached from console # 👈
  --erl SWITCHES              Switches to be passed down to Erlang (*)
  --help, -h                  Prints this message and exits
  --hidden                    Makes a hidden node
  --logger-otp-reports BOOL   Enables or disables OTP reporting
  --logger-sasl-reports BOOL  Enables or disables SASL reporting
  --name NAME                 Makes and assigns a name to the distributed node
  --no-halt                   Does not halt the Erlang VM after execution
  --sname NAME                Makes and assigns a short name to the distributed node
  --version, -v               Prints Elixir version and exits
  --werl                      Uses Erlang's Windows shell GUI (Windows only)

ログについては以下のような方法が思い浮かびますが...

まとめ

だいぶ紆余曲折したので、もう一度手順まとめますね。

# 事前準備
$ yarn global add brunch
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile

$ cd assets
$ brunch build --production

$ cd ..
$ MIX_ENV=prod mix phx.digest

$ MIX_ENV=prod mix ecto.create
$ MIX_ENV=prod mix ecto.migrate

$ PORT=4001 MIX_ENV=prod mix phx.server
  • プロダクションで動かす手順がわかりました。
  • 公式ドキュメントにはだいぶ地雷があります。あとで時間ができたらプルリクエストの状況確認しておきます。
  • ログの扱いなど、いろいろ考えることがありそう...
    • この辺、Heroku は結構うまくやってくれそうな気がしているので、 次回は Heroku でお会いしましょう
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away