📩

通信経路が片方向の場合のElixirのノード間通信の挙動を確認する

2022/02/08に公開

はじめに

一方からは通信経路があるが、逆はそうではないというネットワークの状況において、Elixirのノード間通信が可能なのかどうかを確認したいと思います。

* ローカル → クラウド(経路あり)
* クラウド → ローカル(経路なし)

具体的には、上記のようにパブリッククラウド上のホストと、プライベートネットワーク内の手元のマシンとでノード間通信が可能なのか、その場合の通信はどうなっているのかを確認したいということです。

実験

クラウド上のホスト環境

まず、Google Cloud Platform(GCP)上で最小限のインスタンスを作った後、asdfでErlangとElixirをインストールしました。さらに、ノード間通信におけるポート解決のためのデーモンであるepmdと、実際のノード間接続で使うポートを、以下のように開放しておきます。

  • 4369: epmdが待ち受けるポート
  • 9100-9155: ノード間接続で使うポート

基本的なノード間接続

ノードの起動

クラウドとローカルのノードを、それぞれ起動します(IPアドレスは適宜読み替えてください)。

クラウド側(GCP上のホスト)

$ iex --name cloud@34.146.99.77 --cookie mycookie --erl "-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155"
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]

Interactive Elixir (1.13.2) - press Ctrl+C to exit (type h() ENTER for help)

ローカル側(手元のマシン)

$ iex --name device@local --cookie mycookie
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Interactive Elixir (1.13.2) - press Ctrl+C to exit (type h() ENTER for help)

ノード間で接続する

ローカル側のホストはプライベートネットワーク内にあるため通信経路がないので、クラウド側からは当然接続できません。そこで、ローカル側から接続します。

ローカル側(手元のマシン)

iex(device@local)1> Node.connect(:"cloud@34.146.99.77")
true
iex(device@local)2> Node.list
[:"cloud@34.146.99.77"]

ちゃんとつながったようです。クラウド側のノードでも確認します。

クラウド側(GCP上のホスト)

$ iex(cloud@34.146.99.77)1> Node.list
[:device@local]

メッセージを送信する

今度は、クラウド側からローカルのノードにメッセージを送れるかどうかを検証します。

以下では、ローカルのノードでself()が返すiexのプロセスをグローバルに参照可能な状態として登録した後、メッセージを待ち受けます(receiveによってブロックして待ち受ける)。

ローカル側(手元のマシン)

iex(device@local)2> :global.register_name(:local, self())
(device@local)3> receive do
...(device@local)3> {sender, msg} ->
...(device@local)3>   IO.puts "received message: #{msg}"
...(device@local)3>   send sender, {:ok, msg}
...(device@local)3> end

クラウド側では、登録された名前に紐づくpidにメッセージを投げてやります。

クラウド側(GCP上のホスト)

