ちょっとRubyの中を覗いてみよう

久保田です。

ある程度プログラムを書いていると、ふと、これはなぜ動いているんだろう。。。ただの文字列じゃないか。。。と思うことがあると思います。

というわけで今回はちょっとだけ、Rubyがどのように動いているかを覗き見してみます。
(Rubyがどういった規則で構文解析をしているかなどには触れません。あくまでYARVに渡されるまでの流れを追っていきます。)

例えば以下のようなRubyのプログラムです。

  • foo.rb
result = 1 + 2
puts result

誰でも3が出力されるとわかるようなプログラムですが、$ ruby foo.rb と動かし、3を出力する間に何が起きているでしょう。

実はRubyでは、渡されたプログラムをそのまま実行しているのではなく、 Rubyというプログラムが理解できる形に変換してから実行しています。

そして変換は、字句解析 -> 構文解析 -> コンパイル という3つのステップを経て変換されていきます。

今回はこの3つのステップを少しずつ追っていきたいと思います。

字句解析

まずは字句解析です。
字句解析はただの文字列であるプログラムを トークン という意味のある単語に分ける仕事をします。
foo.rbの一行目でいうと、
resultという変数 / = という演算子 / 1という数字 / +という演算子 / 2という数字 に分けていきます。
(空白や改行などもトークンとして認識しますが、一旦無視します。)

それでは、実際にfoo.rbを字句解析してみましょう。
Rubyには標準ライブラリとしてRipperというものがあり、Ripperを使うと字句解析や構文解析の結果を渡してくれます。

require 'ripper'
require 'pp'

code =<<EOF
result = 1 + 2
puts result
EOF

pp Ripper.lex(code)

結果

[[[1, 0], :on_ident, "result"],
 [[1, 6], :on_sp, " "],
 [[1, 7], :on_op, "="],
 [[1, 8], :on_sp, " "],
 [[1, 9], :on_int, "1"],
 [[1, 10], :on_sp, " "],
 [[1, 11], :on_op, "+"],
 [[1, 12], :on_sp, " "],
 [[1, 13], :on_int, "2"],
 [[1, 14], :on_nl, "\n"],
 [[2, 0], :on_ident, "puts"],
 [[2, 4], :on_sp, " "],
 [[2, 5], :on_ident, "result"],
 [[2, 11], :on_nl, "\n"]]

配列の入れ子でプログラムが トークン に分けられていますね。
一つ目の配列を見て、構成を見てみます。

[[1, 0], :on_ident, "result"]

配列の最小の要素に、プログラム中の[何行目, 何文字目]かが格納されています。
次に、このトークンの種類が入っています。
最後の要素には実際に使われている文字列が入っています。

字句解析はこのように行なわれて、意味のある文字列に分けられているようですね。
ただこの状態では、まだ文としては意味を持っていません。
なので、字句解析の後、Rubyはこの分けられたトークンを元に構文解析を行います。

構文解析

構文解析では、意味のある文字に分けられたRubyプログラムを意味のある文にグループ化し、ASTノードにしていきます。つまり、 パース をします。
パースは、パーサジェネレータを使って行います。Rubyではbisonを使ってパーサジェネレータを作成します。

早速どのような動作をするか確認してみます。

require 'ripper'
require 'pp'

code =<<EOF
result = 1 + 2
puts result
EOF

pp Ripper.sexp(code)

先ほどの字句解析のRipper.lexをRipper.sexpに変更しただけです。

結果

[:program,
 [[:assign,
   [:var_field, [:@ident, "result", [1, 0]]],
   [:binary, [:@int, "1", [1, 9]], :+, [:@int, "2", [1, 13]]]],
  [:command,
   [:@ident, "puts", [2, 0]],
   [:args_add_block, [[:var_ref, [:@ident, "result", [2, 5]]]], false]]]]

先程よりはちょっと複雑な入れ子の配列になっています。
これがASTノードと呼ばれる構造です。
一部を抜き出すと、こういった構造ですね。

