Containerizing Ballerina GraalVM Executable
This article is written using Ballerina Swan Lake Update 7(2201.7.0). We will explore how to containerize a GraalVM native executable for a ballerina application.
Join the DZone community and get the full member experience.
Join For FreeWelcome back to the series exploring the synergy between Ballerina and GraalVM. In the previous article, ‘Ballerina Code to GraalVM Executable,’ we delved into the seamless integration of Ballerina and GraalVM, witnessing how Ballerina applications can build GraalVM native executable and achieve improved performance and reduced memory consumption. In this continuation, we take the next step in our journey, exploring how to containerize a Ballerina GraalVM executable. If you have not read the previous article, I recommend you do so before continuing with this one.
We will use the same Conference Service
application to build a Docker image containing the GraalVM executable. The code for this application can be found in the below link:
A RESTful conference service which is written in Ballerina. — GitHub — TharmiganK/conference-service-ballerina: A RESTful…github.com
We will be looking into the following ways to create the Docker image.
- Using a custom Docker file.
- Using the Ballerina Code to Cloud feature.
Using a Custom Docker File
As we know already, the GraalVM native executable is platform-dependent. If you are a Linux user, then you can build the GraalVM executable locally and pass it to a Docker with the simplest slim container. If you are using macOS or Windows to build a Docker image containing the GraalVM executable, then you have to build the executable in a Docker container.
In this post, I am using a macOS, so I need to build the executable in a Docker container that has the GraalVM native image tool. The GraalVM community already has container images with the native-image tool. The images can be found on the GraalVM container page. Since Ballerina Swan Lake Update 7 works with Java11, I have chosen this image: ghcr.io/graalvm/native-image:ol8-java11–22.3.3
.
Let’s start by building the application and obtaining the JAR file.
$ bal build Compiling source tharmigan/conference_service:1.0.0 Generating executable target/bin/conference_service.jar
Use the following Docker file to build the GraalVM executable in the graalvm/native-image
container and run the executable in adebian:stable-slim
container.
FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3 as build
WORKDIR /app/build
COPY target/bin/conference_service.jar .
RUN native-image -jar conference_service.jar --no-fallback
FROM debian:stable-slim
WORKDIR /home/ballerina
COPY --from=build /app/build/conference_service .
CMD echo "time = $(date +"%Y-%m-%dT%H:%M:%S.%3NZ") level = INFO module = tharmigan/conference_service message = Executing the Ballerina application" && "./conference_service"
$ docker build . -t ktharmi176/conference-service:1.0.0
Use the following Docker compose file to run the conference_service
and the mock country_service
in the host network.
version: '2'
services:
conference-service:
image: 'ktharmi176/conference-service:1.0.0'
ports:
- '8102:8102'
volumes:
- ./Config.toml:/home/ballerina/Config.toml
depends_on:
country-service:
condition: service_started
network_mode: "host"
country-service:
image: 'ktharmi176/country-service:latest'
hostname: country-service
container_name: country-service
ports:
- '9000:9000'
network_mode: "host"
Check the image names and run the following command:
$ docker compose up
Now, the two services have been started. Test the service using the request.http
file.
Using the Ballerina Code to Cloud Feature
Default Mode
The Code to Cloud feature in Ballerina enables developers to quickly deploy their Ballerina applications to cloud platforms without the need for extensive configuration or manual setup. It aims to reduce the complexity of cloud-native development and streamline the deployment process.
We can simply run the following command to build the GraalVM executable in a Docker container.
$ bal build --graalvm --cloud=docker Compiling source tharmigan/conference_service:1.0.0 Generating artifacts Building the native image. This may take a while [+] Building 331.1s (13/13) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 439B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for gcr.io/distroless/base:latest 3.0s => [internal] load metadata for ghcr.io/graalvm/native-image:ol8-java11-22.3.3 3.7s => [build 1/4] FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3@sha256:c0b4d9c31013d4fd91c4dec25f8772602e851ee67b8510d21bfdab532da4c17c 0.0s => [stage-1 1/3] FROM gcr.io/distroless/base@sha256:73deaaf6a207c1a33850257ba74e0f196bc418636cada9943a03d7abea980d6d 0.0s => [internal] load build context 0.4s => => transferring context: 42.65MB 0.4s => CACHED [stage-1 2/3] WORKDIR /home/ballerina 0.0s => CACHED [build 2/4] WORKDIR /app/build 0.0s => [build 3/4] COPY conference_service.jar . 0.1s => [build 4/4] RUN native-image -jar conference_service.jar -H:Name=conference_service --no-fallback -H:+StaticExecutableWithDynamicLibC 326.3s => [stage-1 3/3] COPY --from=build /app/build/conference_service . 0.3s => exporting to image 0.3s => => exporting layers 0.3s => => writing image sha256:4a6e1223a8d5a0446b688b110522bdc796027bfc1bc4fe533c62be649900ee05 0.0s => => naming to docker.io/library/conference_service:latest 0.0s Execute the below command to run the generated Docker image: docker run -d conference_service:latest
The auto-generated Docker file can be found in the following path: target/docker/conference_service
# Auto Generated Dockerfile
FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3 as build
WORKDIR /app/build
COPY conference_service.jar .
RUN native-image -jar conference_service.jar -H:Name=conference_service --no-fallback -H:+StaticExecutableWithDynamicLibC
FROM gcr.io/distroless/base
WORKDIR /home/ballerina
COPY --from=build /app/build/conference_service .
CMD ["./conference_service"]
Now, let’s run docker-compose
after changing the image name of the conference_service
.
version: '2'
services:
conference-service:
image: 'conference_service:latest'
ports:
- '8102:8102'
volumes:
- ./Config.toml:/home/ballerina/Config.toml
depends_on:
country-service:
condition: service_started
network_mode: "host"
country-service:
image: 'ktharmi176/country-service:latest'
hostname: country-service
container_name: country-service
ports:
- '9000:9000'
network_mode: "host"
Configure Mode
The Code to Cloud feature supports overriding the default mode where we configure the following:
- The GraalVM build-image
- The native-image build command
- The base image for the deployment
This can be achieved by providing the configurations via Cloud.toml
file. The following shows an example to provide the same configurations used for the custom Docker file.
[container.image]
name = "conference-service"
repository = "ktharmi176"
tag = "1.0.0"
base = "debian:stable-slim"
[graalvm.builder]
base = "ghcr.io/graalvm/native-image:ol8-java11-22.3.3"
buildCmd = "native-image -jar conference_service.jar --no-fallback"
$ bal build --graalvm --cloud=docker Compiling source tharmigan/conference_service:1.0.0 Generating artifacts Building the native image. This may take a while [+] Building 310.0s (14/14) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 372B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/debian:stable-slim 3.9s => [internal] load metadata for ghcr.io/graalvm/native-image:ol8-java11-22.3.3 2.5s => [auth] library/debian:pull token for registry-1.docker.io 0.0s => [build 1/4] FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3@sha256:c0b4d9c31013d4fd91c4dec25f8772602e851ee67b8510d21bfdab532da4 0.0s => [stage-1 1/3] FROM docker.io/library/debian:stable-slim@sha256:6fe30b9cb71d604a872557be086c74f95451fecd939d72afe3cffca3d9e60607 0.0s => [internal] load build context 0.3s => => transferring context: 42.65MB 0.3s => CACHED [stage-1 2/3] WORKDIR /home/ballerina 0.0s => CACHED [build 2/4] WORKDIR /app/build 0.0s => [build 3/4] COPY conference_service.jar . 0.1s => [build 4/4] RUN native-image -jar conference_service.jar --no-fallback 305.1s => [stage-1 3/3] COPY --from=build /app/build/conference_service . 0.2s => exporting to image 0.3s => => exporting layers 0.3s => => writing image sha256:1f5b5b30653a48a6d27258f785d93a1654dde25d2e70899e14f2b61996e01996 0.0s => => naming to docker.io/ktharmi176/conference-service:1.0.0 0.0s Execute the below command to run the generated Docker image: docker run -d ktharmi176/conference-service:1.0.0
The auto-generated Docker file with the above configurations will look like this.
# Auto Generated Dockerfile
FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3 as build
WORKDIR /app/build
COPY conference_service.jar .
RUN native-image -jar conference_service.jar --no-fallback
FROM debian:stable-slim
WORKDIR /home/ballerina
COPY --from=build /app/build/conference_service .
CMD ["./conference_service"]
This is the same as the one we wrote manually. Run Docker-compose and check the functionality using the request.http
file.
In conclusion, we have built a GraalVM executable for a Ballerina application and containerized it in Docker. GraalVM and Ballerina with the Code to Cloud feature simplify the experience of developing and deploying the Ballerina GraalVM native image in the cloud. It also enables the use of cloud-native technologies with GraalVM easily without in-depth knowledge.
Published at DZone with permission of Tharmigan Krishnananthalingam. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments