Kubernetes

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. Jolie microservices deployed inside a Docker container can be managed by Kubernetes as well. We are going to use what learnt in Docker section to deploy an easily-scalable application, with multiple containers running the same service behind a load balancer. To run the example a Kubernetes environment is needed, the easiest way to get it is to install Minikube.

Deploying "Hello" Jolie service in a container Docker

Let's make some modifications to helloservice.ol used in the previous Docker example:

include "runtime.iol"

interface HelloInterface {

RequestResponse:
    hello( string )( string )
}

execution{ concurrent }

inputPort Hello {
    Location: "socket://localhost:8000"
    Protocol: sodep
    Interfaces: HelloInterface
}

init {
    getenv@Runtime( "HOSTNAME" )( HOSTNAME )
}

main {
    hello( request )( response ) {
        response = HOSTNAME + ":" + request
    }
}

The HOSTNAME environment variable is set by Kubernetes itself and it's printed out to show what microservice instance is answering the request.

Creating a docker image

The Dockerfile needed to create a docker image of this microservice is the same seen in the Docker section:

FROM jolielang/jolie
EXPOSE 8000
COPY helloservice.ol main.ol
CMD jolie main.ol

Typing the following command in the console actually creates the image:

docker build -t hello .

Creating a Kubernetes Deployment

This image can now be wrapped in Pods, the smallest deployable units of computing that can be created and managed in Kubernetes. A Deployment describes in a declarative way the desired state of a ReplicaSet having the purpose to maintain a stable set of replica Pods running at any given time:

apiVersion: apps/v1
kind: Deployment
metadata:
    name: jolie-sample-deployment
    labels:
        app: jolie-sample
spec:
    replicas: 2
    selector:
        matchLabels:
            app: jolie-sample
    template:
        metadata:
            labels:
                app: jolie-sample
        spec:
            containers:
            - name: jolie-k8s-sample
                image: hello
                ports:
                - containerPort: 8000
                imagePullPolicy: IfNotPresent

To create the Deployment save the text above in jolie-k8s-deployment.yml file and type this command:

kubectl apply -f jolie-k8s-deployment.yml

After a few seconds you can see your pods up and running using this command:

kubectl get pods

Exposing Deployment by a Service

Now we have 2 running Pods, each one listening on port 8000, but with 2 issues: 1. they're reachable only from the internal Kubernetes cluster network; 2. they're ephemeral. As explained here, a Service is needed to expose the application in the right way. Following the Minikube tutorial, just type:

kubectl expose deployment jolie-sample-deployment --type=LoadBalancer --port=8000

to create such Service. The result can be verified with this command:

kubectl get services

and the output should be something like this:

NAME                                            TYPE                     CLUSTER-IP            EXTERNAL-IP     PORT(S)                    AGE
jolie-sample-deployment     LoadBalancer     10.109.47.147     <pending>         8000:30095/TCP     13s
kubernetes                                ClusterIP            10.96.0.1             <none>                443/TCP

The last step is to make the Service visible from your host going through a "minikube service":

minikube service jolie-sample-deployment
|-----------|-------------------------|-------------|-----------------------------|
| NAMESPACE   | NAME                      | TARGET PORT   | URL                           |
|-------------|---------------------------|---------------|-------------------------------|
| default     | jolie-sample-deployment   |               | http://<your_IP>:<ext_port>   |
| ----------- | ------------------------- | ------------- | ----------------------------- |

Invoking microservices from client

Now we a stable access door to our application, and it can be invoked by a client:

include "console.iol"

interface HelloInterface {
RequestResponse:
    hello( string )( string )
}

outputPort Hello {
    Location: "socket://<your_IP>:<ext_port>"
    Protocol: sodep
    Interfaces: HelloInterface
}


main {
    hello@Hello( "hello" )( response );
    println@Console( response )()
}

Each time you make a request typing:

jolie client.ol

your local is hit and the LoadBalancer redirects the request to one of the 2 available Pods running the service. Printing out the HOSTNAME variable makes visible the load balancing, showing which Pod is serving the response:

$ jolie client.ol
jolie-sample-deployment-655f8b759d-mq8cn:hello
$ jolie client.ol
jolie-sample-deployment-655f8b759d-bmzk7:hello
$ jolie client.ol
jolie-sample-deployment-655f8b759d-mq8cn:hello
$ jolie client.ol
jolie-sample-deployment-655f8b759d-bmzk7:hello