f:id:AdwaysEngineerBlog:20170317135428p:plain

少しだけ見ていきます。
まず、配列の最初の要素に:programと書かれています。始まるよー!的な意味ですね。
そして同じ階層のもう一つの要素は配列になっており、その配列の要素は二つありどちらも配列です。
その配列の構造も[シンボル: [配列]]となっているようです。

ここではASTに関しては詳しくは述べませんが、
それぞれの配列の最初のシンボルになっている要素が グループ化された文の種類 を表し、後に続く要素が配列の場合はさらにグループ化されています。 そして続く要素が配列でなくなった時に解析が終了しています。

このように 意味のある文字列(トークン) 意味のある文(ASTノード) にしています。
ここまで来てやっと、Rubyはどのようにただの文字列であるプログラムをどのように扱っていいかがわかるというわけです。

実は、このRuby1.9以前はこの状態まででプログラムの変換は終了だったそうです。
しかしこのままでは実行速度に問題あったらしく、Ruby1.9から笹田耕一さんにより開発されたYARVというRubyの仮想マシンが組みこまれました。

そのYARVがプログラムを実行するため、先ほどの構文木をコンパイルし、YARVが実行できる形式に変換します。

コンパイル

3つのステップの最後、コンパイルです。
早速やってみましょう。

code =<<EOF
result = 1 + 2
puts result
EOF

puts RubyVM::InstructionSequence.compile(code).disasm

結果

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] result
0000 trace            1                                               (   1)
0002 putobject_OP_INT2FIX_O_1_C_
0003 putobject        2
0005 opt_plus         <callinfo!mid:+, argc:1, ARGS_SKIP>
0007 setlocal_OP__WC__0 2
0009 trace            1                                               (   2)
0011 putself
0012 getlocal_OP__WC__0 2
0014 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0016 leave

さぁ、ついに意味がわからなくなってきました。笑
正直YARVの処理はまだあまり理解していません。。。
YARVの話だけで一ヶ月ブログ書けるくらいのボリュームだと思っています 。。

かろうじてわかるのは、
0007 setlocal_OP__WC__0 2
で変数をセットして、
0012 getlocal_OP__WC__0 2
で呼び出し、
0014 opt_send_simple <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
でputsメソッドを呼び出しているのだろう、ということくらいですね。笑

数字の後に続いているのがYARV命令というやつで、YARVが何をするかはここで決めています。

ここら辺の話はまた今度できたらな、と思います。

まとめ

Rubyは、プログラムを渡されると実行までに上記の3ステップの長〜い道のりを行くんですね。
いつも何気なく動かしているプログラムですが、ものすごく高度な技術が折り重なってできているんですね。
皆さんも是非自分のプログラムを覗き見してみてください。

参考文献

CUIで動くプログレスバーを作ろう

久保田です。

今日は小ネタです。

エンジニアの方々は、おそらく毎日コマンドラインを使っていると思います。
そして、コマンドラインを使っているなら、このような表示をwgetやdockerなど様々なところで見かけると思います。

f:id:AdwaysEngineerBlog:20170310170213g:plain

僕はエンジニアになりたての頃、このプログレスバーがどのように作られているかふわっと疑問に思っていました。
普通にコンソールにログを出したらこうはならないじゃないか、と
一行ずつ表示しちゃうじゃないか、と

でもある日 キャリッジリターン だと気がつきました。
通常改行時に\nとセットで使う\r、これを使います。

\rだけで使用すると、カーソル行頭復帰するので、
出力 -> キャリッジリターン -> 出力 -> キャリッジリターン -> 出力 を繰り返せばよかったのです。

歴史的には、以下のような感じらしいです。(wikipediaより)

キャリッジ・リターン(carriage return)は本来、テレタイプ端末の Baudot Code における制御文字を指す用語で、行末から行頭に戻す復帰コードであって、改行コードを含まない。その後、タイプライターで一行打ち込んだ後で紙を固定するシリンダー(キャリッジ)を次の行の先頭にタイプできるように戻し(リターン)改行する機構(またはその機構を操作するレバー)を「キャリッジ・リターン」と呼んだ。