iex(cloud@34.146.99.77)2> send :global.whereis_name(:local), {self, "hello"} 
{#PID<0.111.0>, "hello"}

ローカルのノードでは、以下の通りメッセージが届きました。

ローカル側(手元のマシン)

received message: hello
{:ok, "hello"}

つまり、ローカル → クラウドの接続が確立すれば、その後は確立した経路を通って、クラウド → ローカルにもメッセージを送れることが確認できました。

どのように接続しているか

では、二つのノードはどのように接続しているのか、見ていきましょう。

ローカル側(手元のマシン)

ノード間通信に関するプロセスは以下の通り。

$ ps | grep erl
  708 ttys000    0:00.67 /Users/kentaro/.asdf/installs/erlang/24.2/erts-12.2/bin/beam.smp -- -root /Users/kentaro/.asdf/installs/erlang/24.2 -progname erl -- -home /Users/kentaro -- -kernel shell_history enabled -- -pa /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/eex/ebin /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/elixir/ebin /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/ex_unit/ebin /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/iex/ebin /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/logger/ebin /Users/kentaro/.asdf/installs/elixir/1.13.2/bin/../lib/mix/ebin -elixir ansi_enabled true -noshell -user Elixir.IEx.CLI -name device@local -setcookie mycookie -- -extra --no-halt +iex

この708のIDを持つプロセスによるネットワーク接続を確認しましょう。

$ lsof -n -P -p 708 | grep TCP
beam.smp 708 kentaro   35u    IPv4 0x5addf861f7b0fadb      0t0                 TCP *:64530 (LISTEN)
beam.smp 708 kentaro   36u    IPv4 0x5addf861f7985a8b      0t0                 TCP 127.0.0.1:64531->127.0.0.1:4369 (ESTABLISHED)
beam.smp 708 kentaro   39u    IPv4 0x5addf861f79aefab      0t0                 TCP 172.16.0.101:64533->34.146.99.77:9100 (ESTABLISHED)

それぞれ以下の通りです。

  • 1行目: ノードの接続を待ち受けている
  • 2行目: ローカルのホスト上で動作するepmdとの接続が確立している
  • 3行目: クラウド側のノードが待ち受けている9100番ポートとの接続が確立している

では、クラウド側はどうなっているでしょうか。

クラウド側(GCP上のホスト)

ノード間通信に関するプロセスは以下の通り。

$ ps aux | grep erl
kentarok 10777  0.0  0.0   3916   104 ?        S    05:59   0:00 /home/kentarok/.asdf/installs/erlang/24.2.1/erts-12.2.1/bin/epmd -daemon
kentarok 12596  0.0  4.6 2103156 46596 pts/3   Sl+  06:21   0:00 /home/kentarok/.asdf/installs/erlang/24.2.1/erts-12.2.1/bin/beam.smp -- -root /home/kentarok/.asdf/installs/erlang/24.2.1 -progname erl -- -home /home/kentarok -- -pa /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/eex/ebin /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/elixir/ebin /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/ex_unit/ebin /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/iex/ebin /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/logger/ebin /home/kentarok/.asdf/installs/elixir/1.13.2-otp-24/bin/../lib/mix/ebin -elixir ansi_enabled true -noshell -user Elixir.IEx.CLI -kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155 -name cloud@34.146.99.77 -setcookie mycookie -extra --no-halt +iex

この12596のIDを持つプロセスによるネットワーク接続を確認しましょう。

$ lsof -n -P -p 12596 | grep TCP
beam.smp 12596 kentarok   17u     IPv4             100802      0t0    TCP *:9100 (LISTEN)
beam.smp 12596 kentarok   18u     IPv4             100804      0t0    TCP 127.0.0.1:32862->127.0.0.1:4369 (ESTABLISHED)
beam.smp 12596 kentarok   19u     IPv4             101467      0t0    TCP 10.146.0.2:9100->221.112.105.225:64533 (ESTABLISHED)

10.146.0.2というのは、GCP上のインスタンスの内部IPです。3行目で、ローカル側のノードとの接続が確立しているのがわかります。

epmdを使わない接続

epmdは、セキュリティ的な観点から、外部から任意のアクセスが可能な状態に置くべきではありません。詳しくは「インターネット越しでもセキュアなErlang Distributionを実現する」をご覧ください。

今度は、上記の記事で紹介した、epmdを用いないノード間通信を検証します。

ノードの起動・ノード間接続

クラウド側では、冒頭でepmd用に開けたポートを、ノード間通信用に使い回します。

クラウド側(GCP上のホスト)

$ iex --name cloud@34.146.99.77 --cookie mycookie --erl "-start_epmd false" --erl "-erl_epmd_port 4369"
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]

Interactive Elixir (1.13.2) - press Ctrl+C to exit (type h() ENTER for help)

ローカルのノードも同じように起動して接続を試みると、ちゃんと成功しました。

ローカル側(手元のマシン)

$ iex --name device@local --cookie mycookie --erl "-start_epmd false" --erl "-erl_epmd_port 4369"
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Interactive Elixir (1.13.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(device@local)1> Node.connect(:"cloud@34.146.99.77")
true
iex(device@local)2> Node.list
[:"cloud@34.146.99.77"]

どのように接続しているか

前述のepmdを用いたノード間通信とは違い、epmdとの通信は確立されていません。クラウド側のノードとのみ、通信が確立された状態です。

ローカル側(手元のマシン)

$ lsof -n -P -p 8015 | grep TCP
beam.smp 8015 kentaro   35u    IPv4 0x5addf861f6db5a8b      0t0                 TCP *:4369 (LISTEN)
beam.smp 8015 kentaro   38u    IPv4 0x5addf861f7ca9a8b      0t0                 TCP 172.16.0.101:50646->34.146.99.77:4369 (ESTABLISHED)

クラウド側も同様です。

クラウド側(GCP上のホスト)

$ lsof -n -P -p 14228 | grep TCP
beam.smp 14228 kentarok   17u     IPv4             106366      0t0    TCP *:4369 (LISTEN)
beam.smp 14228 kentarok   18u     IPv4             106371      0t0    TCP 10.146.0.2:4369->221.112.105.225:50646 (ESTABLISHED)

この状態でも、メッセージ送信については、前述と同じようにできることを確認しました。

おわりに

本記事では、一方からは通信経路があるが、逆方向はそうではないネットワークにおいて、Elixirのノード間通信が行えるのか、その場合の通信はどうなっているのかを確認しました。

今回は手元のマシンとクラウド上のホストでそれぞれノードを起動して通信しましたが、実際にやりたいのは、プライベートネットワーク内に配置された複数のデバイスとクラウド上のノードとを、ノード間通信で双方向にメッセージが送れる形で接続することです。今回の実験で、そのような接続も同様に可能なことがわかりました。

参考文献

Discussion