Quarkus OpenShift Quickstart – A Bonuslab for the Cloud Native Developer Workshop

Quarkus (https://quarkus.io/) – A Supersonic Subatomic Java has recently been announced from some Red Hat Developers. The idea is to build a Java runtime that is native to Kubernetes and small and fast so it can also used in serverless scenarios or on very small edge devices.

With Quarkus it is possible to create a “native” build that produces a small self-contained binary that can run in any container image.

The goal of this blog post is to provide a simple hands-on lab for Openshift to get started with Quarkus and to do something useful.

I must admit that I shamelessly build upon work done by my great colleagues, namely from Karsten Gresch from this post https://medium.com/@gresch/quarkus-native-builds-with-openshift-s2i-9474ed4386a1

The source code and the application code I got from   http://www.mastertheboss.com/soa-cloud/quarkus/getting-started-with-quarkus

Ok, lets get our hands dirty with it!

Importing the source code

I put the code in my github repository “quarkus-demo”: https://github.com/iboernig/quarkus-demo

If you are working with the Eclipse Che based cloud native workshop, make sure you are in the /projects/labs/ directory:

$ cd /projects/labs

Then clone the git repository:

$ git clone  https://github.com/iboernig/quarkus-demo.git

The quarkus-demo directory should now show up in the project explorer on the left.

Here you can left-click on that repository and convert to a maven project (same as for the other projects). This is not a necessary step, since we will not use the local maven but will use OpenShift s2i to build and deploy the application.

Examining the source code

In the src/main/java/org.acme.quickstart directory we at first the simple REST Endpoint example. It contains the class GreetingResource.java:

package org.acme.quickstart;


import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(@PathParam("name") String name) {
        return service.greeting(name);
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

and the simple GreetingService.java:

package org.acme.quickstart;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

   public String greeting(String name) {
      return "hello " + name;
   }
}

This will produce a simple “hello” output on the /hello URL and a short greeting with a name on /hello/greeting/<name> .

Something useful

But now we look at a little more interesting example. Lets do a simple user registration. Something that is missing in our Coolstuff store.

Lets start with the Class Person.java:

package com.sample;
 
import java.util.Objects;
 
public class Person {
 
    String name;
    String surname;
 
    public Person( ) {  }
 
    public String getName() {
        return name;
    }
    public String getSurname() {  return surname;  }
    public void setName(String name) {
        this.name = name;
    }
    public void setSurname(String surname) {
        this.surname = surname;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(surname, person.surname);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, surname);
    }
}

And an endpoint to safe this into a collection. RESTEndpoint.java:

package com.sample;
 
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;
 
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
@Path("/persons")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class RESTEndpoint {
 
    private Set<Person> persons = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
 
    @GET
    public Set<Person> list() {
        return persons;
    }
 
    @POST
    public Set<Person> add(Person person) {
        System.out.println("Saving: " +person);
        persons.add(person);
        return persons;
    }
 
}

Note: The files are already in the repository. This is just for a review.

 Adding an UI

Adding HTML UI is also quite easy with quarkus. Lets turn to the src/main/resources/META-INF/resources/ directory. Here you can find the following HTML code (with AngularJS for displaying the data) in the index.html:

 <!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Quarkus REST service</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/wingcss/0.1.8/wing.min.css"/>
    <!-- Load AngularJS -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
    <script type="text/javascript">
      var app = angular.module("PersonManagement", []);
 
      //Controller Part
      app.controller("PersonManagementController", function ($scope, $http) {
 
        //Initialize page with empty data
        $scope.persons = [];
 
        $scope.form = {
          name: "",
          surname: ""
        };
 
        //Now load the data from server
        _refreshPageData();
 
        //HTTP POST methods for add data
        $scope.add = function () {
          var data = { "name": $scope.form.name, "surname": $scope.form.surname };
 
          $http({
            method: "POST",
            url: '/persons',
            data: angular.toJson(data),
            headers: {
              'Content-Type': 'application/json'
            }
          }).then(_success, _error);
        };
 
 
        //HTTP GET- get all persons collection
        function _refreshPageData() {
          $http({
            method: 'GET',
            url: '/persons'
          }).then(function successCallback(response) {
            $scope.persons = response.data;
          }, function errorCallback(response) {
            console.log(response.statusText);
          });
        }
 
        function _success(response) {
          _refreshPageData();
          _clearForm();
        }
 
        function _error(response) {
          alert(response.data.message || response.statusText);
        }
 
        //Clear the form
        function _clearForm() {
          $scope.form.name = "";
          $scope.form.surname = "";
        }
      });
    </script>