というわけで、goで実装しました。

今回は例なので、
- 100%になるまでランダムに計算を繰り返し、%に合うだけ=を出力 & キャリッジリターンする。
- 今回はランダムで数字を与え続け、30になるまで繰り返す。
という流れです。

package main

import (
    "fmt"
    "math/rand"
    "strings"
    "time"
)

type ProgressBar struct {
    max         int
    progress int
    percent   float64
}

func main() {
        // 乱数生成器作成
    rand.Seed(time.Now().UnixNano())
        // プログレスバーを表す構造体
    progressBar := ProgressBar{max: 30, progress: 0}

    for {
        // 加算処理
        randNum := rand.Intn(10)
        progressBar.progress += int(randNum)

         // max値を超えないようにする
        if progressBar.progress > progressBar.max {
            progressBar.progress -= (progressBar.progress - progressBar.max)
        }

        // プログレスのパーセンテージ
        progressBar.percent = (float64(progressBar.progress) / float64(progressBar.max)) * 100
                // いくつ=を表示するかを決めるため、intに変換
        roundPercent := int(progressBar.percent)
                // ===> のように >付きで表示したいので、-1する。
        if roundPercent > 0 {
            roundPercent -= 1
        }
     // .も合わせて最大5桁、小数点は1桁で出力。
        fmt.Print(fmt.Sprintf("%5.1f%% [%s>%s] %d \r", progressBar.percent, strings.Repeat("=", roundPercent), strings.Repeat(" ", 99-roundPercent), progressBar.progress))

        if progressBar.progress == progressBar.max {
            fmt.Println("")
            break
        }

        time.Sleep(1 * time.Second)
    }
}

こんな感じです。

f:id:AdwaysEngineerBlog:20170310170033g:plain

今回は簡単な例ですが、 このような出力の仕組みを利用すると、CUIで人を驚かせられるような面白い遊びができるので、ぜひ試してみてください。
(表示上は消えているように見せたり、動いているように見せたりできます。)

mackerel-agent-pluginsにコントリビュートした話

久保田です。

最近、mackerel-agent-pluginsにプラグインを作り、コントリビュートしました。

人生初のossへのコントリビュートだったので、その話を忘れないうちに書いておきます。

今回、僕はGoogle Cloud PlatformのCompute Engineに入れることでcustom metric を収集することのできるプラグインを書きました。

すでに存在していた、mackerel-plugin-aws-ec2のGCPバージョン、という位置付けで作りました。

僕がgcpを使っているということは正直なく、ただOSSに貢献したかったから作った、というものになります。

そして無事マージしていただきました。mackerel-agent-pluginsの方々、ありがとうございます。mm

github.com

というわけでマージまで至った流れと反省をつらつらと書いていきたいと思います。

大まかな流れ

マージまでの流れは以下のような感じでした。

  • 実装
  • READMEを書く
  • CIでテスト
  • プルリク
  • レビューしていただく
  • マージ

実装

今回、goで何かを作るのも初めてだったため、実装の前にいろいろな方のpluginを参考にしました。

mackerel-agent-pluginを作る時は、だいたい以下のような構成になります。 (mackerel-agent-plugin-gcp-compute-engineの場合)

|----- README.md
|----- main.go
|----- lib
     |------ gcp-compute-engine.go
     |------ gcp-compute-engine_test.go

かなり端折っていますが、パッケージの実装の構成は以下のような感じです。

  • gcp-compute-engine.go
package gcpce // パッケージ名を決める

import (
  mp "github.com/mackerelio/go-mackerel-plugin-helper"
   // importするパッケージを指定
)


// プラグインで使用する構造体。
// この構造体に必要なメソッドを作っていく。
type ComputeEnginePlugin struct {
    Project           string
    InstanceName      string
    MonitoringService *monitoring.Service
    Option            *Option
    Tempfile          string
}

// graphの定義をmap[string]に入れていく。
// それぞれが1つのグラフになる。
var graphdef = map[string]mp.Graphs{
    "Firewall.DroppedBytesCount": mp.Graphs{
        Label: "FireWall Dropped Bytes Count",
        Unit:  "bytes",  // 型の指定
        Metrics: []mp.Metrics{
            {Name: "dropped_bytes_count", Label: "Dropped Bytes Count", Type: "uint64"},
        },
    },
    "Firewall.DroppedPacketsCount": mp.Graphs{
        Label: "FireWall Dropped Packets Count",
        Unit:  "integer",
        Metrics: []mp.Metrics{
            {Name: "dropped_packets_count", Label: "Dropped Packets Count", Type: "uint64"},
        },
    }, 

        ....
}

// グラフの定義を返す構造体のメソッド。
// グラフを動的に変化させたい場合はここで処理する。
func (p ComputeEnginePlugin) GraphDefinition() map[string]mp.Graphs {
    return graphdef
}

// 最新の値を取得する関数。
// gcp-compute-engineの場合は、gcpのAPIコールをして値を取得する。
func getLatestValue() (interface{}, error) {
       ....
}

// mackerelに送信する値を取得し、まとめるメソッド。
// getLatestValueを必要に応じて呼び出し、メトリックを集める。
func (p ComputeEnginePlugin) FetchMetrics() (map[string]interface{}, error) {
       ....
}

// pluginのメイン処理。
func Do() {
    // コマンドライン引数を処理したり、メタデータを集める。
        ....

    var computeEngine = ComputeEnginePlugin{
        MonitoringService: service,
        Project:           "projects/" + projectID,
        InstanceName:      instanceName,
        Option:            &Option{Key: *optAPIKey},
    }

    // 構造体をmackerel-agent-plugin-helperに渡す。
    helper := mp.NewMackerelPlugin(computeEngine)

    if *optTempfile != "" {
        helper.Tempfile = *optTempfile
    } else {
        helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-gcp-compute-engine-%s", computeEngine.InstanceName)
    }


    helper.Run() // 渡した構造体のFetchMetricsメソッドなどを実行してくれる。 https://github.com/mackerelio/go-mackerel-plugin-helper/blob/master/mackerel-plugin.go#L310

}

次に最終的に動かすことになるmain.goです。 - main.go

package main

import (
    "github.com/mackerelio/mackerel-agent-plugins/mackerel-plugin-gcp-compute-engine/lib" // パッケージ本体をインポート
)

func main() {
    gcpce.Do()
}

プラグインで使用し、makerel-agent-plugin-helperに渡す構造体を作り、
graphdefにグラフ定義を書き、
グラフを返すGraphDefinitionメソッドを定義し、
値を収集するFetchMetricsメソッドを定義し、
Do関数で引数を処理したりし、構造体をmackerel-agent-plugin-helperに渡して、helper.Run()で実行

という流れです。

その他必要に応じてメソッドや構造体を実装します。

READMEを書く

READMEを書く、と言うと簡単じゃん、と思うかもしれません。僕もそう思っていました。
しかし実際書くとREADMEがシステムの行く末を決める、と言っても過言ではないなと思いました。

使う人にとってはREADMEの情報がほぼ全てだと思いますし、
(ほとんどの人は使い方がわからないときは、ソースの中まで見ずに他の解決法を探すと思います。)
よって、ここにどれだけわかりやすく簡潔に書くか、ということが求められると感じました。
システムのインターフェイス、と言ってしまってもいいかもしれません。

そして、このREADMEを英語で書かなければなりません。。。
割と読める方だとは思っていたのですが、書くとなるとどうしていいかわからないものでした。

なので、僕はちょくちょくブログも書いてくれる後輩であるアメリカ帰りの湯浅君に英語を教わりながらREADMEを書いていきました。
大感謝です。湯浅君がすごくカッコよく見えました。

もしかしたらREADMEが今回一番苦戦し、一番勉強になった箇所かもしれません。。。笑

CIでテスト

