Connect Nerves devices forming an Erlang cluster

I want to enable my Nerves devices to talk to each other using Distributed Erlang. Here is my memo.

Preparation

Find hostname or IP address for each device

When shelling into a Nerves device, we will see IP address and host name printed by nerves_motd.

DNS Bridge configuration

According to mdns_lite - DNS Bridge configuration documentation, we need to let Erlang/OTP's built-in DNS resolver know about mDNS. Underjord's YouTube video explaining DNS Bridge configuration was helpful.

Start Erlang nodes

  • nerves_pack - Erlang distribution documentation explains about how to form an Erlang cluster.
  • Let's say we have a device with host name nerves-mn00.local and anoter with host name nerves-mn02.local.
  • Alternatively IP addresses can be used instead of host names
  • Make sure that the same Erlang magic cookie is used
❯ ssh nerves-mn00.local

# make sure the epmd OS process is running by calling epmd -daemon
iex> System.cmd("epmd", ["-daemon"])

# start a node.
iex> Node.start(:"nerves@nerves-mn00.local")

# check if current node. `node/0` does the same.
iex(nerves@nerves-mn00.local)> Node.self()

# configure the magic cookie to form this cluster.
iex(nerves@nerves-mn00.local)> Node.set_cookie(:securecookie)
❯ ssh nerves-mn02.local

iex> System.cmd("epmd", ["-daemon"])
iex> Node.start(:"nerves@nerves-mn02.local")
iex(nerves@nerves-mn02.local)> Node.self()
iex(nerves@nerves-mn02.local)> Node.set_cookie(:securecookie)

Connect a node to another

  • Let's connect nerves-mn02.local to nerves-mn00.local
iex(nerves@nerves-mn02.local)> Node.connect(:"nerves@nerves-mn00.local")
true

iex(nerves@nerves-mn02.local)> Node.list()
[:"nerves@nerves-mn00.local"]

Invoke a function in another node

  • From nerves-mn02.local invoke a function that is defined in nerves-mn00.local using Node.spawn/2
  • Let's access to the other device and peek into its firmware infomation by invoking Toolshed.uname/0 that is normally available in our Nerves IEx shell.
Node.spawn(
  # the node name we want to invoke a function in
  :"nerves@nerves-mn00.local",
  # the function we want to invoke
  fn -> Toolshed.Nerves.uname() end
)
iex(nerves@nerves-mn02.local)> Node.spawn(:"nerves@nerves-mn00.local", fn -> Toolshed.Nerves.uname() end)
Nerves nerves-mn00 hello_nerves 0.1.0 (240d82e1-1e6c-5800-0ef9-63cba9efc212) arm
#PID<51667.6840.0>

Yay, we were able to look at the other devices info through Erlang distribution. Isn't it cool?