General

In an earlier blog I demonstrated how a local boot2docker based Docker experimental using overlay networking on Docker Swarm can be set up. In this article I show how a Cassandra cluster can be set-up on top of the overlay network.

A boot2docker image containing the experimental version is used. Information about building it can be found here.

Overview of the Architecture

The set-up consists of an infra-node which contains the Consul server used as the back-end for overlay networking. As the overlay networking is still in an experimental stage it has some issues. It for example breaks the outside connectivity of the containers. This prevents building of images. For this reason infra-node is used for building.

To have a shorter development feedback cycle, a private registry is set up on the infra node so that the swarm members can get their images from there instead of having to use Docker Hub for building and distributing the images.

In addition to the infra-node a swarm master and another swarm-node are created. Adding more swarm nodes is trivial.

The Cassandra image

The Cassandra image used is almost uncustomized and can be found from GitHub. A startup-script has been added which modifies the cassandra.yaml so that the seed list is changed to include a value that is given as part of the startup command. If the value is omitted it defaults to 127.0.0.1. Also the listen address is cleared so that the hostname is used instead of localhost so that the cluster can work. RPC connections are allowed from everywhere. By default the Cassandra image uses quite a lot of memory, so the amount of memory can throttled using Docker options.

Setting up the environment

Downloading the experimental boot2docker-image and setting it as the default

As downloading the image every time takes considerable time, it’s easier to download the image once and set it as the default for docker-machine. (Thanks JoyceBabu for the tip!).

1
2
curl -L http://sirile.github.io/files/boot2docker-1.9.iso > $HOME/.docker/machine/cache/boot2docker-1.9.iso
export VIRTUALBOX_BOOT2DOCKER_URL=file://$HOME/.docker/machine/cache/boot2docker-1.9.iso

Afterwards unsetting the variable is enough to revert to default image.

Creating the infra node

Infra node contains the private registry and Consul. The IP is given so that the locally built images can be pushed to the private registry without setting up TLS. If the created node gets another IP from the VirtualBox DHCP server it may be easiest to reset the DHCP server (I disabled and re-enabled it from the VirtualBox GUI) and restart the machine. Otherwise getting the private registry to work may require quite a lot of tinkering.

Create the node using Docker Machine

1
docker-machine create --driver virtualbox  --virtualbox-memory 2048  --engine-insecure-registry 192.168.99.100:5000 infra

Start the private registry

1
docker $(docker-machine config infra) run -d -p 5000:5000 --restart=always --name registry registry:2

Start Consul

1
docker $(docker-machine config infra) run -d -p 8500:8500 progrium/consul -server -bootstrap-expect 1

Create the Swarm token

1
export SWARM_TOKEN=$(docker $(docker-machine config infra) run swarm create)

Creating the Swarm master (swarm-0)

Create the node using Docker Machine

1
docker-machine create -d virtualbox  --engine-opt="default-network=overlay:multihost" --engine-opt="kv-store=consul:$(docker-machine ip infra):8500" --engine-label="com.docker.network.driver.overlay.bind_interface=eth1" --engine-insecure-registry $(docker-machine ip infra):5000 swarm-0

Start swarm

1
2
docker $(docker-machine config swarm-0) run -d --restart="always" --net="bridge" swarm:latest join --addr "$(docker-machine ip swarm-0):2376" "token://$SWARM_TOKEN"
docker $(docker-machine config swarm-0) run -d --restart="always" --net="bridge" -p "3376:3376" -v "$HOME/.docker/machine/machines/swarm-0:/etc/docker" swarm:latest manage --tlsverify --tlscacert="/etc/docker/ca.pem" --tlscert="/etc/docker/server.pem" --tlskey="/etc/docker/server-key.pem" -H "tcp://0.0.0.0:3376" --strategy spread "token://$SWARM_TOKEN"

Creating the swarm node (swarm-1)

This step can be repeated and more nodes can be created by just changing the machine name.

Create the node using Docker Machine

1
docker-machine create -d virtualbox  --engine-opt="default-network=overlay:multihost" --engine-opt="kv-store=consul:$(docker-machine ip infra):8500" --engine-label="com.docker.network.driver.overlay.bind_interface=eth1" --engine-label="com.docker.network.driver.overlay.neighbor_ip=$(docker-machine ip swarm-0)" --engine-insecure-registry $(docker-machine ip infra):5000 swarm-1

Start the swarm agent

1
docker $(docker-machine config swarm-1) run -d --restart="always" --net="bridge" swarm:latest join --addr "$(docker-machine ip swarm-1):2376" "token://$SWARM_TOKEN"

Getting the Cassandra image to local registry

If you want to build the image it can be found from here. Otherwise you can pull the image from Dockerhub and push it to local registry.

1
2
3
docker $(docker-machine config infra) pull sirile/minicassandra
docker $(docker-machine config infra) tag sirile/minicassandra $(docker-machine ip infra):5000/cass
docker $(docker-machine config infra) push $(docker-machine ip infra):5000/cass

Starting the images

Point docker client to swarm master

1
2
3
export DOCKER_HOST=tcp://"$(docker-machine ip swarm-0):3376"
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH="$HOME/.docker/machine/machines/swarm-0"

Start a few Cassandra instances

The last parameter is the seed list that is substituted to the cassandra.yaml configuration file. Allow for some time between starting the instances so that the cluster builds up correctly. The instance logs can be read using the normal docker logs command, for example docker logs cass1.

1
2
3
docker run -d --name cass1 192.168.99.100:5000/cass cass1
docker run -d --name cass2 192.168.99.100:5000/cass cass1
docker run -d --name cass3 192.168.99.100:5000/cass cass1

Testing the set-up

Show how the instances are spread between nodes

1
2
3
4
5
$ docker ps
CONTAINER ID        IMAGE                      COMMAND             CREATED              STATUS              PORTS               NAMES
eab9a8726ca9        192.168.99.100:5000/cass   "/start.sh cass1"   48 seconds ago       Up 48 seconds                           swarm-1/cass3
84191d24d9a1        192.168.99.100:5000/cass   "/start.sh cass1"   About a minute ago   Up About a minute                       swarm-0/cass2
d800115d147f        192.168.99.100:5000/cass   "/start.sh cass1"   2 minutes ago        Up 2 minutes                            swarm-1/cass1

Checking the ring

1
2
3
4
5
6
7
8
9
$ docker exec cass1 /cassandra/bin/nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address     Load       Tokens  Owns (effective)  Host ID                               Rack
UN  172.21.0.1  51.61 KB   256     66.4%             ad94770f-0119-427b-a603-1fb278fbad46  rack1
UN  172.21.0.3  66.49 KB   256     63.2%             708e5394-4e2f-4c48-9254-cbb8e1d6ed90  rack1
UN  172.21.0.2  66.05 KB   256     70.4%             6eb5464b-771f-4242-913f-d399f05411d2  rack1

Starting cqlsh

1
2
3
4
5
$ docker exec -it cass1 /cassandra/bin/cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 2.1.9 | CQL spec 3.2.0 | Native protocol v3]
Use HELP for help.
cqlsh>

Final thoughts

Everything seems to work as expected. The port for connecting from outside of the swarm can be exposed with the normal Docker procedures of defining -p on one of the instances.