mackerel-agent-pluginsの場合は、

https://github.com/mackerelio/mackerel-agent-plugins/blob/master/CONTRIBUTING.md

こちらにコントリビュートへの手順がありました。
こちらに書いてある通り、CIでテストをパスする必要があります。
travisCIにforkしたリポジトリを登録し、 テストをします。

僕の場合は、golintでかなりテストを失敗していました。。。
goのお作法を知らなかったので、なかなか苦戦しました。

プルリク

CIでテストが通ったら、プルリクです。
人生初のプルリクということでかなりドキドキしていました。

この時、プルリクに残すメッセージに何が適切かわからず、今も何が適切かわかりません。。。
次は他の方のを参考にしたいと思います。

レビューをしていただく

ありがたいことにレビューをしていただきました。

astjさん、ありがとうございました。

僕自身goを書くのが初だったので、拙いコードを読ませてしまうことになり、大変心苦しかったです。。。

次はもっと綺麗なコードでプルリクできるようにします。
(レビューしていただいた後にタイポでテストが通らない、などしょうもないミスがありました。大変申し訳ありません。。。)

マージ

レビューしていただき、直し、テストも通したらあとは待つのみ、です。

僕は幸せなことにマージしていただいたので、現在は晴れてmackerel-agent-pluginsの一員になっています。

初のプルリク、ということで、反省がかなり多くなってしまいました。
どう使われるのが一番なのか、英語でREADMEを書くことなど、しかし普段の開発ではわからないことや初めてのことも多く、大変勉強になりました。

次はもっとスムーズにできるようにしたいなと思います。

改めまして、mackerelioの方々、ありがとうございました。

canvasタグを使って動画のスクリーンショットを撮る方法

はじめまして。エンジニアブログ初投稿の南です。
広告代理店であるアドウェイズではさまざまな動画広告を媒体に配信しています。
ある日、「動画からサムネイルを手軽に作る機能がほしい」と要望がありました。
用件としては、

  • 動画広告はサムネイルによって効果が変わる場合があるので、一つの動画から複数のサムネイルを作成する場合がある
  • 毎回スクリーンショットを撮るのは面倒
  • 再生しながらボタン一つで作成したい

以上のようなものでした。
今回はhtml5で新たに追加されたcanvasタグとjavascriptを用いて実装してみようと思います。

html用意

動画を描画するhtmlを用意します。

<html>
  <head>
    <meta charset="utf-8">
    <script src="http://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
  </head>
  <body>
    <video id="video" src="test.mp4" controls></video>
    <button id="capture">キャプチャー</button>
    <a href="" id="download" download="test.png">ダウンロード</a>
    <canvas id="canvas"></canvas>
  </body>
</html>

javascriptでcanvasに描画

ボタンがクリックされた際の動画の状態をcanvasに描画します

$('#capture').on('click', function(){
  var video = document.getElementById('video');
  var $canvas = $('#canvas');
  
  $canvas.attr('width', video.videoWidth);
  $canvas.attr('height', video.videoHeight);
  $canvas[0].getContext('2d').drawImage(video, 0, 0, $canvas.width(), $canvas.height());
});

canvasにはwidthとheightをvideoタグと同じ大きさで設定しています。
canvasのサイズはデフォルトでは300px×150pxが設定されており、cssを用いてwidthなどを変更してしまうと、デフォルトサイズが適用され思うように描画することができません。

後はaタグのhref属性にcanvasのバイナリを埋め込む処理をon click処理内に追記すればダウンロードまで完成です。

$('#download').attr('href', $canvas[0].toDataURL('image/png'));

デモ

f:id:AdwaysEngineerBlog:20170224130140g:plain

よさげですね。

感想

それほど複雑なコードを書くことなく作成できたので、「canvasすげー」ってなりました。
業務ではRailsを利用しているので、バイナリをサーバに送信し、RMagickを使ってごにょごにょやってます。
そこら辺も機会があれば書きたいと思います。

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がリリースパッケージの作成を自動化してくれるのでとても簡単ですね。