vintage_netを含むnerves_packを使ってこのようなことをしていました。今回はNerves起動時にNode.connect()
するようにしました。これは Nervesで起動時にNode.connect()
するようにした のvintage_net版になります。個人的にはきっとこれからこっちを使うことになると思ってます。
- nerves_pack(vintage_net含む)を使ってNervesのネットワーク設定をした〜SSHログインまで〜」
- nerves_pack(vintage_net含む)を使ってNervesのネットワーク設定をした〜SSHのポート番号変更〜
追記
mix local.nerves
してnerves_bootstrap 1.8.0にアップデートしたら暗黙の動作が増えたようなのでバグ対応を含めbootexineris.ex
ファイルを修正しました。
環境
ハードウェアとネットワークはこのような構成です。
今回もホスト名flamboise03を操作することにします。
nerves_pack
のインストールと設定は「nerves_pack(vintage_net含む)を使ってNervesのネットワーク設定をした〜SSHログインまで〜」をみてください。
ハードウェア
- Mac OSX
- NervesをRaspberryPi 3で稼働
ネットワーク
-
Mac OSX
- 有線LAN→メンテナンス用、固定IPアドレス(192.168.5.100/24)
-
RaspberryPi 3(flamboise03)
このようにしました
IPアドレスの設定は完了しているとして、次のファイルをそれぞれ追記、作成します。
- lib/exineris002/application.ex:nerves起動時の設定を追記
- lib/exineris002/bootexineris.ex:Node.connectするプログラム
前回の Nervesで起動時にNode.connect()
するようにした の時はNerves起動時に接続した後、再接続する仕組みがなかったので今回は再接続の仕組みを追加しています。
lib/exineris002/application.ex
Bootexineris
モジュールにリストを渡して起動します。
リストは左から「自分のnode_name」「クッキー(共通鍵)」「接続先」としています。
def children(_target) do
[
# {Bootexineris, [node_name, cookie, conn_node]}
{Bootexineris, ["exima", "comecomeeverybody", "kazumambp@192.168.46.100"]}
]
end
lib/exineris002/bootexineris.ex
- Nerves起動時の動作(キーワード:
:wakeup
,nodeconn/2
) - Nerves起動後1秒おきに接続しようとする
-
wlan0
インタフェースにDHCPよりIPアドレスが割り当てられるのを確認後、Node.connect/1
の手続きを実施 - 接続完了後、接続確認の動作に遷移
- 接続確認の動作(キーワード:
:alive
,re_nodeconn/2
) - 60秒おきに接続を確認
- 正常時 → 再度60秒接続を確認を実施 (
Node.list
に接続先が存在している &&Node.ping
で応答がある場合 →:node_alive
) - 接続が切れた後に接続先が復帰した場合 → 再接続を実施し失敗した場合は1秒おきに再接続を実施 (
Node.list
に接続先が存在していない &&Node.png
で応答がある場合 →:node_re_conn
) - 接続が切れている場合 → 1秒おきに再接続を実施 (
Node.list
に接続先が存在していない &&Node.ping
で応答がない場合 →:node_down
)
defmodule Bootexineris do
use GenServer
require Logger
@interval_init_ms 1_000
@interval_wakeup_ms 1_000
@interval_alive_ms 60_000
@interval_alive_false_ms 1_000
def start_link(node_option \\ []) do
# to init/1
GenServer.start_link(__MODULE__, node_option, name: __MODULE__)
end
def init(node_option) do
set_interval(:init, @interval_init_ms)
{:ok, node_option}
end
def set_interval(msg, ms) do
# to handle_info/2
Process.send_after(self(), msg, ms)
end
def handle_info(:init, node_option) do
init_nodeconn(wlan0_ready?(), node_option)
{:noreply, node_option}
end
def handle_info(:wakeup, node_option) do
nodeconn(node_option)
{:noreply, node_option}
end
def handle_info(:alive, node_option) do
re_nodeconn(conn_node_alive?(node_option), node_option)
{:noreply, node_option}
end
defp init_nodeconn(true, [node_name, cookie, _]) do
node_host = get_ipaddr_wlan0()
System.cmd("epmd", ["-daemon"])
Node.start(:"#{node_name}@#{node_host}")
Node.set_cookie(:"#{cookie}")
Logger.info("=== Node.start -> #{node_name}@#{node_host} ===")
Logger.info("=== Node.set_cookie -> #{cookie} ===")
case [node_start?(), node_set_cookie?()] do
[true, true] ->
Logger.info("=== init_nodeconn -> success! Node.start & Node.set ===")
set_interval(:wakeup, @interval_wakeup_ms)
[_, _] ->
Logger.info("=== init_nodeconn -> false, node_start(#{inspect(node_start?())}), node_set_cookie(#{inspect(node_set_cookie?())}) ===")
set_interval(:init, @interval_init_ms)
end
end
defp init_nodeconn(false, [_, _, _]) do
Logger.info("=== init_nodeconn -> false, wlan0_ready(#{inspect(wlan0_ready?())}) ===")
set_interval(:init, @interval_init_ms)
end
defp nodeconn([_, _, conn_node]) do
conn = Node.connect(:"#{conn_node}")
Logger.info("=== Node.connect -> try connect to #{conn_node} ===")
case conn do
true ->
Logger.info("=== nodeconn -> #{conn} ===")
set_interval(:alive, @interval_alive_ms)
_ ->
set_interval(:wakeup, @interval_wakeup_ms)
end
end
defp re_nodeconn(:node_alive, _) do
set_interval(:alive, @interval_alive_ms)
end
defp re_nodeconn(:node_re_conn, [_, _, conn_node]) do
conn = Node.connect(:"#{conn_node}")
Logger.info("=== re_nodeconn Node.connect -> #{conn_node} ===")
case conn do
true ->
Logger.info("=== re_nodeconn -> #{conn} ===")
set_interval(:alive, @interval_alive_ms)
_ ->
set_interval(:alive, @interval_alive_false_ms)
end
end
defp re_nodeconn(:node_down, [_, _, conn_node]) do
Logger.debug("=== re_nodeconn -> false... try connect to #{conn_node} ====")
set_interval(:alive, @interval_alive_false_ms)
end
def node_start?() do
case Node.self() do
:nonode@nohost -> false
_ -> true
end
end
def node_set_cookie?() do
case Node.get_cookie() do
:nocookie -> false
_ -> true
end
end
def conn_node_alive?([_, _, conn_node]) do
case [conn_node_list_find?(conn_node), conn_node_ping?(conn_node)] do
[true, true] -> :node_alive
[false, true] -> :node_re_conn
[_, _] -> :node_down
end
end
def conn_node_list_find?(conn_node) do
case Node.list() |> Enum.find(fn x -> x == :"#{conn_node}" end) do
nil -> false
_ -> true
end
end
def conn_node_ping?(conn_node) do
case Node.ping(:"#{conn_node}") do
:pang -> false
:pong -> true
end
end
def wlan0_ready?() do
case get_ipaddr_wlan0() do
nil -> false
_ -> true
end
end
def get_ipaddr_wlan0() do
case VintageNet.get_by_prefix(["interface", "wlan0", "addresses"]) do
[] ->
nil
[list_int_wlan0_addr] ->
list_int_wlan0_addr
|> (fn {_, list_settings} -> list_settings end).()
|> hd()
|> Map.get(:address)
|> VintageNet.IP.ip_to_string()
end
end
end
確認
OSXのターミナルでiex
を起動します。
$ iex --name kazumambp@192.168.46.100 --cookie comecomeeverybody
iex(kazumambp@192.168.46.100)> Node.list
[]
次にNervesを起動します。
bootexineris.ex
がうまく動いていれば自動的にNode.connect
して、OSXとNervesのNode.list
にお互いが登録されているはずです。
$ ssh 192.168.5.55
iex> …①
iex(exima@192.168.46.11)> …②
iex(exima@192.168.46.11)> Node.list …③
[:"kazumambp@192.168.46.100"]
①Nerves起動直後はNode.startしてないのでこの表示
②wlan0がIPアドレスをDHCPサーバより取得するとNode.startしてNode.set_cookie後、Node.connectする
③Node.listして接続の確認
NervesでNode.connect
できていることを確認できたのでOSXでも確認します。
iex(kazumambp@192.168.46.100)> Node.list
[:"exima@192.168.46.11"]
Node.connect
していることを確認できました!!
結果は省きますが、以下の機能もあります。
-
Node.connect
先のOSXがNervesより後に起動した場合の接続 -
Node.connect
先のOSXがダウンして再起動した場合の再接続
まとめ
Nerves起動時にNode.connect()
できるようになりました!
vintage_netを使った今回の方が 前回 より関連するファイルが少なく導入が楽なのかなぁという印象です。安定しててほしいなぁ。
def
とdefp
の使い分けが適当になってきてるので整理したいなー。