Containerization

How to use Docker, Kubernetes, and Helm Charts to deploy your vlingo/PLATFORM services.

The next lines show how to set up a reactive, scalable, event-driven application based on vlingo, being deployed on Kubernetes and packaged by Helm Chart.

Quick start with vlingo-xoom and Micronaut

First, we need a project structure which allows us to start building our application. That's when Micronaut CLI comes to play. It saves a lot of effort providing handful commands, specially one to generate initial development resources such as application files, directory structure, Dockerfile and, in this case, Maven configuration. Once CLI is installed, an application can be created by the following command:

mn create-app xoom-example --build maven

This command will create an application named xoom-example, setting Maven as the build management tool. Now, we can move forward and make some adjustments in the auto-generated project files.

Considering we intent to explore the multiple benefits of vlingo platform, mainly vlingo-http, we can add it just by replacing Netty, which is the default server used by Micronaut, with vlingo-server.

<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
<scope>compile</scope>
</dependency>

Therefore, the dependency above should be replaced by the following:

<dependency>
<groupId>io.vlingo</groupId>
<artifactId>vlingo-xoom-server</artifactId>
<version>x.y.z</version>
<scope>compile</scope>
</dependency>

Still editing the POM file, some package transformers are required to make the application JAR executable. Here it is:

<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>.SF</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>.DSA</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>.RSA</resource>
</transformer>

In order to avoid an invalid signature exception while executing the jar, those transformers should be added into the maven-shade-plugin configuration.

Building the Docker image

So far, we have properly updated Maven to build the deployment artifact. In addition, the Dockerfile, in the root folder, was already generated by Micronaut CLI:

FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim
COPY target/xoom-example-*.jar xoom-example.jar
EXPOSE 8080
CMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar xoom-example.jar

That means the image is ready to be built along with the executable JAR. Both tasks are performed here:

$ ./mvn clean package && docker build ./ -t xoom-example:latest

Now, let's tag and publish this local image into Docker hub.

$ ./docker tag xoom-example:latest [publisher]/xoom-example:latest
$ ./docker push [publisher]/xoom-example

Alternative without Xoom and Micronaut

Those previous steps are pretty similar for a vlingo application without Xoom and Micronaut. The executable JAR, including the dependency jars, can be generated with the following plugin configuration:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>io.vlingo.app.infra.Bootstrap</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>vlingo-app</finalName>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>io.vlingo.app.infra.Bootstrap</mainClass>
<classpathPrefix>dependency-jars/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>

The Dockerfile requires the jar with dependencies:

FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim
COPY target/vlingo-app-withdeps.jar vlingo-app.jar
EXPOSE 8082
CMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar vlingo-app.jar

Now, besides the application itself, the Docker image is ready to be built and published:

$ ./mvn clean package && docker build ./ -t vlingo-app:latest
$ ./docker tag vlingo-app:latest [publisher]/vlingo-app:latest
$ ./docker push [publisher]/vlingo-app

Kubernetes Deployment

Kubernetes is the chosen tool for container orchestration. In this scenario, it will run a single node cluster serving the xoom application. Whereas kubeadm is installed, the cluster initialization is showed below:

$ ./sudo kubeadm init --pod-network-cidr=192.168.0.0/16

Secondly, the settings folder should be mapped:

$ ./mkdir -p $HOME/.kube
$ ./sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ ./sudo chown $(id -u):$(id -g) $HOME/.kube/config

Kubernetes supports multiple networking model implementations. For now, we choose Calico. Its network policy configuration file can be added using kubectl, the command line tool for controlling Kubernetes clusters:

$ ./kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

Considering the single node cluster is the option for this example, the last step is to prepare the master node by removing taints which, in short, prevents a deployable unit (Pods) to run on it.

$ ./kubectl taint nodes --all node-role.kubernetes.io/master-

Management and deployment with Helm Chart

At this point, we need to tell Kubernetes what is the application desired state and how we want to expose our services, number of replicas, allocated resources... The simpler way is through Helm, a special tool for Kubernetes application management. It simplifies installation, upgrade, scaling and other common tasks. Getting started, let's create a chart, which is a collection of files inside of a directory. This is how it's made:

$ ./helm create xoom-example

The output looks like the following structure:

xoom-example/
Chart.yaml # A YAML file containing information about the chart
LICENSE # OPTIONAL: A plain text file containing the license for the chart
README.md # OPTIONAL: A human-readable README file
values.yaml # The default configuration values for this chart
values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file
charts/ # A directory containing any charts upon which this chart depends.
crds/ # Custom Resource Definitions
templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes

