No, it’s not a buzzword bingo, but a HelloWorld example.
Many examples of Micronaut projects can be found online, but they almost all focus on Micronaut features. The project described here focuses on the deployment of a Micronaut microservice: it shows the steps to wrap a Micronaut microservice into a configurable Helm chart, ready for integration into your CI/CD pipelines.
This example:
- builds a simple Micronaut microservice
- builds a Docker / GraalVM image
- builds a Helm chart
- shows how configuration items can be handled during all these steps
Technologies
The technologies used are:
- Micronaut 2.2.0
- Docker 20.10.3
- Kubernetes v1.20.2 (Minikube v1.17.1)
- Helm 3.5.1
- a Docker Hub account
- on a Ubuntu 18.04.05 LTS machine
The source code can be found in GitHub: lgorissen/hello-micronaut
If you want to follow this blog hands-on, it is recommended to download the GitHub archive and start from there.
In the remainder of this blog it’s assumed that you have installed all of the above technologies.
1. Micronaut service
1.1 Create project
If you start from the GitHub archive, you don’t have to follow this step. If you start from scratch, then first create a Micronaut project:
mn create-app hello-micronaut
Complete the Micronaut microservice by adding the HelloMicronautController.java class in directory hello-micronaut/src/main/java/hello/micronaut:
import io.micronaut.context.annotation.Property; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; @Controller("/hellomicronaut") public class HelloMicronautController { @Property(name = "hello.greeting") String greeting; @Property(name = "hello.location") String location; @Post(consumes = {MediaType.APPLICATION_JSON}, produces = {MediaType.TEXT_PLAIN}) public String sayHelloMicronaut(String name) { return greeting + " " + name + " from " + location; } }
Note: configuration properties greeting and location are NOT declared as private. Declaring them as private will result in failure. For more information, see paragraph 2.3: ‘Run Docker container’.
The two configuration properties also must be defined in the application.yml file (directory hello-micronaut/src/main/resources):
micronaut: server: port: 8090 application: name: helloMicronaut hello: greeting: Hello location: development world
We also changed the server port into 8090 … for no particular reason ????.
1.2 Run and test project locally
Run:
./gradlew run
Test:
curl -H 'Content-type: application/json' localhost:8090/hellomicronaut -d '{"name": "Luc"}'
2. Build GraalVM Docker image
The next step is to build a Docker image that has a GraalVM Micronaut application in it. Micronaut has made this fairly simple: no messing around with doing your own GraalVM installation etc., just a simple Gradle task.
2.1 Build GraalVM Docker image
Build GraalVM Docker image: Note: This can take long time, in my case 2m41s, but may well go up to 10 minutes…
./gradlew dockerBuildNative > Task :compileJava Note: Writing resource-config.json file to destination: META-INF/native-image/hello.micronaut/hello-micronaut/resource-config.json Note: Creating bean classes for 1 type elements > Task :dockerfileNative Dockerfile written to: /home/developer/projects/hello-micronaut/hello-micronaut/build/docker/DockerfileNative > Task :dockerBuildNative Building image using context '/home/developer/projects/hello-micronaut/hello-micronaut'. Using Dockerfile '/home/developer/projects/hello-micronaut/hello-micronaut/build/docker/DockerfileNative' Using images 'hello-micronaut'. Step 1/11 : FROM oracle/graalvm-ce:20.3.0-java11 AS graalvm ---> 0b7b0c4bfba5 Step 2/11 : RUN gu install native-image ---> Using cache ---> 3587e8ce6778 Step 3/11 : WORKDIR /home/app ---> Using cache ---> b261c52e901c Step 4/11 : COPY build/layers/libs /home/app/libs ---> Using cache ---> 9bef7a42e3b4 Step 5/11 : COPY build/layers/resources /home/app/resources ---> 28b4af8808ae Step 6/11 : COPY build/layers/application.jar /home/app/application.jar ---> 0b8b092a8f8f Step 7/11 : RUN native-image -H:Class=hello.micronaut.Application -H:Name=application --no-fallback -cp /home/app/libs/*.jar:/home/app/resources:/home/app/application.jar ---> Running in b8f29a4420ce [application:26] classlist: 5,079.35 ms, 0.96 GB [application:26] (cap): 920.06 ms, 0.96 GB [application:26] setup: 2,948.00 ms, 0.96 GB [application:26] (clinit): 1,212.49 ms, 4.03 GB [application:26] (typeflow): 25,541.89 ms, 4.03 GB [application:26] (objects): 30,267.57 ms, 4.03 GB [application:26] (features): 3,022.89 ms, 4.03 GB [application:26] analysis: 62,317.35 ms, 4.03 GB [application:26] universe: 2,645.39 ms, 4.03 GB [application:26] (parse): 9,783.03 ms, 4.42 GB [application:26] (inline): 7,042.68 ms, 5.19 GB [application:26] (compile): 44,759.87 ms, 5.26 GB [application:26] compile: 65,952.92 ms, 5.26 GB [application:26] image: 9,048.58 ms, 5.18 GB [application:26] write: 1,131.09 ms, 5.18 GB [application:26] [total]: 149,422.61 ms, 5.18 GB Removing intermediate container b8f29a4420ce ---> 7b0187dda5e4 Step 8/11 : FROM frolvlad/alpine-glibc ---> f6858800cf89 Step 9/11 : RUN apk update && apk add libstdc++ ---> Using cache ---> 14f4843a53ac Step 10/11 : COPY --from=graalvm /home/app/application /app/application ---> fc8770574b87 Step 11/11 : ENTRYPOINT ["/app/application"] ---> Running in 19dd53aad41f Removing intermediate container 19dd53aad41f ---> 81c10a2a3d4c Successfully built 81c10a2a3d4c Successfully tagged hello-micronaut:latest Created image with ID '81c10a2a3d4c'. BUILD SUCCESSFUL in 2m 41s 6 actionable tasks: 5 executed, 1 up-to-date
2.2 Check for Docker image
Check for the Docker image and note the size of the image:
docker images | grep -i hello-micronaut
2.3 Run Docker container
Note
The current Micronaut Gradle DockerBuild does NOT include the default configuration file application.yml.
So, when running the built container, the application properties have to be added.
Below, the application.yml file is mounted and the environment variable MICRONAUT_CONFIG_FILES points to it:
docker run -d -v "$(pwd)"/src/main/resources/application.yml:/app/application.yml --env MICRONAUT_CONFIG_FILES=/app/application.yml -p8020:8090 --name my-hello-micronaut hello-micronaut
Test it:
curl -H 'Content-type: application/json' localhost:8020/hellomicronaut -d '{"name": "Luc"}'
Note: As described above, the configuration properties greeting and location must NOT be declared private.
Declaring them as private will give the following run-time error upon processing a request:
{"message":"Internal Server Error: Failed to inject value for field [greeting] of class: hello.micronaut.HelloMicronautController\n\nMessage: Error setting field value: No field 'greeting' found for type: hello.micronaut.HelloMicronautController\n"}
Stop the container:
docker stop my-hello-micronaut
2.4 Upload the Docker image to Docker Hub
Tag the image and upload to Docker Hub:
docker login --username=lgorissen docker images | grep hello-micronaut docker tag hello-micronaut:latest lgorissen/hello-micronaut:1.0.0 docker push lgorissen/hello-micronaut:1.0.0
Schrödinger’s warning: I know that I don’t have to warn you to use your own Docker Hub account. Meaning of course your Docker account and image name will be different.
2.5 Remove local Docker containers and images
Remove all local Docker artefacts:
docker rm my-hello-micronaut docker rmi hello-micronaut:latest docker rmi lgorissen/hello-micronaut:1.0.0
3. Helm chart
Finally, a Helm chart has to be made that has configuration options.
3.1 Set-up
The Helm chart is in the the hello-micronaut/k8s/charts directory.
The directory structure for the Helm chart is:
hello-micronaut/k8s/charts/hello-micronaut/Chart.yml templates/comfigmap.yml deployment.yml service.yml values.yml
- If you started from scratch, just copy the files from the GitHub archive.
- If you started with the GitHub archive, the files are already present.
The Helm chart deploys the following Kubernetes resources:
- ConfigMap: with the yml configuration file for the Micronaut application.
- Deployment:
- deploys a number of Pods with the Docker image that was uploaded to Docker Hub
- mounts the ConfigMap, thus putting the configuration file in /conf/application.yml
- starts the application with the argument -Dmicronaut.config.files=/conf/application.yml
- Service: for access to the Deployment’s Pods.
3.2 Running the Helm chart
The Helm chart is customized in the values.yml file:
name: hello-micronaut deployment: replicas: 1 image: lgorissen/hello-micronaut:1.0.0 imagePullPolicy: Always containerPort: 8090 greeting: Welcome location: on Terra10-micronaut service: port: 8020
Run the Helm chart with the below command in the right directory: hello-micronaut/k8s/charts/hello-micronaut
helm install my-hello-micronaut --values values.yml
Verify the Pod is running with kubectl get pod.
3.3 Testing the Helm chart
In order to test the Helm chart, first port-forward the hello-micronaut service:
kubectl port-forward service/hello-micronaut 8020:8020
And then test with:
curl -H 'Content-type: application/json' localhost:8020/hellomicronaut -d '{"name": "Luc"}'
By now, we have a Helm chart that can deploy a Micronaut service, including its configuration, to a Kubernetes cluster, using a GraalVM image.