Quarkus auf APPUiO
25.11.2019, Michael Gerber

Mit dem Release von Quarkus 1.0.0 ist Java für Microservices wieder attraktiv geworden. In diesem Blogpost zeige ich, wie man mittels Quarkus native Applikationen auf APPUiO betreiben kann.

⚛ Quarkus Projekt erstellen

Der einfachste Startpunkt, um ein Quarkus-Projekt zu erstellen, ist die Webseite https://code.quarkus.io. Dasselbe Konzept kennt man bereits von Spring Boot https://start.spring.io. Damit lässt sich einfach ein Projekt mit den gewünschten Bibliotheken erstellen.

code.quarkus.ig

Für mein Beispiel reichen die Extensions RESTEasy JAX-RS und SmallRye Health. RESTEasy JAX-RS, welches standardmässig dabei ist, wird für REST-Webservices gebraucht. SmallRye Health ist eine Extension, die hilft, Health-Checks einzubauen.

🏁 Applikation starten

Um Quarkus lokal zu starten, muss man lediglich Java 8 installiert haben. Mit dem Befehl ./mvnw compile quarkus:dev lässt sich die Applikation im Entwicklungsmodus starten. Änderungen im Java Code werden per Hot-Deployment direkt in der laufenden Applikation angepasst. Somit ist ein mühsamer Applikationsneustart nicht nötig 😃.

🌐 REST-Webservices erstellen

Als Beispiel Applikation erstellen wir einen REST-Webservice, welcher eine spezifische Stelle in der Fibonacci-Folge berechnet. Benannt ist die Folge nach Leonardo Fibonacci, der damit im Jahr 1202 das Wachstum einer Kaninchenpopulation beschrieb. Die Fibonacci-Folge lässt sich rekursiv, so wie iterativ berechnen. Um die CPU ein wenig auszureizen verwende ich die rekursive Variante.

package nxt;

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("/fibonacci")
public class FibonacciResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("{number}")
    public int fibonacci(@PathParam("number") Integer number) {
        if (number == 0) {
            return 0;
        }
        if (number == 1) {
            return 1;
        }
        return fibonacci(number - 2) + fibonacci(number - 1);
    }
}

Die Applikation lässt sich mittels curl wie folgt testen:

curl http://0.0.0.0:8080/fibonacci/10

🧪 Rest Service testen

Quarkus verwendet für REST-Webservice Tests die bekannte Bibliothek REST-assured. Der vorhin erstellte Webservice lässt sich mit dem folgenden Code testen:

package nxt;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class FibonacciResourceTest {

    @Test
    public void testEndpoint() {
        testFibonacci(0, 0);
        testFibonacci(1, 1);
        testFibonacci(2, 1);
        testFibonacci(3, 2);
        testFibonacci(4, 3);
        testFibonacci(5, 5);
        testFibonacci(6, 8);
    }

    private void testFibonacci(int number, int fibonacci) {
        given()
          .when().get("/fibonacci/" + number)
          .then()
             .statusCode(200)
             .body(is(String.valueOf(fibonacci)));
    }

}

Der Test kann mit dem Befehl ./mvnw test ausgeführt werden.

📦 Applikation auf APPUiO bauen

Damit die Applikation auf APPUiO, einer schweizer OpenShift Platform, läuft, muss sie zuerst gebaut werden. Das Quarkus-Team stellt dazu einen OpenShift Source-to-Image (S2I) Build zur Verfügung.

Mit dem folgenden Befehl kann man auf OpenShift einen entsprechenden Build erstellen und automatisch starten:

oc new-build \
  quay.io/quarkus/ubi-quarkus-native-s2i:19.2.1~https://gitlab.com/nxt/public/quarkus-fibonacci.git \
  --name=quarkus-fibonacci-build

Die GraalVM welche aus dem Java-Code ein Native Image erstellt, braucht leider ziemliche Rechenleistung. Damit der Build auf APPUiO die gebrauchten Ressourcen akquirieren kann, muss dieser noch entsprechend konfiguriert werden.

oc patch bc/quarkus-fibonacci-build \
  -p '{"spec":{"resources":{"requests":{"cpu":"0.5", "memory":"2Gi"},"limits":{"cpu":"4", "memory":"4Gi"}}}}'

Der obige Befehl setzt die Limite des Build-Jobs auf 4 CPUs und 4 Gigabyte RAM.

Das Schöne an APPUiO ist, dass die Ressourcen, die für den Build gebraucht werden, nicht von den eigenen Projekt-Ressourcen abgezogen werden. Somit kann man sein eigenes Projekt bis an das Limit ausreizen und immer noch Builds auf OpenShift durchführen 😎.

Der Nachteil von dieser Variante ist, dass das resultierende Docker Image eine Grösse von 600 MB hat 🤔. Dies liegt daran, dass das Docker Image die gesamte GraalVM beinhaltet. Mit einem Docker Multistage Build lässt sich dieses Problem elegant lösen.