In this basic scenario, all we need to do is editing values.yaml , informing the Docker image repository, service type / port and number of replicas:

# Default values for xoom-example.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 3
image:
repository: [publisher]/xoom-example
...
service:
type: ClusterIP
port: 8080

Using lint, we can check if the chart is well-formed after the edition:

$ ./helm lint xoom-example
==> Linting xoom-example
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed

With template command, we can see all files that Helm will generate and install into Kubernetes:

# Source: xoom-example/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: RELEASE-NAME-xoom-example
labels:
helm.sh/chart: xoom-example-0.1.0
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
---
# Source: xoom-example/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: RELEASE-NAME-xoom-example
labels:
helm.sh/chart: xoom-example-0.1.0
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
---
# Source: xoom-example/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: RELEASE-NAME-xoom-example
labels:
helm.sh/chart: xoom-example-0.1.0
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
template:
metadata:
labels:
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
spec:
serviceAccountName: RELEASE-NAME-xoom-example
securityContext:
{}
containers:
- name: xoom-example
securityContext:
{}
image: "xoom-example:1.16.0"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}
---
# Source: xoom-example/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-xoom-example-test-connection"
labels:
helm.sh/chart: xoom-example-0.1.0
app.kubernetes.io/name: xoom-example
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['RELEASE-NAME-xoom-example:8080']
restartPolicy: Never

We finish the deployment step executing the install command:

$ ./helm install xoom-example

A new Pod is created by Kubernetes to hold the xoom-example app. You should check if it's running fine:

$ ./kubectl get pods
NAME READY STATUS RESTARTS AGE
xoom-example-765bf4c7b4-26z48 1/1 Running 0 64s

Also, it is recommended to check the application logs:

$ ./kubectl logs xoom-example-765bf4c7b4-26z48

If the application was started successfully, this is the log output:

2019-11-06 09:46:43 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2019-11-06 09:46:43 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2019-11-06 09:46:43 [main] INFO org.hibernate.Version - HHH000412: Hibernate Core {5.4.7.Final}
2019-11-06 09:46:43 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2019-11-06 09:46:43 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2019-11-06 09:46:44 [main] INFO io.vlingo.VlingoScene - New scene created: __defaultStage
2019-11-06 09:46:44 [pool-3-thread-4] INFO i.v.a.Logger - ServerRequestResponseChannelActor: OPENING PORT: 8080
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Server vlingo-http-server is listening on port: 8080 started in 47 ms
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Resource: Account Endpoint Resource v1.1
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Action: id=0, method=GET, uri=/v1/accounts/{id}, to=dynamic1(Long id)
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Action: id=1, method=PUT, uri=/v1/accounts/{id}, to=dynamic2(Long id)
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Action: id=2, method=DELETE, uri=/v1/accounts/{id}, to=dynamic3(Long id)
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Action: id=3, method=GET, uri=/v1/accounts, to=dynamic4()
2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Action: id=4, method=POST, uri=/v1/accounts, to=dynamic5()
2019-11-06 09:46:44 [main] INFO io.vlingo.VlingoServer -
░▒░▒░ _ _
░▒░▒░ | (_)
▒░▒░▒ ░▒░▒░ __ _| |_ _ __ __ _ ___
▒░▒░▒ ░▒░▒░ \ \ / / | | '_ \ / _` |/ _ \
▒░▒░▒ \ V /| | | | | | (_| | (_) |
░▒░▒░ \_/ |_|_|_| |_|\__, |\___/
__/ | Xoom v0.1.0
|___/
2019-11-06 09:46:44 [main] INFO io.vlingo.VlingoServer - Started embedded Vlingo Xoom server at http://localhost:8080
2019-11-06 09:46:44 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 1846ms. Server Running: http://localhost:8080

Packaging and versioning

Helm also supports a packaging and versioning mechanism, that is, a set of commands that allows us to package the Chart structure and files to make it collaborative. First, an index.yaml file should be created based on a Git repository, that will be the chart repository:

$ ./helm repo index chart-repo/ --url https://<username>.github.io/chart-repo

Next, the remote repository is added:

$ ./helm repo add chart-repo https://<username>.github.io/chart-repo

At last, enable the chart installation from the repository:

$ ./helm install my-repo/hello-world --name=hello-world

More information

Find a complete code example in our Github account built on a DDD microservices architecture, combining Kubernetes, Helm Chart and vlingo-xoom.