Phoenixのデプロイ
Erlang (Elixir) の機能の中で気になっているのが、ホットデプロイだ。といっても、そういう機能があるからといって気軽にデプロイできるものでもないらしいけど。
デプロイに関するスライド。
- http://sile.github.io/slide/kbkz_tech_05/#/
- http://www.slideshare.net/ohr486/shibuyaex-1-elixir?next_slideshow=1
デプロイ方法
Exrm
Exrm (Elixir Release Manager) を使うのがよさげ。 Phoenix 公式でも紹介されている。 設定方法などはそちらを参照。
パッケージ作成
MIX_ENV=prod mix release
で本番環境用のリリースパッケージが rel/hello_phoenix/releases
の下にバージョンごとに作成される。
起動
$ ./rel/hello_phoenix/bin/hello_phoenix start
停止するときは stop で。
更新
mix.ex に書かれているバージョンを上げ、MIX_ENV=prod mix release
でパッケージを作成する。
(ここでは バージョンをv0.0.1 からv0.0.2に上るとする。)
v0.0.1のアプリが稼働中の状態でv0.0.2に更新するには
rel/hello_phoenix/bin/hello_phoenix upgrade 0.0.2
で、OK。
自分の環境で試した限りでは、更新時に接続できないということはなかった。
mix phoenix.new のオプション
アプリケーション作成時のオプションに何があるか調べてみた(といっても、大して調べてないけれど)。
答えはここに。
- --app
- アプリケーション名?よくわかってない。
- --module
- モジュール名を指定する。phoenix.new したときのディレクトリ名とモジュール名を異なる名前にできる。
- --database
- PostgreSQL以外のDBを使うときに。
- --no-brunch
- brunchを使わないときに指定する。APIサーバとして使うならこれを指定した方がいいかも。
- --no-ecto
- Ectoを使用しない場合に使用。
Phoenix のアップデート
v0.17.0 がリリースされていたので更新してみる。フレームワーク(というか、Mixでインストールしたライブラリ)をアップデートするのは今回が初めて。
% mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.17.0/phoenix_new-0.17.0.ez Found existing archive(s): phoenix_new-0.16.1.ez. Are you sure you want to replace them? [Yn] y * creating .mix/archives/phoenix_new-0.17.0.ez
特別悩むことはなくアップデート完了。インストールと同じコマンドで大丈夫だった。
変更点は http://www.phoenixframework.org/blog/upgrading-from-v016-to-v017 に書かれている。
大きな変更はないけれど、個人的に良かったと思う点は、パラメータに %{"format" => "html"} が追加されなくなったところ。jsonしか返さないAPIサーバを作る場合とか邪魔だったので。
求めていたのはinspect/2だった
to_string/1 ではなくinspect/2 だった。
iex(1)> to_string %{a: 1, b: "hoyhoy"} ** (Protocol.UndefinedError) protocol String.Chars not implemented for %{a: 1, b: "hoyhoy"} (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1 (elixir) lib/string/chars.ex:17: String.Chars.to_string/1
iex(1)> inspect %{a: 1, b: "hoyhoy"} "%{a: 1, b: \"hoyhoy\"}"
Map には to_string が定義されていないので、どうすればログに出力できるのかを悩んでいたのだけれど、これで解決。
IEx.Pryでデバッグ
ここで
デバッガみたいなのはある?
と書いた。調べてみたところIEx.pryが使えることがわかった。
使い方
大雑把にいうとこんな感じ
- IEx モジュールを require する。
- ブレイクポイントコードに仕込む。
- iex でサーバを起動する
- リクエストを出す。
- ブレイクポイントに到達すると、pry を実行するかを尋ねられるので Y を入力する。
- respwan で処理を続行する。
コードはこんな感じ(ベースはここ)。
defmodule HelloPhoenix.PageController do use HelloPhoenix.Web, :controller require IEx def index(conn, _params) do IEx.pry # ブレイクポイント render conn, "index.html" end end
できること
- 変数の内容を表示させる
などなど(詳しく調べていない)
実行サンプル
localhost:4000/?foo=bar
にリクエストを出したところ。
% iex -S mix phoenix.server Erlang/OTP 18 [erts-7.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> 17 Aug 21:02:01 - info: compiled 5 files into 2 files, copied 3 in 2280ms Request to pry #PID<0.267.0> at web/controllers/page_controller.ex:7. Allow? [Yn] y Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help) pry(1)> _params %{"foo" => "bar", "format" => "html"} pry(2)> conn %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<1.90859823/1 in Plug.CSRFProtection.call/2>, #Function<4.77861751/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.121794808/1 in Plug.Session.before_send/2>, #Function<1.60260050/1 in Plug.Logger.call/2>, #Function<0.94118563/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>], body_params: %{}, cookies: %{}, halted: false, host: "localhost", method: "GET", owner: #PID<0.267.0>, params: %{"foo" => "bar", "format" => "html"}, path_info: [], peer: {{127, 0, 0, 1}, 36626}, port: 4000, private: %{HelloPhoenix.Router => {[], %{}}, :phoenix_action => :index, :phoenix_controller => HelloPhoenix.PageController, :phoenix_endpoint => HelloPhoenix.Endpoint, :phoenix_flash => %{}, :phoenix_layout => {HelloPhoenix.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_route => #Function<1.114667012/1 in HelloPhoenix.Router.match/4>, :phoenix_router => HelloPhoenix.Router, :phoenix_view => HelloPhoenix.PageView, :plug_session => %{}, :plug_session_fetch => :done}, query_params: %{"foo" => "bar"}, query_string: "foo=bar", remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [{"user-agent", "curl/7.35.0"}, {"host", "localhost:4000"}, {"accept", "*/*"}], request_path: "/", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "jcei0fi1smlj9c9npf323nfukor7p6eo"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil} pry(3)> respawn Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>
PhoenixのLogger
とりあえず、アプリが起動する状態になった。
Phoenix・Elixir をちょっとさわって思ったのは、コード上に型が現れてれていないとなにかと調べにくい、ということ。馴染みのある言語なら、型が現れてなくてもなんとかなっちゃうんだけど、馴染みのない言語だとそうはいかない。
結局コード見ててもわからないので、ログを出しまくって、どういう動きをするか、どういうデータが入力・出力されるのかを追っているところ(デバッガみたいなのはある?)。まあ、始めはログの出し方もわからなかったのだけれど。
Logger
debug, info, warn, error の4レベルがある。
使い方としてはこんな感じ(サンプルはここで作ったhello_phoenixプロジェクト)。
defmodule HelloPhoenix.PageController do use HelloPhoenix.Web, :controller require Logger def index(conn, _params) do Logger.error "Error!!!" Logger.warn "Warn!!" Logger.info "Info!" Logger.debug "Debug" render conn, "index.html" end end
ログの設定は config の下の設定ファイルで変えられる。
出力先はデフォルトで :console となっている。
ファイルに落とすにはリダイレクトするのかな...?と思って調べたら Backend
というものがあって、そこにファイルに直接ログを出力する拡張機能を追加すれば、リダイレクトは不要。標準ではファイルに落とす拡張機能は用意されていないが、logger_file_backendが公開されていたので使ってみる。
まずは mix.exs 依存関係を追加して、% mix deps.get
でダウンロードする。
--- mix.exs.orig 2015-08-15 15:12:58.149425240 +0900 +++ mix.exs 2015-08-15 15:13:18.074069599 +0900 @@ -31,6 +31,7 @@ [{:phoenix, "~> 0.16"}, {:phoenix_html, "~> 2.0"}, {:phoenix_live_reload, "~> 0.6", only: :dev}, + {:logger_file_backend, "~> 0.0.3"}, {:cowboy, "~> 1.0"}] end end
設定ファイルはこんな感じ。
--- config/dev.exs.orig 2015-08-15 15:08:00.514099611 +0900 +++ config/dev.exs 2015-08-15 15:10:06.139743337 +0900 @@ -24,4 +24,7 @@ ] # Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" +config :logger, backends: [{LoggerFileBackend, :file}] +config :logger, :file, + path: "/tmp/hello_phoenix.log", + level: :debug
結果
% cat /tmp/hello_phoenix.log 15:16:29.472 [info] Running HelloPhoenix.Endpoint with Cowboy on http://localhost:4000 15:16:49.496 [info] GET / 15:16:49.510 [debug] Processing by HelloPhoenix.PageController.index/2 Parameters: %{"format" => "html"} Pipelines: [:browser] 15:16:49.526 [info] Sent 200 in 29ms
Phoenixを始めてみた
Phoenix が気になったので、さわり始めた。
Phoenix は Elixir で書かれていて、アプリを作る場合も Elixir で書く。Phoenix もElixir も今まで使ったことがなく、初めての言語、初めてのフレームワークで、いろいろとつまづくことが多そう。その反面、面白いこともありそうなわけで。
とりあえず環境を整えよう。
環境構築
Erlang, Elixir, Phoenix をインストールする。
Elang, Elixir は anyenv を使ってインストールする。
今回インストールしたバージョンは
- Erlaang/OTP 18
- Elixir 1.0.5
- Phoenix 0.16.0
OS は Ubuntu 14.04.3。
*env
anyenv のインストールはREADME.md を参照。
% anyenv install erlenv % anyenv install exenv
Erlangのインストール
Erlang は erlang-build みたいなのがないので、自分でビルドする。
% curl -O http://www.erlang.org/download/otp_src_18.0.tar.gz % tar xzf otp_src_18.0.tar.gz % cd otp_src_18.0 % ./configure --prefix=/home/ymmtmsys/.anyenv/envs/erlenv/releases/18.0 % make -j4 % make install % erlenv global 18.0
ソースは消してOK.
Elixirのインストール
% exenv install -l # 最新バージョンの確認のため % exenv install 1.0.5 % exenv global 1.0.5
Phoenix のインストール
公式ドキュメントを参照
node.js はすでに入っているので省略。
Up And Running
ここ を参考に、サンプルのアプリが起動するか確認。
% mix phoenix.new hello_phoenix % hello_phoenix % mix phoenix.server . . . [info] Running HelloPhoenix.Endpoint with Cowboy on http://localhost:4000 [warning] the :size option when configuring HelloPhoenix.Repo is deprecated, please use :pool_size instead 14 Aug 11:03:56 - info: compiled 5 files into 2 files, copied 3 in 2675ms [info] GET / [debug] Processing by HelloPhoenix.PageController.index/2 Parameters: %{"format" => "html"} Pipelines: [:browser] [info] Sent 200 in 17ms
Ok.
warning は設定ファイルのフィールド名を変えれば出なくなる。
--- config/dev.exs.orig 2015-08-14 11:21:57.671526209 +0900 +++ config/dev.exs 2015-08-14 11:22:01.059526209 +0900 @@ -32,4 +32,4 @@ username: "postgres", password: "postgres", database: "hello_phoenix_dev", - size: 10 # The amount of database connections in the pool + pool_size: 10 # The amount of database connections in the pool