</head>
<body ng-app="PersonManagement" ng-controller="PersonManagementController">
 
<div class="container">
    <h1>Quarkus REST Service</h1>
 
    <form ng-submit="add()">
        <div class="row">
            <div class="col-6"><input type="text" placeholder="Name" ng-model="form.name" size="60"/></div>
        </div>
        <div class="row">
            <div class="col-6"><input type="text" placeholder="Surname" ng-model="form.surname" size="60"/></div>
        </div>
        <input type="submit" value="Save"/>
    </form>
 
    <h3>Person List</h3>
    <div class="row">
        <div class="col-4">Name</div>
        <div class="col-8">Surname</div>
    </div>
    <div class="row" ng-repeat="person in persons">
        <div class="col-4">{{ person.name }}</div>
        <div class="col-8">{{ person.surname }}</div>
    </div>
</div>
 
</body>
</html>

Now its time to get it running!

Let OpenShift do its work!

First of all, make sure that you are still logged in with the right user into OpenShift:

$ oc whoami
user1

Now check that you are in the right project

$ oc project
coolstore-01

(If you trying this outside the cloud native workshop, simple make sure that you have a project and are logged in into OpenShift).

The Quarkus S2i (Source-to-image) builder image is not yet a part of OpenShift by default, but it is available as a builder image on quay.io and needs to be called directly:

$ oc new-app quay.io/quarkus/centos-quarkus-native-s2i~https://github.com/iboernig/quarkus-demo.git --name=quarkdemo

Now OpenShift starts a build process and you can follow the progress here:

$ oc logs -f bc/quarkdemo

This is progressing quite slow and in the normal workshop scenario it finally stops and fails. Can you tell why?

Compiling to native needs a lot of resources and each project has limits. Either increase the limit yourself (if you are a cluster-admin) or talk to you instructor. He can help with increasing or removing the limit.

Ok, lets go for the next try. Restart the build:

$ oc start-build bc/quarkdemo

Now it progresses much faster and the ready built image is pushed to the local registry.

look for the upcoming pods:

$ oc get pods 
NAME                READY     STATUS      RESTARTS   AGE
quarkdemo-1-626xd   1/1       Running     0          2s
quarkdemo-1-build   0/1       OOMKilled   0          5m
quarkdemo-2-build   0/1       Completed   0          5s

Its running! And you also can see, why the first build failed!

Now expose the service and get a route:

$ oc expose svc quarkdemo
route.route.openshift.io/quarkdemo exposed
$ oc get routes
NAME        HOST/PORT                                                   PATH      SERVICES    PORT       TERMINATION   WILDCARD
quarkdemo   quarkdemo-<project>-<serverurl>   quarkdemo  8080-tcp                 None

Now you can look at the URL in your browser:

You can add names ad see what happens!

Also the simple rest service works with this binary:

$ curl http://<route>/hello
hello
$ curl http://<route>/hello/greeting/ingo
hello ingo

Very nice. But what makes Quarkus so special?

Its very small. Only 19 MB. And it doesn’t use a JVM to run: its just a simple binary:

$ oc get pods
NAME                READY     STATUS      RESTARTS   AGE
quarkdemo-1-626xd   1/1       Running     0          2h
quarkdemo-1-build   0/1       OOMKilled   0          3h
quarkdemo-2-build   0/1       Completed   0          2h
$ oc rsh quarkdemo-1-626xd
sh-4.2$ ls -lh
total 22M
-rwxr-xr-x. 1 quarkus    root 22M Apr  8 14:38 quarkus-quickstart-1.0-SNAPSHOT-runner
-rw-r--r--. 1 1000660000 root 337 Apr  8 14:39 quarkus.log

