How to run Akka Cluster on Google Cloud Platform

This is a short guide to walk you through how to deploy and run Akka Cluster based application on Google Cloud Platform.

Note: This guide is about how to deploy Akka application to Google Container Engine (GKE). If you are interested in deploying Akka application to Google Compute Engine (GCE) instead, read the following article.

Background: Google Container Engine’s underlying technologies are Docker and Kubernetes. Some familiarity with those technologies is assumed.

Creating Google Container cluster:

Google provides an excellent documentation how to signup to their platform, create and manage Container Clusters. For the rest of this guide, you will need:

  • Signup to Google Container Engine. Documentation is here.
  • Create a cluster with 3 nodes (default configuration should suffice). Documentation is here.
  • To get familiar with Google Container Registry. Documentation is here.

Compiling and packaging sample Akka application:

Background: Our sample application is inspired by akka-sample-cluster. It has backend nodes that calculate factorial upon receiving messages from frontend nodes.

  • Clone sample application: from here.
  • Compile and package sample components:

    sbt backend:assembly # backend
    sbt frontend:assembly # frontend
    
    Dockerizing Akka application:
  • Docker build:

We have 3 Dockerfiles:

  • Dockerfile-java is a file for base Docker image that contains Java installation. To build it:

docker build -t java -f Dockerfile-java .
- Dockerfile-backend is file with a Backend component image. To build it:

docker build -t akka-sample-cluster-backend -f Dockerfile-backend .
- Dockerfile-frontend is file with a Front component image. To build it:

docker build -t akka-sample-cluster-frontend -f Dockerfile-frontend .
- Testing locally with Docker:

  • Running 2 backend nodes:
    docker run -d -it -e "AKKA_THIS_IP=192.168.99.100" -e "AKKA_SAMPLE_SEED_IP_1=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_1=2551" -e "AKKA_SAMPLE_SEED_IP_2=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_2=2552" -p "2551:2551" --name akka-sample-cluster-backend-1 akka-sample-cluster-backend java -jar akka-sample-backend.jar 2551 docker run -d -it -e "AKKA_THIS_IP=192.168.99.100" -e "AKKA_SAMPLE_SEED_IP_1=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_1=2551" -e "AKKA_SAMPLE_SEED_IP_2=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_2=2552" -p "2552:2552" --name akka-sample-cluster-backend-2 akka-sample-cluster-backend java -jar akka-sample-backend.jar 2552
  • Running a frontend:
    docker run -d -it -e "AKKA_THIS_IP=192.168.99.100" -e "AKKA_SAMPLE_SEED_IP_1=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_1=2551" -e "AKKA_SAMPLE_SEED_IP_2=192.168.99.100" -e "AKKA_SAMPLE_SEED_PORT_2=2552" --name akka-sample-frontend akka-sample-cluster-frontend java -jar akka-sample-frontend.jar
    Note: Substitute 192.168.99.100 with IP of your Docker machine (docker-machine env).
  • Verifying it works: check out frontend logs (docker logs -f <container-id>). You should see stream of following:
    [INFO] [10/22/2016 19:04:39.132] [AkkaSampleCluster-akka.actor.default-dispatcher-19] [akka.tcp://AkkaSampleCluster@192.168.99.100:36900/user/factorialFrontend] 10! = 3628800 sender: akka.tcp://AkkaSampleCluster@192.168.99.100:2552/user/factorialBackend

Note: If you change the source, you will need to repackage and then rebuild relevant docker image.

Deploy to Google Container Engine:

  • Pushing images to Google Container Engine. Documentation how to push can be found here.

Note: Don’t forget to do gcloud init if you haven’t done yet.

```
docker tag akka-sample-cluster-backend gcr.io//akka-sample-cluster-backend

docker tag akka-sample-cluster-frontend gcr.io//akka-sample-cluster-frontend

gcloud docker push gcr.io//akka-sample-cluster-backend

gcloud docker push gcr.io//akka-sample-cluster-frontend
```
Background: with Akka Cluster every node should know IPs/hostnames and ports of cluster seed nodes. Containers in Google Container Engine have dynamic IPs making it impossible to manage a list of static IPs for seed nodes. Some possible solutions are to use etcd directly or via ConstructR that utilizes etcd as Akka extension. However, Kubernetes also have headless services. We are going to use it to expose all seed node IPs via DNS by having a headless service, which will be attached to each seed node through selector.

Headless service and Akka seed: In our case in Discovery-service.yaml we create a headless service named discovery-svc, set spec.clusterIP to None and attach selector name: seed-node to it. It means that any pod created from deployment marked by this selector will run instance of discovery-svc service as well. Each instance of this service will register its IP under discovery-svc.default.svc.cluster.local DNS entry, where default.svc.cluster.local is a default Kubernetes namespace. We use this feature for managing centralized list of seed node IPs. However, the way Akka seed works, all nodes also need to see the same first node in list of seed nodes. So we split all backend nodes into 2 groups: backend seed nodes (Backend-seed.yaml) and backend regular nodes (Backend-node.yaml). Backend seed nodes should always have fixed number of replicas in its deployment that equals to constructr.dns.num-of-seeds (conf) and selector seed-node. We also use ConstructR with our implementation of DnsCoordination that makes sure that any node can join only after the number of IPs registered in DNS equals to number of seed nodes. Once this number is reached, it always returns list of seed nodes sorted so all nodes see the same first node as required.

  • Deploy headleass service:
    kubectl create -f Discovery-service.yaml
    Note: In our case we mark all backend nodes as seed nodes, in real use case you need to mark only some of them as seed and have two different deployments.

  • Deploy a sampe application components:

  • Backend seed:
    kubectl create -f Backend-seed.yaml
  • Backend:
    kubectl create -f Backend-node.yaml
  • Frontend:
    kubectl create -f Frontend.yaml
  • Verifying it works:

Check out frontend logs (kubectl logs -f <container-id>). You should see stream of following:
[INFO] [10/22/2016 20:07:43.055] [AkkaSampleCluster-akka.actor.default-dispatcher-16] [akka.tcp://AkkaSampleCluster@10.0.2.14:34018/user/factorialFrontend] 50! = 30414093201713378043612608166064768844377641568960512000000000000 sender: akka.tcp://AkkaSampleCluster@10.0.2.12:2551/user/factorialBackend

Note Yaml deployment files declare 2 backend nodes and 1 frontend node to be started right away. It should be sifficient to see expected results. In general, to scale up/down:

kubectl scale --replicas=<target-number> deployment/cluster-backend-seed
kubectl scale --replicas=<target-number> deployment/cluster-frontend

For more kubectl options such as listing pods, deplyoments etc: Kubernetes cheatsheet.

Summary

This guide shows how deploy and run Akka Cluster application on Google Container Engine. While more steps should be taken to harden it for production use, it is a successful proof-of-concept that demonstrates Akka Cluster working on Google Container Engine.