Bozobooks.com: Fullstack k8s application blog series

Book Information Microservice — Quarkus Native Image with Redis Cache

In this blog, we will build a quarkus native image application to fetch Book information, by calling Google Books API, and storing the results in the Redis cache for future use. We will also learn how to inject Vault secrets into a quarkus application

A B Vijay Kumar
Cloud Native Daily
Published in
10 min readOct 16, 2022

--

In the previous chapter, we configured Vault and stored the Google API key in it. In this chapter, we will build the BozoBookInfoService as a quarkus application and we will be injecting the Google API into it.

BozoBookInfoService provides service endpoints to fetch the books, from Google Book API. Let’s now build this Microservice as a Quarkus application

Step 1: Generate a Quarkus app

Quarkus boilerplate code can be generated at https://code.quarkus.io/ and or use the command line to generate the boilerplate. Please refer to Quarkus documentation for generating the boilerplate code.

We will need the following dependencies — JSONB, RESTEasy, arc, JUnit, assured, container-image-docker, smallrye, graphQL, jackson, redis-client, kubernetes-config. The pom file should look something like the following screenshot.

container-image-docker dependency helps build the docker image directly and kubernetes-config also helps use the Kubernetes resources, within the quarkus application. We will be injecting the ConfigMaps directly into the Quarkus application as properties, later in the blog. These are optional and can help build the docker image and use Kubernetes resources directly in the quarkus application.

Step 2: Install and configure GraalVM

( I will be using Visual Studio Code GraalVM plugin, which helps configure GraalVM environments). Refer to the documentation to set up GraalVM.

Here is the screenshot of my Visual Studio Code, GraalVM environment.

Step 3: Setup Redis for cache

To keep it simple, we will provision a Redis pod, using simple kubectl.

We will explore Redis operator-based deployment in a different blog. In that blog, we will cover cluster deployment, cache management, revalidation, etc.

In our current use case, we are going to store fetched results cached in the Redis in memory. So we will need to create a Redis deployment and use that in the quarkus application.

The following is the Redis deployment YAML

