Deploy a Micronaut Microservices Application to the Azure Kubernetes Service

This guide shows how to deploy a Micronaut® application, consisting of three microservices, to the Azure Kubernetes Service (AKS) using the Micronaut Kubernetes project.

The Micronaut Kubernetes project provides integration between Micronaut and Kubernetes. It adds support for the following features:

  • Service Discovery
  • Configuration client for config maps and secrets
  • Kubernetes blocking and non-blocking clients built on top of the official Kubernetes Java SDK

AKS is a managed Kubernetes service for deploying containerized applications to the cloud. The guide demonstrates how to use Kubernetes Service Discovery and Distributed Configuration to connect three microservices, and discover how Micronaut integration with Kubernetes simplifies deployment to AKS. The application consists of three microservices:

  • users - contains customer data that can place orders on items, also a new customer can be created. It requires HTTP basic authentication to access it.
  • orders - contains all orders that customers have created as well as available items that customers can order. This microservice also enables the creation of new orders. It requires HTTP basic authentication to access it.
  • api - acts as a gateway to the orders and users microservices. It combines results from both microservices and checks data when a customer creates a new order.

Prerequisites #

A note regarding your development environment

Consider using Visual Studio Code, which provides native support for developing applications with the Graal Development Kit extension.

Note: If you use IntelliJ IDEA, enable annotation processing.

Windows platform: The GDK guides are compatible with Gradle only. Maven support is coming soon.

1. Create or Download a Microservices Application #

You can create a microservices application from scratch by following this guide, or you can download the completed example:

The application ZIP file will be downloaded to your default downloads directory. Unzip it, open it in your code editor, and proceed to the next steps.

Note: By default, a Micronaut application detects its runtime environment. A detected environment (in this case, k8s) overrides the default specified environment (in this case, azure). This means that you should locate your configuration in the application-k8s.properties and bootstrap-k8s.properties files. Alternatively, you can specify the azure environment passing it as a command-line option (-Dmicronaut.environments=azure) or via an environment variable (MICRONAUT_ENVIRONMENTS=azure).

2. Prepare to Deploy Microservices #

2.1. Create a Resource Group #

We recommend that you create a new resource group for this guide, but you can use an existing resource group instead.

Run the az group create command to create a resource group named gdkguides in the eastus region:

az group create --location eastus --name gdkguides

If you prefer using the region geographically closer to you, run az account list-locations to list all available regions.

2.2. Register Microsoft.ContainerRegistry as a Resource Provider #

Run the az provider register command to register Microsoft.ContainerRegistry as a Resource Provider:

az provider register --namespace 'Microsoft.ContainerRegistry'

This will take a while to execute. Check the status:

az provider show --namespace Microsoft.ContainerRegistry

Once the "registrationState": "Registering" changes to "registrationState": "Registered", the registration is complete.

2.3. Register Microsoft.ContainerService as a Resource Provider #

Run the az provider register command to register Microsoft.ContainerService as a Resource Provider:

az provider register --namespace 'Microsoft.ContainerService'

This will take a while to execute. Check the status:

az provider show --namespace Microsoft.ContainerService

Once the "registrationState": "Registering" changes to "registrationState": "Registered", the registration is complete.

2.4. Register Microsoft.Compute as a Resource Provider #

Run the az provider register command to register Microsoft.Compute as a Resource Provider:

az provider register --namespace 'Microsoft.Compute'

This will take a while to execute. Check the status:

az provider show --namespace Microsoft.Compute

Once the "registrationState": "Registering" changes to "registrationState": "Registered", the registration is complete.

2.5. Create an Azure Container Registry #

Run the az acr create command to create an Azure Container Registry:

az acr create \
   --name gdkrepo \
   --resource-group gdkguides \
   --sku Basic

2.6. Create an Azure Kubernetes Cluster #

Run the az aks create command to create an Azure Kubernetes Cluster:

az aks create \
   --name gdkCluster \
   --resource-group gdkguides \
   --node-count 1 \
   --dns-name-prefix=gdk \
   --attach-acr gdkrepo \
   --generate-ssh-keys

Run kubectl get nodes to list the cluster nodes to verify the connection to your cluster:

kubectl get nodes

The response should look like this:

NAME                                STATUS   ROLES    AGE   VERSION
aks-nodepool1-70833750-vmss000000   Ready    <none>    3m   v1.29.7

2.7. Authenticate to Your Azure Container Registry #

Run the az aks get-credentials command to generate a kubectl configuration for authentication to ACR:

az aks get-credentials \
   --resource-group gdkguides \
   --name gdkCluster

Run the az acr login command to authenticate to your Azure Container Registry:

az acr login --name gdkrepo

Alternately, login using docker login.

Run az acr login with the --expose-token flag to generate an access token, and save it in the TOKEN environment variable:

export TOKEN=$(az acr login --name gdkrepo --expose-token --output tsv --query accessToken)

Then run:

docker login gdkrepo.azurecr.io \
   --username 00000000-0000-0000-0000-000000000000 \
   --password-stdin <<< $TOKEN

You should see Login Succeeded after logging in.

2.8. Create and Publish Container Images #

2.8.1. Create and Publish a Container Image of the Native Users Microservice

  1. To create a container image of the native users microservice named “users”, run the following command from the users directory:
    ./gradlew :azure:dockerBuildNative

    Note: If you encounter problems creating a container image, run the following command from the users/build/docker/native-main/ directory:

    docker build . -t users-azure -f DockerfileNative
    ./mvnw package -Dpackaging=docker-native -Pgraalvm

    Note: If you encounter problems creating a container image, run the following command from the users/target/ directory:

    docker build . -t users-azure -f Dockerfile
  2. Tag the image with the login server of your container registry:
     docker tag users-azure gdkrepo.azurecr.io/users:latest
    
  3. Push the image to the container registry:
     docker push gdkrepo.azurecr.io/users:latest
    

2.8.2. Create and Publish a Container Image of the Native Orders Microservice

  1. To create a container image of the native orders microservice named “orders”, run the following command from the orders directory:
    ./gradlew :azure:dockerBuildNative

    Note: If you encounter problems creating a container image, run the following command from the orders/build/docker/native-main/ directory:

    docker build . -t orders-azure -f DockerfileNative
    ./mvnw package -Dpackaging=docker-native -Pgraalvm

    Note: If you encounter problems creating a container image, run the following command from the orders/target/ directory:

    docker build . -t orders-azure -f Dockerfile
  2. Tag the image with the login server of your container registry:
     docker tag orders-azure gdkrepo.azurecr.io/orders:latest
    
  3. Push the image to the container registry:
     docker push gdkrepo.azurecr.io/orders:latest
    

2.8.3. Create and Publish a Container Image of the Native API (Gateway) Microservice

  1. To create a container image of the native api microservice named “api”, run the following command from the api directory:
    ./gradlew :azure:dockerBuildNative

    Note: If you encounter problems creating a container image, run the following command from the api/build/docker/native-main/ directory:

    docker build . -t api-azure -f DockerfileNative
    ./mvnw package -Dpackaging=docker-native -Pgraalvm

    Note: If you encounter problems creating a container image, run the following command from the api/target/ directory:

    docker build . -t api-azure -f Dockerfile
  2. Tag the image with the login server of your container registry:
     docker tag api-azure gdkrepo.azurecr.io/api:latest
    
  3. Push the image to the container registry:
     docker push gdkrepo.azurecr.io/api:latest
    

2.9. Update the Kubernetes Manifest Files #

2.9.1. Update the Users Manifest File

Edit the file named users/k8s-azure.yml as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: gdk-k8s
  name: users
spec:
  selector:
    matchLabels:
      app: users
  template:
    metadata:
      labels:
        app: users
    spec:
      serviceAccountName: gdk-service
      containers:
        - name: users
          image: 'gdkrepo.azurecr.io/users:latest' # <1>
          imagePullPolicy: Always # <2>
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
          env:
            - name: MICRONAUT_ENVIRONMENTS
              value: azure
---
apiVersion: v1
kind: Service
metadata:
  namespace: gdk-k8s
  name: users
spec:
  selector:
    app: users
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080

1 The location of the image that you created in ACR

2 Change imagePullPolicy to Always.

2.9.2. Update the Orders Manifest File

Edit the file named orders/k8s-azure.yml as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: gdk-k8s
  name: orders
spec:
  selector:
    matchLabels:
      app: orders
  template:
    metadata:
      labels:
        app: orders
    spec:
      serviceAccountName: gdk-service
      containers:
        - name: orders
          image: 'gdkrepo.azurecr.io/orders:latest' # <1>
          imagePullPolicy: Always # <2>
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
          env:
            - name: MICRONAUT_ENVIRONMENTS
              value: azure
---
apiVersion: v1
kind: Service
metadata:
  namespace: gdk-k8s
  name: orders
spec:
  selector:
    app: orders
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080

1 The location of the image that you created in ACR

2 Change imagePullPolicy to Always.

2.9.3. Update the API Manifest File

Edit the file named api/k8s-azure.yml as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: gdk-k8s
  name: api
spec:
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      serviceAccountName: gdk-service
      containers:
        - name: api
          image: 'gdkrepo.azurecr.io/api:latest' # <1>
          imagePullPolicy: Always # <2>
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
          env:
            - name: MICRONAUT_ENVIRONMENTS
              value: azure
---
apiVersion: v1
kind: Service
metadata:
  namespace: gdk-k8s
  name: api
spec:
  selector:
    app: api
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 8080

