I’ve bought 3 Raspberry Pis (B+, a week before the announcement of the Pi 2), and created a little cluster with them. Each Pi is connected to wireless (192.168.1.0/24) and they share an internal, wired, 10.248.0.0/16 network.

Virtual IP

I want one IP address to be load balanced across the three Pis. This can be with the CLUSTERIP target in iptables. In raspbian the kernel module for this was not enabled, but luckily this was fixed very quickly.

The virtual IP address is 192.168.1.10 and creating it is done like so:

auto wlan0:0
iface wlan0:0 inet static
address 192.168.1.10
pre-up iptables -A INPUT -d 192.168.1.10 -j CLUSTERIP -i wlan0 \
--new --hashmode sourceip-sourceport --clustermac 01:00:5E:00:00:01 \
--total-nodes 3 --local-node 2
pre-down iptables -D INPUT -d 192.168.1.10 -j CLUSTERIP -i wlan0 \
--new --hashmode sourceip-sourceport --clustermac 01:00:5E:00:00:01 \
--total-nodes 3 --local-node 2

The mac address (01:00:5E:00:00:01) is a multicast mac address. The above snippet has to be run on each of the Pis; this is on my second node (--local-node 2). This also needs to some health checking to remove nodes from the config when they are dead. This is still on the todo list.

If all this went correct you can ping this IP. You would see the icmp packets arriving at all hosts, but only one will reply (yes, this is a poor man’s load balancing).

Etcd

Go has excellent cross compiling support and ARM support. This means we are able to cross compile Etcd and run it.

First compile Go to be able to make ARM binaries:

cd $GOROOT/src
GOARCH=arm ./make.bash

Then compile Etcd for ARM:

cd github.com/cores/etcd
GOARCH=arm GOARM=5 go build

When done, you have a binary for ARM:

% file etcd
etcd: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

When running this on my Pis, it crashed on startup, which can be fixed with a small patch that is included in that Etcd issue (full fix is pending).

Then I use zbundle, to combine the startup code with the etcd binary and copy the resulting (executable) zipfile to the Pis.

Startup code (contained in a file temp. called x.sh)

#!/bin/bash
unzip -qo $0 2>/dev/null  # needed for zbundle

TENIP=$(ip -4 -o addr sh eth0 | awk ' { print $4 } ' | sed 's|/[0-9][0-9]||')
VIP=192.168.1.10
HOST=$(hostname)

PEER="http://localhost:2380,http://localhost:7001"
CLIENT="http://localhost:2379,http://localhost:4001,http://$VIP:4001"
ADVERT="http://localhost:2379,http://localhost:4001,http://$TENIP:4001,http://$VIP:4001"

CMD="./etcd -name $HOST -initial-advertise-peer-urls http://$TENIP:2380
  -listen-peer-urls "$PEER,"http://$TENIP:2380
  -listen-client-urls $CLIENT
  -advertise-client-urls $ADVERT
  -initial-cluster-token pi-etcd
  -initial-cluster pi0=http://10.248.0.1:2380,pi1=http://10.248.0.2:2380,pi2=http://10.248.0.3:2380
  -initial-cluster-state new"

echo $CMD
$CMD &
disown
exit

All of the above tells etcd that it should contact the other members via the 10.248.0.0/16 address and listen for client connections on the 192.168.1.10 virtual IP of the wireless network.

Bundle and deploy (who needs Docker anyway?):

% zbundle x.sh etcd
  adding: etcd (deflated 69%)
etcd.zip

% for i in 0 1 2; do scp etcd.zip pi$i: ; done
etcd.zip                            100% 2368KB   2.3MB/s   00:00
etcd.zip                            100% 2368KB   2.3MB/s   00:00
etcd.zip                            100% 2368KB   2.3MB/s   00:01

Now on each the Pis, we can just execute ./etcd.zip and “stuff” should happen.

And then from my laptop:

% etcdctl -C http://192.168.1.10:4001 set /foo/bar "Hello world" --ttl 60
Hello world

% ./etcdctl -C http://192.168.1.10:4001 ls /foo/bar
/foo/bar

% ./etcdctl -C http://192.168.1.10:4001 get /foo/bar
Hello world

So \o/ yes, this works.

Now on with my next plans: Putting SkyDNS on the same virtual IP, creating a DHCP server that uses Etcd as a storage backend and getting this all to work with IPv6.