Native-Image-Workshop

05 : Deployment Options for GraalVM Native Images

Estimated time: 15 minutes

Overview

In the previous part we’ve build a small microservice that can respond to the HTTP traffic. Let’s continue using it and try to explore some options how we can deploy it differently.


Note

A Note on Building on OSX

If you use a Mac you will need to build your native images inside a linux Docker container if you want to deploy them inside Docker containers. If you stop and think about it for a second, that makes perfect sense, right? You build on a mac, you get a mac executable.

You will, from time to time, forget this and then you will see the following error when you deploy your app into a docker container:

Note Error Message

standard_init_linux.go:211: exec user process caused "exec format error"

Note: Also currently building staticaly linked images on Mac / OSX is not currently supported, but you can build in Linux on Docker. So if you have a Mac, use the docker builds.


Building Our Web Micro-Service

Locate the sample project we created - there’s a copy of this in the current folder:

User Input Shell Script

cd primes-web

If you haven’t done it yet, build the app:

User Input Shell Script

./gradlew build

Build the native image of it again (let’s not use the upxed version, if you used the -k option to keep the backup, the executable should be in build/native-image/application.~).

User Input Shell Script

./gradlew nativeImage

And locate the binary of the app (if needed move the upx backup file: mv build/native-image/application.~ build/native-image/application):

User Input Shell Script

./build/native-image/application

Explore the output of ldd to check which libraries it’s linked against:

User Input Shell Script

ldd ./build/native-image/application

Packing in a Docker Container

Let’s package it into a docker image. One of the benefits of the native image is that it offers smaller deployment sizes, so we’ll use a slim image.

NOTE: We will use a docker based build, as on a mac the binary built will not work when added to the linux docker container - see the note earlier. We will use a 2-step docker build. The first will build the binary and the second will copy the binary from the image built in step 1.

Create a Dockerfile.slim file with the following:

User Input Docker

FROM oracle/graalvm-ce:20.3.0-java11 AS builder

# Here we are using GraalVM CE - but you don't have to.
# There is not yet a public GraalVM EE docker image that you can download,
# but you can make your own easily

RUN gu install native-image
RUN mkdir app
COPY . /app
WORKDIR app

# Build the Jar
RUN ./gradlew build

# Build the Native Image
RUN ./gradlew nativeImage

# Step 2 : build the image that will run

FROM oraclelinux:8-slim
COPY --from=builder app/build/native-image/application app
ENTRYPOINT ["/app"]

Build the image:

User Input Shell Script

docker build -f Dockerfile.slim -t primes-web:slim .

Install the dive utility to inspect the images: https://github.com/wagoodman/dive#installation

Run it on the new image and explore the output:

User Input Shell Script

dive primes-web:slim

The image doesn’t have much except our application. We can still run it and access the app on port 8080.

User Input Shell Script

docker run --rm -p 8080:8080 primes-web:slim

# visit the following URL in your browser
# http://localhost:8080/primes/random/200

Tiny Docker Containers

However, we can do even better. One way is to link all necessary libraries statically, except libc. Which we’ll depend on from the environment.

Edit the build.gradle file to configure the native-image use through the Micronaut’s Gradle plugin. Add the following section:

User Input File

nativeImage {
  args("-H:+StaticExecutableWithDynamicLibC")
}

We will use Distroless as our base. Distroless is a very minimal container distro. We can now package our app into an even smaller docker image. Let’s call this Dockerfile.distroless

User Input Docker

FROM oracle/graalvm-ce:20.3.0-java11 AS builder

# Here we are using GraalVM CE - but you don't have to.
# There is not yet a public GraalVM EE docker image that you can download,
# but you can make your own easily

RUN gu install native-image
RUN mkdir app
COPY . /app
WORKDIR app

# Build the Jar
RUN ./gradlew build
# Build the Native Image
RUN ./gradlew nativeImage

FROM gcr.io/distroless/base
COPY --from=builder app/build/native-image/application app
ENTRYPOINT ["/app"]

Build the image and explore it with dive:

User Input Shell Script

docker build -f Dockerfile.distroless -t primes-web:distroless .

Look at the image efficiency score!

User Input Shell Script

dive primes-web:distroless

Running it is just as simple:

User Input Shell Script

docker run --rm -p 8080:8080 primes-web:distroless

Use the app a few times, explore the docker container stats in docker stats or some monitoring service like cadvisor.

Bonus Points!

As a bonus exercise you can build a statically linked native image which is completely self-contained and can be used in the empty docker image: FROM scratch. This is possible on Linux only, and requires some prerequisites described in the docs.

TLDR;

In a nutshell you can pass --static --libc=musl and get a statically linked binary.

Please take your time to explore it.

Next, we’ll try to explore various options for configuring the runtime for native images.