Das folgende Dockerfile beinhaltet einen Multistage Build. In der ersten Stage wird das Native Image mit der GraalVM gebaut. In der zweiten Stage wird ein minimales Docker Image gebaut, basierend auf ubi-minimal.

## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/centos-quarkus-maven:19.2.1
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
USER root
RUN chown -R 1001 /usr/src/app
USER 1001
RUN mvn \
    -f /usr/src/app/pom.xml \
    package \
    -Pnative \
    -e \
    -B \
    -DskipTests \
    -Dmaven.javadoc.skip=true \
    -Dmaven.site.skip=true \
    -Dmaven.source.skip=true \
    -Djacoco.skip=true \
    -Dcheckstyle.skip=true \
    -Dfindbugs.skip=true \
    -Dpmd.skip=true \
    -Dfabric8.skip=true \
    -Dquarkus.native.enable-server=true

## Stage 2 : create the docker final image
FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY --from=0 /usr/src/app/target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

Auf OpenShift kann man Multistage Docker-Builds ausführen. Der erste der beiden folgenden Befehle erstellt den Image Stream für das neue schlanke Docker Image und der zweite erstellt den Docker Build.

oc create is quarkus-fibonacci
oc create -f - <<EOF
{
    "apiVersion": "build.openshift.io/v1",
    "kind": "BuildConfig",
    "metadata": {
        "labels": {
            "build": "quarkus-fibonacci-build"
        },
        "name": "quarkus-fibonacci-build"
    },
    "spec": {
        "output": {
            "to": {
                "kind": "ImageStreamTag",
                "name": "quarkus-fibonacci:latest"
            }
        },
        "resources": {
            "limits": {
                "cpu": "4",
                "memory": "4Gi"
            },
            "requests": {
                "cpu": "500m",
                "memory": "2Gi"
            }
        },
        "source": {
            "git": {
                "uri": "https://gitlab.com/nxt/public/quarkus-fibonacci.git"
            },
            "type": "Git"
        },
        "strategy": {
            "type": "Docker"
        }
    }
}
EOF

🚢 Applikation auf APPUiO veröffentlichen

Die zuvor gebaute Applikation kann mit den beiden folgenden Befehlen auf APPUiO veröffentlicht werden:

oc new-app --image-stream=quarkus-fibonacci:latest
oc expose svc quarkus-fibonacci

Der erste Befehl erstellt ein Deployment mit einem Pod und einem entsprechenden Service. Der zweite Befehl kreiert eine Route für den definierten Service, welche den Microservices öffentlich zugänglich macht.

Mit dem nachstehenden Befehl lässt sich die ausgerollte Applikation testen:

curl http://$(oc get route | grep quarkus-fibonacci | awk '{print $2}')/fibonacci/1

🩺 Health-Checks einrichten

Quarkus hat eine Extension, die Health-Checks von Haus aus anbietet. Wenn die Erweiterung noch nicht zu dem Projekt hinzugefügt worden ist, dann kann sie mit dem Befehl ./mvnw quarkus:add-extension -Dextensions="health" hinzugefügt werden. Quarkus erstellt dann automatisch Health-Check-Endpoints, die über die URL /health/live und /health/ready aufrufbar sind.

In OpenShift lassen sich die Health-Checks mit dem nachstehenden Befehl hinzufügen:

oc set probe dc/quarkus-fibonacci \
  --liveness \
  --get-url=http://:8080/health/live \
  --initial-delay-seconds=1

oc set probe dc/quarkus-fibonacci \
  --readiness \
  --get-url=http://:8080/health/ready \
  --initial-delay-seconds=1

⚖️ Autoscaling

Quarkus Applikation lassen sich extrem schnell starten. Diese Eigenschaft ist besonders praktisch, wenn das Autoscaling Feature genutzt wird. Damit lassen sich Pod’s bei Bedarf dynamisch starten und stoppen.

oc autoscale dc/quarkus-fibonacci --min 1 --max 2 --cpu-percent=80

Der obige Befehl fügt ein Autoscaling hinzu, bei dem bei Gebrauch ein zweiter Pod gestartet wird. Wenn die Last wieder abnimmt, wird der zusätzliche Pod automatisch wieder heruntergefahren.

Das Autoscaling lässt sich mit dem Tool ApacheBench von Apache testen.

ab -n 5000 -c2 http://$(oc get route | grep quarkus-fibonacci | awk '{print $2}')/fibonacci/30

Dieser Befehl sendet 5000 parallele Anfragen an den Microservice. OpenShift wird nach ein paar Sekunden den zweiten Pod hochfahren, welcher dann sofort einsatzbereit ist.

Autoscaling

📔 Fazit

Mit Quarkus kann man schnell und einfach einen Microservice mit Java erstellen, der alle Anforderungen erfüllt, um in einem OpenShift oder Kubernetes effizient betrieben zu werden.

Der ganze Code kann in unserem GitLab Repository studiert werden.