Tuesday, October 30, 2018

Running Reactive Spring Boot on GraalVM in Docker

GraalVM is an open source polyglot VM which makes it easy to mix and match different languages such as Java, Javascript and R. It has the ability (with some restrictions) to compile code to native executables. This of course offers great performance benefits. Recently, GraalVM Docker files and images have become available. See here.

Since Spring Boot is a popular Java framework and reactive (non blocking) RESTful services/clients implemented in Spring Boot are also interesting to look at, I thought; lets combine those and produce a Docker image running a reactive Spring Boot application on GraalVM.

I've used and combined the following
As a base I've used the code provided in the following Git repository here. In the 'complete' folder (the end result of the tutorial) is a sample Reactive RESTful Web Service and client.
The reactive Spring Boot RESTful web service and client

When looking at the sample, you can see how you can implement a non-blocking web service and client. Basically this means you use;
  • org.springframework.web.reactive.function.server.ServerRequest and ServerResponse and instead of the org.springframework.web.bind.annotation.RestController
  • Mono<ServerResponse> for the response of the web service
  • for a web service client you use org.springframework.web.reactive.function.client.ClientResponse and Mono<ClientResponse> for getting a response
  • since you won't use the (classic blocking) RestController with the RequestMapping annotations, you need to create your own configuration class which defines routes using  org.springframework.web.reactive.function.server.RouterFunctions
Since the response is not directly a POJO, it needs to be converted into one explicitly like with res.bodyToMono(String.class). For more details look at this tutorial or browse this repository

Personally I would have liked to have something like a ReactiveRestController and keep the rest (pun intended) the same. This would make refactoring to reactive services and clients more easy.

GraalVM

GraalVM is a polyglot VM open sourced by Oracle. It has a community edition and enterprise edition which provides improved performance (a smaller footprint) and better security (sandboxing capabilities for native code) as indicated here. The community edition can be downloaded from GitHub and the enterprise edition from Oracle's Technology Network. Support for GraalVM for Windows is currently still under development and not released yet. A challenge for Oracle with GraalVM will be to keep the polyglot systems it supports up to date version wise. This already was a challenge with for example the R support in Oracle database and Node support in Application Container Cloud Service. See here.

When you download GraalVM CE you'll get GraalVM with a specific OpenJDK 8 version (for GraalVM 1.0.0-rc8 this is 1.8.0_172). When you download GraalVM EE from OTN, you'll get Oracle JDK 8 of the same version.

To see which components are available, you can do:

bash-4.2# gu available
Downloading: Component catalog
ComponentId              Version             Component name
----------------------------------------------------------------
python                   1.0.0-rc8           Graal.Python
R                        1.0.0-rc8           FastR
ruby                     1.0.0-rc8           TruffleRuby

GraalVM and LLVM

GraalVM supports LLVM. LLVM is a popular toolset to provide language agnostic compilation and optimization of code for specific platforms. LLVM is one of the reasons many programming languages have starting popping up recently. Read more about LLVM here or visit their site here. If you can compile a language into LLVM bitcode or LLVM Intermediate Representation (IR), you can run it on GraalVM (see here). The LLVM bitcode is additionally optimized by GraalVM to receive even better results.

GraalVM and R

GraalVM uses FastR which is based on GNU-R, the reference implementation of R. This is an alternative implementation of the R language for GraalVM and thus not actual R! For example: 'support for dplyr and data.table are on the way'. Read more here. Especially if you use exotic packages in R, I expect there to be compatibility issues. It is interesting to compare the performance of FastR on GraalVM to compiling R code to LLVM instructions and run that on GraalVM (using something like RLLVMCompile). Haven't tried that though. GraalVM seems to have momentum at the moment and I'm not so sure about RLLVMCompile.

Updating the JVM of GraalVM

You can check out the following post here for building GraalVM with a JDK 8 version. This refers to documentation on GitHub here.

"Graal depends on a JDK that supports a compatible version of JVMCI (JVM Compiler Interface). There is a JVMCI port for JDK 8 and the required JVMCI version is built into the JDK as of JDK 11 (build 20 or later)."

I have not tried this but it seems thus relatively easy to compile GraalVM from sources with support for a different JDK.

GraalVM in Docker

Oracle has recently provided GraalVM as Docker images and put the Dockerfile's in their Github repository. See here. These are only available for the community edition. Since the Dockerfiles are provided on GitHub, it is easy to make your own GraalVM EE images if you want (for example want to test with GraalVM using Oracle JDK instead of OpenJDK).

To checkout GraalVM you can run the container like:

docker run -it oracle/graalvm-ce:1.0.0-rc8 bash

Spring Boot in GraalVM in Docker

How to run a Spring Boot application in Docker is relatively easy and described here. I've run Spring Boot applications on various VM's also and described the process on how to achieve this here. As indicated above, I've used this Ubuntu Development VM.

sudo apt-get install maven
git clone https://github.com/spring-guides/gs-reactive-rest-service.git
cd gs-reactive-rest-service/complete

Now create a Dockerfile:

FROM oracle/graalvm-ce:1.0.0-rc8
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Edit the pom.xml file

Add to the properties tag a prefix variable:

        <properties>
                <java.version>1.8</java.version>
                <docker.image.prefix>springio</docker.image.prefix>
        </properties>

Add a build plugin

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                        <plugin>
                                <groupId>com.spotify</groupId>
                                <artifactId>dockerfile-maven-plugin</artifactId>
                                <version>1.3.6</version>
                                <configuration>                                        <repository>${docker.image.prefix}/${project.artifactId}</repository>
                                        <buildArgs><JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                                        </buildArgs>
                                </configuration>
                        </plugin>
                </plugins>
        </build>

Now you can do:

mvn clean package
mvn dockerfile:build

And run it:

docker run -p 8080:8080 -t springio/gs-reactive-rest-service:latest

It’s as simple as that!

No comments:

Post a Comment