Elixir Node.ping/1で気軽に分散システムを楽しむ

[ノード間の通信][otp_distribution | elixirschool]で[Node.ping/1]が便利だなと思ったのでメモ。

ただ[ノード][otp_distribution | elixirschool]の接続を確認するだけではなく、未接続の場合には接続を確立してくれます。[Livebook]でも使用されています。

https://elixirschool.com/ja/lessons/advanced/otp_distribution

https://hexdocs.pm/elixir/Node.html#ping/1

https://github.com/livebook-dev/livebook/blob/484e47142ab3c9a2b033f3a0939e0d468c356b2a/lib/livebook/runtime/attached.ex#L42-L54

この記事も参考になりました。

https://medium.com/@pawel_dawczak/elixir-distribution-eli5-309193004119

## [Erlangの分散機能][Distributed Erlang]について

ポイントがいくつかあります。

### ノードの名前

[分散ノード][Distributed Erlang]を起動する方法は2つあります。

- `--sname`オプション
  - 短い名前
  - 同一のホスト上にクラスターを設けるときに便利
- `--name`オプション
  - ノードの場所（例、IPアドレス）を含む完全修飾名

http://erlang.org/pipermail/erlang-questions/2006-December/024274.html

https://erlangforums.com/t/difference-between-short-and-long-names-how-erlang-resolves-them/1531

### Erlangクッキー

- クラスターを識別するためのもの
- セキュリティ用ではない
- ノードを接続するためには同じErlangクッキーを共有する必要

### [epmd] (Erlang Port Mapper Daemon)

- 登録されたノードのポートを追跡するプロセス
- 分散ノードでBEAMを起動するときに起動
- デフォルトではポート`4369`で動作
- ノードが他のノードへの接続が成功した時点でランダムなポートが割り当てられるらしい

`epmd -names`コマンドですべてのノードのポートが確認できます。

```sh
$ epmd -names

epmd: up and running on port 4369 with data:
name piyo at port 63110
name fuga at port 63108
name hoge at port 63106
```

### [メッシュネットワーク]

- 新しいノードがクラスタに加わると、[メッシュネットワーク]が形成されます。
- クラスタの全てのノードは他の全てのノードに接続されます。


論よりRun
:speaking_head: < :computer:

## 同一PC上でクラスタを作ってみる

### シェルを複数起動

複数の[シェル][コマンドラインインタプリタ]を起動します。

![シェルを複数起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/30fb94e7-87fb-bbc2-7628-1ea0b1b1a900.png)

### [IEx]セッションからノードを起動

`hoge`、`fuga`、`piyo`の三つのノードをそれぞれ別々の[IEx]セッションで起動します。

`--sname`オプションでノード名を指定します。

```sh
iex --sname hoge@localhost
```

```sh
iex --sname fuga@localhost
```

```sh
iex --sname piyo@localhost
```

![IExセッションからノードを起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/60ec14b1-c3a2-bc2e-c3d7-785a4965e753.png)

### Erlangクッキーを設定

ノードが接続する際に必要になります。あるクラスターに属するすべてのノードが同じクッキーを共有することになります。

```elixir
Node.set_cookie(:mycookie)
```


![Erlangクッキーを設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/9fe625a4-7de7-371f-908d-819c77ab07ad.png)

ちなみにiexコマンドの`--cookie`オプションまたは`--erl`オプションを用いることにより、Erlangランタイムシステム起動時にクッキーを設定することも可能です。

```elixir
iex --sname hoge --cookie mycookie
```

```elixir
iex --sname hoge --erl "-setcookie mycookie"
```

https://www.erlang.org/doc/man/erl.html

### [Node.ping/1]でノードを接続

`hoge`ノードのIExから他のノードに接続します。

```elixir
:pong = Node.ping(:"fuga@localhost")
:pong = Node.ping(:"piyo@localhost")
```

[Node.ping/1]は、成功した場合は `:pong`、失敗した場合は`:pang`を返します。

[Node.list/0]で現在接続されているノードのリストが確認できます。この時点で全てのノードが他の全てのノードに接続されているはずです。

![Node.ping/1でノードを接続](https://user-images.githubusercontent.com/7563926/204115162-555817a7-671b-4025-96bb-b3b4a98e0e45.png)

### 別ノードでコードを実行

`fuga`ノードのIExに`Toukon`モジュールを貼り付けて定義します。

```elixir
defmodule Toukon do
  def aisatsu, do: "元氣ですかーーーーッ！"
end
```

`piyo`ノードのIExから`fuga`ノードで定義された`Toukon`モジュールを実行します。

```elixir
Node.spawn(
  :"fuga@localhost",
  fn -> Toukon.aisatsu() |> IO.puts() end
)
```

![別ノードでコードを実行](https://user-images.githubusercontent.com/7563926/204115167-f2b82e87-0073-4f33-a329-1212d397e809.png)

## Nodeモジュール

Nodeモジュールの関数はすべて、Erlangの実装に委譲されています。分散ノードに関連する関数を便宜上まとめたものと言えます。

https://github.com/elixir-lang/elixir/blob/0909940b04a3e22c9ea4fedafa2aac349717011c/lib/elixir/lib/node.ex


[Node.ping/1]が便利なのでいまいち[Node.connect/1]の使い道がわかりません。[Livebook]では[Node.ping/1]でノードを接続しているので、深く考えず[Node.ping/1]でいいんじゃないかなと思ってます。

## ご参考までに

https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf


[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Phoenix]: https://www.phoenixframework.org/
[Nerves]: https://hexdocs.pm/nerves
[Livebook]: https://livebook.dev/
[IEx]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[Node | hexdocs]: https://hexdocs.pm/elixir/Node.html
[otp_distribution | elixirschool]: https://elixirschool.com/ja/lessons/advanced/otp_distribution
[Node.ping/1]: https://hexdocs.pm/elixir/Node.html#ping/1
[Node.connect/1]: https://hexdocs.pm/elixir/Node.html#connect/1
[Node.spawn/2]: https://hexdocs.pm/elixir/Node.html#spawn/2
[Node.list/0]: https://hexdocs.pm/elixir/Node.html#list/0
[Node.set_cookie/2]: https://hexdocs.pm/elixir/Node.html#set_cookie/2
[Node.get_cookie/0]: https://hexdocs.pm/elixir/Node.html#get_cookie/0
[epmd]: https://www.erlang.org/doc/man/epmd.html
[Elixirの分散処理(Node)とMnesia]: https://qiita.com/sand/items/c5c9dab1dce3d5c526ee
[Elixir Distribution, ELI5]: https://medium.com/@pawel_dawczak/elixir-distribution-eli5-309193004119
[Node.start/3でエラーが出る場合にはepmdをバックグラウンド起動する]: https://qiita.com/zacky1972/items/cb3aa801a179322c56d6
[コマンドラインインタプリタ]: https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%97%E3%83%AA%E3%82%BF
[端末エミュレータ]: https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%9C%AB%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF
[メッシュネットワーク]: https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%83%E3%82%B7%E3%83%A5%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF
[Distributed Erlang]: https://www.erlang.org/doc/reference_manual/distributed.html
[ホスト名]: https://ja.wikipedia.org/wiki/%E3%83%9B%E3%82%B9%E3%83%88%E5%90%8D