Elixir - DistilleryによるHot Code Swapping

こんにちは、エンジニアの渡部です。

Erlang/Elixirの特徴として分散処理と同じくらいよく目にする機能が、サーバーを稼働させたまま更新するホットコードスワッピングがあります。

この機能はErlang VMがコードのバージョンを2つまで保てることを利用したものです。

サーバープログラムの更新時にダウンタイムなしにリロードを行う方法はいくつかあると思いますが(ブルーグリーンデプロイメントなど)、言語環境に組み込んであるのはすごいですね!

しかし、Erlang作者が著者の入門本(通称:飛行機本)にも、Elixirの入門本プログラミングElixirにもほとんどホットコードスワッピングの説明がなく、ちゃんとしたやり方がいまいちつかめませんでした。

今回は、ElixirのDistilleryというモジュールがホットコードスワッピングさせることができるという情報が記されていたので、そちらを試してみました。

Distillery

Elixirでは将来的にmixに統合されるデプロイツールDistilleryというモジュールがあります。

事前準備

$ mix new hoge
$ cd hoge
$ vim mix.exs
# ...
# 依存関係にdistilleryを追加
# ...
$ mix deps.get
$ mix release.init                       # ・・・ リリース用のconfigファイルを生成(rel/config.exs)
$ MIX_ENV=prod mix release --env=prod    # ・・・ 本番環境でリリースパッケージの作成(①)

①について、--env=prodはconfigファイルのどの部分を使うかを指定しています。

MIX_ENV=prodは、どの開発環境時のモジュールを引っ張ってこないのと、コンパイルされたファイルの最適化を行うということを伝えているようで、それぞれ指定しておいた方が良さそうです。

最初のリリース

①まで実行すると、 _build/prod/rel/hoge/releases/0.1.0にhoge.tar.gzというリリース用のtarballが生成されています。

あとは、tarballをデプロイ対象のサーバーに転送して、対象サーバーのコンソールで

$ mkdir deployment && cd deployment
$ tar xvzf hoge.tar.gz
$ ./bin/hoge start

を実行すると最初のリリースが完了です!

ps aux | grep erlを実行すると、ErlangのOSプロセスが起動していることがわかります。

本番用のREPL(iex)の起動も

$ ./bin/hoge remote_console

で起動できるようです。終了はCtrl-C

何これ超便利・・・。

試しに、Hogeモジュールの関数をリストしてみました。

iex(hoge@127.0.0.1)1> Hoge.__info__(:functions)
[]

何も定義していないので空のリストになりましたね。

2回目以降のリリース

2回目以降のリリースを行いたいので、バージョンの変更とコードを適当に変更します。

今回は、lib/hoge.exにhelloという関数を追加しました。

defmodule Hoge do
  def hello do
    IO.puts "Hello, world!"
  end
end

次にバージョンの変更はmix.exsを開いてバージョンを書き換えます。

def project do
  [app: :hoge,
   version: "0.1.1",    # 0.1.0から0.1.1にアップグレード!
   elixir: "~> 1.3",
   build_embedded: Mix.env == :prod,
   start_permanent: Mix.env == :prod,
   deps: deps()]
end

次に更新用のリリースパッケージを生成します。最初のリリースと若干コマンドが異なります。

$ MIX_ENV=prod mix release --env=prod --upgrade    # 更新用パッケージの生成

このコマンドを実行する最初のリリースのときと同じように、 _build/prod/rel/hoge/releases/0.1.1にhoge.tar.gzというリリース用のtarballが生成されています。

このtarballをデプロイ対象のサーバーのdeployment/releases/0.1.1に転送します。

(0.1.1というディレクトリは存在しないのであらかじめ作っておく必要があるみたいです)

転送が終わったら、「最初のインストール」と同様に対象サーバーのコンソールで

$ cd deployment
$ ./bin/hoge upgrade 0.1.1

とするとアップグレード完了です。。

ここまで実行しておいて、ふとした疑問が頭をもたげます。

「簡単すぎる・・・、本当にちゃんとアップグレードできているのか・・・?」

$ ./bin/hoge remote_console
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(hoge@127.0.0.1)1> Hoge.__info__(:functions)
[hello: 0]
iex(hoge@127.0.0.1)1> Hoge.hello
Hello, world!
:ok

おお~。

Distilleryドキュメントによるとアップグレード時に自動的にホットコードスワッピングが行われるようです。

Phoenixアプリについて

時間ぎりぎりで気づいてしまったので、記事にできませんでしたが、

PhoenixアプリもDistilleryの設定を少しだけスタンダードなものから変更したら適用することができました。

詳しくはこちらを参照してください。

Phoenixの場合、dev環境のリリースパッケージを作るにはcode_reloaderをオフにする必要があるようです。

その他

今回は手動でtarballをリモートサーバーに転送していましたが、 「edeliver」という、Erlangのデプロイツール「deliver」を元にしたデプロイツールがあるようです。

RailsでいうとCapistranoに当たるツールですね。

こちらを利用すると分散したノードでもまとめて面倒を見てくれそうです。

リリースパッケージの作成には、いくつか選択肢があるようでDistilleryも使えるようです。

時間があるときにまたまとめたいと思います。

参考になりそうな記事がありましたのでリンクしておきます。

How to Set up a Distributed Elixir Cluster on Amazon EC2 · Pivotal Engineering Journal

感想

Erlangではホットコードスワッピングするときに、アップグレードパッケージにrelupというファイルを含める必要があるようですが、

公式ドキュメントを読む感じだとrelup作成には色々と事前準備と知識が必要なようで大変そうでした。

特に意識はしていませんが、ホットコードスワッピング周りはコード内のOTPが活躍しているようです。

ElixirのホットコードスワッピングはDistilleryがリリースパッケージの作成を自動化してくれるのでとても簡単ですね。