1 The location of the image that you created in ACR

2 Change imagePullPolicy to Always.

3. Deploy Microservices to AKS #

  1. Deploy the auth.yml file that specifies service roles and secrets:

     kubectl apply -f auth.yml
    
  2. Deploy the users microservice:

     kubectl apply -f users/k8s-azure.yml
    
  3. Deploy the orders microservice:

     kubectl apply -f orders/k8s-azure.yml
    
  4. Deploy the api microservice:

     kubectl apply -f api/k8s-azure.yml
    

4. Test Integration Between the Microservices Deployed to AKS #

  1. Run the following command to check the status of the pods and make sure that all of them have the status “Running”:
     kubectl get pods -n=gdk-k8s
    
     NAME                      READY   STATUS    RESTARTS   AGE
     api-6fb4cd949f-kxxx8      1/1     Running   0          12s
     orders-595887ddd6-6lzp4   1/1     Running   0          17s
     users-df6f78cd7-lgnzx     1/1     Running   0          23s
  2. Run this command to check the status of the microservices:

     kubectl get services -n=gdk-k8s
    
     NAME         TYPE           CLUSTER-IP       EXTERNAL-IP             PORT(S)             AGE
     api          LoadBalancer   10.100.208.154   <redacted>              8080:31171/TCP      31s
     orders       NodePort       10.100.157.155   <none>                  8080:30742/TCP      36s
     users        NodePort       10.100.126.97    <none>                  8080:31580/TCP      42s

    If EXTERNAL-IP is in a <pending> state, wait a couple of seconds and then run the command again.

  3. Retrieve the URL of the api microservice and set it as the value of the $API_URL environment variable:

     export API_URL=http://$(kubectl get svc api -n=gdk-k8s -o json | jq -r '.status.loadBalancer.ingress[0].ip'):8080
    
  4. Run a curl command to create a new user via the api microservice:

     curl -X "POST" "$API_URL/api/users" \
          -H 'Content-Type: application/json; charset=utf-8' \
          -d '{ "first_name": "Nemanja", "last_name": "Mikic", "username": "nmikic" }'
    

    Your output should look like:

     {
       "id":1,
       "username":"nmikic",
       "first_name":"Nemanja",
       "last_name":"Mikic"
     }
    
  5. Run a curl command to create a new order via the api microservice:

     curl -X "POST" "$API_URL/api/orders" \
          -H 'Content-Type: application/json; charset=utf-8' \
          -d '{ "user_id": 1, "item_ids": [1,2] }'
    

    Your output should include details of the order, as follows:

     {
       "id": 1,
       "user": {
         "first_name": "Nemanja",
         "last_name": "Mikic",
         "id": 1,
         "username": "nmikic"
       },
       "items": [
         {
           "id": 1,
           "name": "Banana",
           "price": 1.5
         },
         {
           "id": 2,
           "name": "Kiwi",
           "price": 2.5
         }
       ],
       "total": 4.0
     }
    
  6. Run a curl command to list the orders:

     curl "$API_URL/api/orders" \
          -H 'Content-Type: application/json; charset=utf-8'
    

    You should see output that is similar to the following:

     [
       {
         "id": 1,
         "user": {
           "first_name": "Nemanja",
           "last_name": "Mikic",
           "id": 1,
           "username": "nmikic"
         },
         "items": [
           {
             "id": 1,
             "name": "Banana",
             "price": 1.5
           },
           {
             "id": 2,
             "name": "Kiwi",
             "price": 2.5
           }
         ],
         "total": 4.0
       }
     ]
    
  7. Try to place an order for a user who does not exist (with id 100). Run a curl command:

     curl -X "POST" "$API_URL/api/orders" \
          -H 'Content-Type: application/json; charset=utf-8' \
          -d '{ "user_id": 100, "item_ids": [1,2] }'
    

    You should see the following error message:

     {
       "message": "Bad Request",
       "_links": {
         "self": [
           {
             "href": "/api/orders",
             "templated": false
           }
         ]
       },
       "_embedded": {
         "errors": [
           {
             "message": "User with id 100 doesn't exist"
           }
         ]
       }
     }
    

7. Clean up Cloud Resources #

Once you are done with this guide, you can delete the Azure resources created to avoid incurring unnecessary charges.

Delete the resource group and all of its resources with:

az group delete --name gdkguides

Alternatively, run these commands to delete resources individually:

kubectl delete namespaces gdk-k8s
az aks delete --name gdkCluster --resource-group gdkguides
az acr delete --name gdkrepo
az group delete --name gdkguides

Summary #

This guide demonstrated how to use Kubernetes Service Discovery and Distributed Configuration, provided with the Micronaut Kubernetes integration, to connect three microservices, and to deploy these microservices to a Kubernetes cluster in the Azure Kubernetes Service.