All in one 22 MB binary. Also impressive:

sh-4.2$ cat quarkus.log 
2019-04-08 14:39:02,410 quarkdemo-1-626xd quarkus-quickstart-1.0-SNAPSHOT-runner[7] INFO  [io.quarkus] (main) Quarkus 0.12.0 started in 0.009s. Listening on: http://[::]:8080
2019-04-08 14:39:02,410 quarkdemo-1-626xd quarkus-quickstart-1.0-SNAPSHOT-runner[7] INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb]

That’s a startup time in the ms range! So it makes difference!  Hope you enjoyed that lab!

 

 

Run a defined workload on a Docker or Kubernetes environment

Sometimes you want to stress test a container environment or you are measuring CPU and memory in such an environment and you want to test, of your metrics reporting is correct.

Introducing docker-stress

This small tool allows to gauge metrics reporting using the  simple command line utility “stress” inside a docker container.

The needed Dockerfile can be found in this repository:

https://github.com/iboernig/docker-stress/

One can use this tool on a local container host or on a Kubernetes cluster. I will show podman for local deployment and OpenShift for the cluster deployment here.

Local usage with podman (or docker if you wish)

For the local deployment i will use “podman” here. As a proud Fedora user, I use the native and easiest tool that can be used by a normal user with no special rights to work with containers locally. If you need more information to podman have a look here: https://podman.io/.

If you prefer docker to podman, feel free to use docker. the command line is exactly the same.

To start, clone the repository:

[iboernig@t470 Projects]$ git clone https://github.com/iboernig/docker-stress.git
[iboernig@t470 Projects]$ cd docker-stress/

Build locally using podman (you can use “docker” with the same arguments, if you do not have podman)

[iboernig@t470 Projects]$ podman build -t stress .
STEP 1: FROM fedora:latest
STEP 2: RUN yum -y install stress procps-ng 
--> Using cache 8f2c95619aeb7799ae9806fd94d83605f894a458d344659910240092ed7efc4d
STEP 3: FROM 8f2c95619aeb7799ae9806fd94d83605f894a458d344659910240092ed7efc4d
^[[OSTEP 4: ENV CPU_LOAD=1 MEM_LOAD=1 MEM_SIZE=256M
--> Using cache 401365dae085f9a53d39e2eb6a2a14e6b21129d0e19d9f75a16503fc780b2bd5
STEP 5: FROM 401365dae085f9a53d39e2eb6a2a14e6b21129d0e19d9f75a16503fc780b2bd5
STEP 6: CMD stress --cpu $CPU_LOAD --vm $MEM_LOAD --vm-bytes $MEM_SIZE
--> Using cache 8f71320d0ecad024efcbf98be0504eaf3354c08e6469e76a028c6f3b55e0bc95
STEP 7: COMMIT

and then run it:

[iboernig@t470 docker-stress]$ podman run -it -rm stress 
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 1 vm, 0 hdd

You can change the parameters by setting the enviroment variables:

[iboernig@t470 docker-stress]$ podman run -it -rm -e CPU_LOAD=2 -e MEM_LOAD=1 -e MEM_SIZE=512M stress
stress: info: [1] dispatching hogs: 2 cpu, 0 io, 1 vm, 0 hdd

Note that each number of CPU_LOAD and each number of MEM_LOAD allocates one thread. The memory in the variable MEM_SIZE is the size one MEM_LOAD thread consumes. So if you use more than one, you will consume a multiple of MEM_SIZE memory. For more infomation see “man stress”.

Cluster deployment in OpenShift

In OpenShift you only have to define a new app based on this repository:

oc new-app https://github.com/iboernig/docker-stress.git

OpenShift then pulls the repository, starts a docker build and deploys the image in one go.

If you need to tune the parameters you can do so by adding the environment variable to the deplyoment config. Either in the webconsole or by changing the deploymentconfig manually:

oc set env dc/docker-stress CPU_LOAD=2 MEM_LOAD=1 MEM_SIZE=512M

Happy stressing!