At Find a Player[1], we've been trying to figure out how we want to cluster our Elixir servers - so that they can share state between each other - recently.
Because we come from ruby (ie, not a high-availability background), when we want to discover other machines in a network we'd traditionally use something like Consul[2] - of which I wrote a ruby library called Diplomat[3] (which admittedly needs some love).
However, after chatting with Gordon Guthrie[4] - a guy who's been working with Erlang for over 13 years - over twitter, he told us to checkout `:net_adm.world/0`. Which does pretty much exactly what we want, straight out of the box.
So, if you are building an Elixir app with Autoscaling and AWS, here is how to autoconnect your nodes:
On each node, make sure that the hostname is set to the private DNS:
HOSTNAME="$(curl http://169.254.169.254/latest/meta-data/hostname)" sudo hostnamectl set-hostname $HOSTNAME echo "127.0.0.1 ${HOSTNAME}" >> /etc/hosts
If you have `awscli` installed (highly recommended) then you can run this at some point before running the app to build the file:
SERVER_CLASS=tag-for-similar-nodes-in-aws aws ec2 describe-instances \ --query 'Reservations[].Instances[].NetworkInterfaces[].PrivateIpAddresses[].PrivateDnsName' \ --output text \ --filter Name=tag:Class,Values=${SERVER_CLASS} | sed '$!N;s/\t/\n/' | sed -e "s/\(.*\)/'\1'./" > $OTP_ROOT/.hosts.erlang
The above script will find matching instances, print out their private DNS, and produce a file in the format below:
'lorem.example.org'. 'ipsum.example.org'.
Make sure the hostname is setup correctly in your vm.args. Here's a one-liner to do it using sed:
sed -r -i "s/\-sname.+/\-name api\@$(hostname)/g" /opt/api/releases/$LATEST_RELEASE/vm.args
In your code, add a module which checks if the `.hosts.erlang` file exists, and if so, use it to connect to one of the nodes.
defmodule Api.Discovery do require Logger def discover do # Attempt to connect to any other outside nodes. # This command makes use of the /opt/api/.hosts.erlang file hosts_path = Path.expand("/opt/api/.hosts.erlang") case File.exists?(hosts_path) do true -> Logger.info("Attempting to join cluster") :net_adm.world(:verbose) false -> Logger.warn("Couldn't find #{hosts_path} file - not joining cluster") end end end