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.
First, we need a project structure that allows us to start building our application. That's when VLINGO/XOOM Starter comes to play. It saves a lot of effort providing a web/graphical user interface to generate initial development resources such as application files, directory structure, Dockerfile
and much more. Once it is installed, you can use the project generator wizard running the following command:
$ ./xoom gui
This command will open your preferred browser. Just fill in the wizard steps so the project will be generated and ready to start the development.
If you choose either Docker or Kubernetes on the deployment step, a Dockerfile
will be placed in the root folder:
FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slimCOPY target/xoom-example-*.jar xoom-example.jarEXPOSE 8080CMD 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 through a single Starter CLI command:
$ ./xoom docker package
Now, let's tag and publish this local image into Docker Hub.
$ ./xoom docker push
You can find more information on xoom docker push
and other containerization shortcut commands here.
The previous steps are pretty similar for a VLINGO/PLATFORM service or application without VLINGO/XOOM. 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-slimCOPY target/vlingo-app-withdeps.jar vlingo-app.jarEXPOSE 8082CMD 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 is the chosen tool for container orchestration. In this scenario, it will run a single node cluster serving the VLINGO/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-
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 chartLICENSE # OPTIONAL: A plain text file containing the license for the chartREADME.md # OPTIONAL: A human-readable README filevalues.yaml # The default configuration values for this chartvalues.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml filecharts/ # A directory containing any charts upon which this chart depends.crds/ # Custom Resource Definitionstemplates/ # 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: 3image:repository: [publisher]/xoom-example...service:type: ClusterIPport: 8080
Using lint
, we can check if the chart is well-formed after the addition:
$ ./helm lint xoom-example==> Linting xoom-example[INFO] Chart.yaml: icon is recommended1 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.yamlapiVersion: v1kind: ServiceAccountmetadata:name: RELEASE-NAME-xoom-examplelabels:helm.sh/chart: xoom-example-0.1.0app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEapp.kubernetes.io/version: "1.16.0"app.kubernetes.io/managed-by: Helm---# Source: xoom-example/templates/service.yamlapiVersion: v1kind: Servicemetadata:name: RELEASE-NAME-xoom-examplelabels:helm.sh/chart: xoom-example-0.1.0app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEapp.kubernetes.io/version: "1.16.0"app.kubernetes.io/managed-by: Helmspec:type: ClusterIPports:- port: 8080targetPort: httpprotocol: TCPname: httpselector:app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAME---# Source: xoom-example/templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:name: RELEASE-NAME-xoom-examplelabels:helm.sh/chart: xoom-example-0.1.0app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEapp.kubernetes.io/version: "1.16.0"app.kubernetes.io/managed-by: Helmspec:replicas: 3selector:matchLabels:app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEtemplate:metadata:labels:app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEspec:serviceAccountName: RELEASE-NAME-xoom-examplesecurityContext:{}containers:- name: xoom-examplesecurityContext:{}image: "xoom-example:1.16.0"imagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 8080protocol: TCPlivenessProbe:httpGet:path: /port: httpreadinessProbe:httpGet:path: /port: httpresources:{}---# Source: xoom-example/templates/tests/test-connection.yamlapiVersion: v1kind: Podmetadata:name: "RELEASE-NAME-xoom-example-test-connection"labels:helm.sh/chart: xoom-example-0.1.0app.kubernetes.io/name: xoom-exampleapp.kubernetes.io/instance: RELEASE-NAMEapp.kubernetes.io/version: "1.16.0"app.kubernetes.io/managed-by: Helmannotations:"helm.sh/hook": test-successspec:containers:- name: wgetimage: busyboxcommand: ['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 podsNAME READY STATUS RESTARTS AGExoom-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.H2Dialect2019-11-06 09:46:44 [main] INFO io.vlingo.VlingoScene - New scene created: __defaultStage2019-11-06 09:46:44 [pool-3-thread-4] INFO i.v.a.Logger - ServerRequestResponseChannelActor: OPENING PORT: 80802019-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 ms2019-11-06 09:46:44 [pool-3-thread-3] INFO i.v.a.Logger - Resource: Account Endpoint Resource v1.12019-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 v1.3.0|___/2019-11-06 09:46:44 [main] INFO io.vlingo.VlingoServer - Started embedded Vlingo Xoom server at http://localhost:80802019-11-06 09:46:44 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 1846ms. Server Running: http://localhost:8080
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
Find a complete code example on github, built on a DDD microservices architecture, combining Kubernetes, Helm Chart and VLINGO/XOOM.