I’ve been running a few experiments in distributed systems on top of akka and time and again I needed a small akka cluster as a base for my tests. Thus, kluster was born.

kluster is a minimal akka cluster application that runs locally via docker containers. It includes a shell script to build, kill and redeploy the application.

The code is on github and the README contains instructions on how to build and run the application. You should get everything up and running with a single command line: run.sh.

EDIT: see updates on how to add and remove nodes from the cluster.

How does it work?

Docker

kluster relies on user defined networks to run the cluster. The script run.sh handles all the magic. The script creates a user defined network named kluster if it doesn’t exist. Then, the containers comprising the cluster are run on this network. Furthermore, the hostname of each container is set and follows the naming convention klusterN. This way, containers can query their own hostnames and talk to other containers in the cluster.

Akka Cluster

The docker architecture drives the implementation of the akka cluster code.

The configuration value akka.remote.netty.tcp.hostname is not set in the resource file application.conf. Instead, the hostname value is set programmatically since all containers contain the same image.

The main method contains handles the setup:

val hostname: String = {
  val inetAddr = InetAddress.getLocalHost()
  inetAddr.getHostName()
}

val config = ConfigFactory
  .parseString(s"akka.remote.netty.tcp.hostname=$hostname")
  .withFallback(ConfigFactory.load())

The code above fetches the hostname of the node, which was set in the docker run command, and then apply it to the configuration used by akka.

Also, seed nodes are not set in the configuration file. Instead, we assume that the first node is always present at the creation of the cluster. During bootstrap all nodes join the first node kluster1.

The following snippet of code is executed by all nodes:

val addr = Address("akka.tcp", system.name, "kluster1", 2550)
val cluster = Cluster(system)
cluster.join(addr)

Logs

You can follow the logs using docker. For example, to check the logs from kluster1 you can run:

docker logs -f kluste1

Experiments

After making changes to the scala code you can build the code and redeploy the cluster by just running:

./run.sh -f

You can test the side effects of adding or removing nodes to the cluster using docker.

For example, to remove kluster3 run:

docker kill kluster3

To add a new node, say, kluster4, run

docker run --rm -itd --expose=8080 --expose=2550 --network=kluster --hostname=kluster4 --name=kluster4 kluster