It is a standard deployment, that uses a Redis container image, with 1 replica in the cluster to start with. (since it's a dev environment). Let’s now expose this through a k8s service resource. The following is the YAML for the Redis service.

Let’s apply these YAMLs to the cluster using kubectl apply -f. You should find the Redis PODs and services.

Step 4: Create ConfigMap and all configurations that we need in the application

We need various configurations to be passed to the quarkus application. These configurations can be passed through application.properties or we can use the k8s ConfigMap resource, and pass it to Quarkus using quarkus.kubernetes-config.config-maps the parameter in application.properties, which is the right approach. We will explore the later method to pass the configuration parameters.

Here are the configurations, that we will need for the application

The ConfigMap has the following configurations

  • quarkus.http.cors: “true” - This is to let the quarkus application know we will use Cross-Origin Resource Sharing (CORS). This configuration is used so that the UI can directly call the resources using the REST APIs. the UI component URL is going to be different from the Book Library Service URL and Book Information Service URL.
  • quarkus.http.cors.origins: “*”-This is the CORS configuration to say, accept all origins
  • quarkus.http.cors.headers: “accept, authorization, content-type, x-requested-with” -This is CORS configuration that will be used in the header
  • quarkus.http.cors.methods: “GET, OPTIONS, POST”-This configuration is used by the quarkus application to accept the various HTTP methods
  • quarkus.redis.hosts: “redis://redis-cache-service.bozo-book-library-dev.svc.cluster.local:6379” — This is the Redis URL
  • book.info.service.google.book.api.url: “https://www.googleapis.com/books/v1/volumes" : This is the URL to call the Goole API to fetch book information (the API key will be stored separately in the vault — Please refer to Chapter 2 for more details)
  • book.info.service.max.results.per.page: “10” — This configuration is used by Book Library UI component, to show the maximum number of books on a page.

Additional configurations in application.properties

Let's use the kubernetes-config quarkus plugin to inject the ConfigMap properties into the application. We will have to update application.properties to update the configuration. Once we do this, all the ConfigMap key-value pairs can be directly accessed using @ConfigProperty annotation inside the quarkus code, which we will see later in this blog

quarkus.container-image.name=bozo-book-info-service
quarkus.container-image.tag=latest
quarkus.http.root-path=/bookinfoquarkus.kubernetes-config.enabled=true
quarkus.kubernetes-config.config-maps=book-info-service-config-map
quarkus.smallrye-graphql.ui.always-include=true

The following are the configurations we are passing through the application.properties

  • quarkus.container-image.name=bozo-book-info-service — This is the name quarkus docker plugin uses as the docker image name, by default
  • quarkus.container-image.tag=latest-This is the tag, that will be used as default if we use the quarkus docker plugin to build the image
  • quarkus.http.root-path=/bookinfo-This is the root context used by the service REST URL
  • quarkus.kubernetes-config.enabled=true- This configuration is used for quarkus to directly load the k8s config map, that we created in Step 4.
  • quarkus.kubernetes-config.config-maps=book-info-service-config-map-This is the name of the k8s config map. This will help us use @Property annotation to access the configurations in Java code.
  • quarkus.smallrye-graphql.ui.always-include=true — This will enable a graphql-ui, which we can use to execute graphql queries

Step 5: Define the REST resources & implement it

Let’s now define the REST resource and implement it. We will be exposing the REST APIs with GraphQL so that the client can use GraphQL to access the data. We will be using the microprofile to define the GraphQL interface, using @GraphQLApi annotation.

In the above code, we have defined two endpoints search and getBooks, and these endpoints support GraphQL, lets now implement these endpoints

In the above code, We are defining the /bookinfo resource, which exposes the BookInfoService. This service will fetch the list of books by keyword and book details. We will be using RedisClient to store and retrieve the cached book entries.

When we need to fetch the book details, we will first search the Redis cache, if the book information already exists, if it is a hit, we pick it from the cache or we will invoke Google Book API to get the book details, and once retrieved, we will store it in the cache. This will reduce the number of calls to the Google Books API and improves the performance.

between line # 25–32, we are injecting the ConfigMap configurations. The GoogleAPIKey is actually injected through Vault, which we will see later in the blog. (Please read Chapter 2, where we have configured Vault to store the GoogleAPIKey secret.)

In the above code, we are using HttpClient to make a call to the Google API and fetch the list of book titles, based on the keyword that is passed. We are fetching the book details from the cache if it exists in line number 71. We are then storing the results in the Redis cache in line number 77.

In the above code, we are using HttpClient to make a call to the google books API to get the book details and store them in the cache.

Step 6: Build a native image

Let's build the native image now. Quarkus provides the maven build configuration by default. Let’s go to the application folder and execute

./mvnw package -Dnative

once this runs successfully, we should be able to find the image under ./target/bookinfoservice-1.0.0-SNAPSHOT-runner

Let's build a docker image of this using the Dockerfile, Here is the Dockerfile code

We will be using the base universal base image from Redhat (Refer to this site for more details). The GraalVM native image is then copied to the work folder as bookfinfoservice binary file.

Let’s build the docker image

docker build -f Dockerfile.native -t <image-name>:<build-number>.

This will generate the docker image. We should be able to see the docker image by running docker images command.

Step 7: Push to DockerHub

Once we have the docker image, we will have to tag and push it to the docker hub. The following are the commands

docker image tag <image-name>:<build-number> docker.io/<docker-username>/<image-name>:<build-number>docker image push docker.io/<docker-username>/<image-name>:<build-number>

Step 8: Write Kubernetes infrastructure code

Let’s now write the Kubernetes manifests to deploy this container to our Kubernetes cluster. The following is the screenshot of the Kubernetes deployment YAML.

As you can see it's a very standard YAML, except for the following lines, which are very important.

Line 17–24: We provide the Vault configuration in the annotation. This will be picked by the Vault agent (that is injected as a sidecar), to inject the secrets that we have configured. The following are the properties we are passing

vault.hashicorp.com/agent-init-first: ‘true’ — This will force the vault agent container to run first. This is required, to make sure the vault container sets the secrets before any other thing happens

vault.hashicorp.com/agent-inject: ‘true’ — This suggests that the vault agent needs to be injected

vault.hashicorp.com/role: bozobooks-app — This is the same role that we had configured in the vault policy (Please read Chapter 2).

vault.hashicorp.com/agent-inject-secret-config: bozobooks/data/googleapi-config — This is the config key that needs to be injected.

vault.hashicorp.com/agent-inject-template-config — Is the template of the file that needs to be generated. Based on the below value, that we are passing, we are telling vault to generate a file, with an export statement

{{ with secret “bozobooks/data/googleapi-config” -}}
export googleapikey=”{{ .Data.data.googleapikey }}”
{{- end }}

The values of this config will be injected into /vault/secrets/config a file in the pod.

Line 31–39: We then source the config file, which is injected by vault, before executing the native image ./bookinfoservice

Let's now expose the pod as a service, with the following Kubernetes resource.

This is a standard Service resource YAML, nothing special.

Step 9: Deploy in k8s cluster

Now let's apply this deployment with kubectl apply -f.

Once the pods are running, we can run a kubectl describe to ensure that the vault sidecars are running. We should be able to see something like what is shown below

We should be able to see the vault-agent-init run as a init container, as shown in the screenshot below

We should be able to see the book-info-service container and the vault sidecar vault-agent container running.

Step 10: Test the API

Let's now check if our service is working. We can do port-forward using kubectl port-forward to access the service. Since we are using micro profile graphql, we can check the graphql schema at

localhost:<port>/bookinfo/graphql/schema.graphql

The following is the screenshot of postman when this API is called. We can see the complete GraphQL schema definition

and we can also use graphql-ui (which comes packaged with quarkus (we need to include the following line in application.properties

quarkus.smallrye-graphql.ui.always-include=true

localhost:<port>/bookinfo/q/graphql-ui

You can see the screenshot of using graphql-ui to run a query to see the results.

There you go, now we can see that the service is up and running

Lets now build another service to store and retrieve book preferences, in the next chapter

Till then…have fun, leave your comments and feedback

See you soon… take care

References

Further Reading:

--

--

A B Vijay Kumar
Cloud Native Daily

IBM Fellow, Master Inventor, Mobile, RPi & Cloud Architect & Full-Stack Programmer