Kubernetes service discovery and distributed configuration
How to use Kubernetes service discovery and distributed configuration in a Micronaut application
On this guide
In this section
Getting Started
In this guide, we will create three microservices, build containerized versions, and deploy them with Kubernetes. We will use Kubernetes service discovery and distributed configuration to wire up our microservices.
Kubernetes is a portable, extensible, open source platform for managing containerized workloads and services, that facilitates both declarative configuration and automation. It has a large, rapidly growing ecosystem. Kubernetes services, support, and tools are widely available.
You will discover how the Micronaut framework eases Kubernetes integration.
What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE (e.g. IntelliJ IDEA)
-
JDK 17 or greater installed with
JAVA_HOMEconfigured appropriately -
Local Kubernetes cluster. We will use Minikube in this guide.
Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
Writing the Apps
Let’s describe the microservices you will build through the guide.
-
users- This microservice contains customers data that can place orders on items, also a new customer can be created. Microservice requires Basic authentication to access it. -
orders- This microservice contains all orders that customers have created as well as available items that customers can order. Also this microservice enables the creation of new orders. Microservice requires Basic authentication to access it. -
api- This microservice acts as a gateway to theordersandusersservices. It combines results from both services and checks data when customers create a new order.
Initially we will hard-code the URLs of the orders and users services in the api service. Additionally, we will hard-code credentials (username and password) into every microservice configuration that are required for Basic authentication.
In the second part of this guide, we will use a Kubernetes discovery service and Kubernetes configuration maps to dynamically resolve the URLs of the orders and users microservices and get authentication credentials. The microservices call the Kubernetes API to register when they start up and then resolve placeholders inside the microservices' configurations.
Users Microservice
Create the users microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app \
--features=discovery-kubernetes,management,security,kubernetes,serialization-jackson,validation \
--build=maven \
--lang=groovy \
--jdk=21 \
example.micronaut.users|
Note
|
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the yaml, discovery-kubernetes, management, security, serialization-jackson, kubernetes and graalvm features.
The previous command creates a directory named users containing Micronaut application with a package named example.micronaut.
|
Note
|
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create a package named controllers and create a UsersController class to handle incoming HTTP requests for the users microservice:
Create a package named models where we will put our data beans.
The UsersController class uses a User object to represent customer. Create the User class
Create a package named auth where you will check basic authentication credentials.
The Credentials class will load and store credentials (username and password) from a configuration file.
The CredentialsChecker class, as the name suggests, will check if the provided credentials inside the HTTP request’s Authorization header are the same as those that are stored inside the Credentials class that we created above.
Write tests to verify application logic
Create the UsersClient, a declarative Micronaut HTTP Client for testing:
HealthTest checks that there is /health endpoint that is required for service discovery.
UsersControllerTest tests endpoints inside the UserController.
Edit application.properties
Edit the bootstrap.properties file in the resources directory to enable distributed configuration. Change the default contents to the following:
Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.
Create a file named bootstrap-dev.properties to disable distributed configuration in the dev environment:
Create a file named application-test.properties for use in the test environment:
Run the unit test:
./mvnw testRunning the application
Run the users microservice:
MICRONAUT_ENVIRONMENTS=dev ./mvnw mn:run14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081Orders Microservice
Create the orders microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app \
--features=discovery-kubernetes,management,security,kubernetes,serialization-jackson,validation \
--build=maven \
--lang=groovy \
--jdk=21 \
example.micronaut.orders|
Note
|
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the yaml, discovery-kubernetes, management, security, serialization-jackson, kubernetes and graalvm features.
The previous command creates a directory named orders containing a Micronaut application with a package named example.micronaut.
|
Note
|
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create a package named controllers and create the OrdersController and ItemsController classes to handle incoming HTTP requests to the orders microservice:
Create a package named models where you will put your data beans.
The previous OrdersController and ItemsController controller uses Order and Item objects to represent customer orders. Create the Order class:
Create the Item class:
Create a package named auth where you will check basic authentication credentials.
The Credentials class will load and store credentials (username and password) from configuration files.
The CredentialsChecker class, as name suggests, will check if provided credentials inside an HTTP request’s Authorization header are the same as those that are stored inside Credentials class that we created above.
Write tests to verify application logic
Create the OrderItemClient, a declarative Micronaut HTTP Client for testing:
HealthTest checks that there is /health endpoint that is required for service discovery.
ItemsControllerTest tests endpoints inside the ItemController.
OrdersControllerTest tests endpoints inside the OrdersController.
Edit application.properties so it contains:
Edit bootstrap.properties file in the resources directory to enable distributed configuration. Change it to the following:
Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.
Create a file named bootstrap-dev.properties to disable distributed configuration in the dev environment:
Create a file named application-test.properties to be used in the test environment:
Run the unit test:
./mvnw testRunning the application
Run the orders microservice:
MICRONAUT_ENVIRONMENTS=dev ./mvnw mn:run14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8082API (Gateway) Microservice
Create the api microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app \
--features=discovery-kubernetes,management,kubernetes,serialization-jackson,http-client \
--build=maven \
--lang=groovy \
--jdk=21 \
example.micronaut.api|
Note
|
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the yaml, discovery-kubernetes, management, kubernetes, serialization-jackson, mockito, graalvm and http-client features.
The previous command creates a directory named api containing a Micronaut application with a package named example.micronaut.
|
Note
|
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create a package named controllers and create a GatewayController class to handle incoming HTTP requests to the api microservice:
Create a package named models where you will put your data beans.
The previous GatewayController and ItemsController controller uses User, Order and Item to represent customer orders. Create the User class:
Create the Order class:
Create the Item class:
Create a package named clients where you will put the HTTP Clients to call the users and orders microservices.
Create a UsersClient for the users microservice.
Create an OrdersClient for the orders microservice.
Create a package named auth where we will check basic authentication credentials.
Create a Credentials class that will load the username and password from configuration that will be needed for comparison.
Create an AuthClientFilter class that is a client filter applied to every client. It adds basic authentication header with credentials that are stored in the Credentials class.
package example.micronaut.auth
import io.micronaut.http.HttpResponse
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.annotation.Filter
import io.micronaut.http.filter.ClientFilterChain
import io.micronaut.http.filter.HttpClientFilter
import org.reactivestreams.Publisher
@Filter(Filter.MATCH_ALL_PATTERN)
class AuthClientFilter implements HttpClientFilter {
private final Credentials credentials
AuthClientFilter(Credentials credentials) {
this.credentials = credentials
}
@Override
Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
chain.proceed(request.basicAuth(credentials.username, credentials.password))
}
}Create a class named ErrorExceptionHandler in the example.micronaut package. ErrorExceptionHandler will propagate errors from the orders and users microservices.
Write tests to verify application logic
Create a GatewayClient, a declarative Micronaut HTTP Client for testing:
HealthTest checks that there is /health endpoint that is required for service discovery.
GatewayControllerTest tests endpoints inside the GatewayController.
Edit application.properties
Edit the bootstrap.properties file in the resources directory to enable distributed configuration so it looks like the following:
Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.
Create a file named bootstrap-dev.properties to disable distributed configuration in the dev environment:
Create a file named application-test.properties to be used in the test environment:
Run the unit test:
./mvnw testRunning the application
Run api microservice:
MICRONAUT_ENVIRONMENTS=dev ./mvnw mn:run14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8080Test integration between applications
Store the URL of the api microservice in the API_URL environment variable.
export API_URL=http://localhost:8080Run 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" }'{"id":1,"username":"nmikic","first_name":"Nemanja","last_name":"Mikic"}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] }'{"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}Run a cURL command to list created orders:
curl "$API_URL/api/orders" -H 'Content-Type: application/json; charset=utf-8'[{"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}]We can try to place an order for a user who doesn’t 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] }'{"message":"Bad Request","_links":{"self":[{"href":"/api/orders","templated":false}]},"_embedded":{"errors":[{"message":"User with id 100 doesn't exist"}]}}Kubernetes and the Micronaut framework
In this chapter we will first create the necessary Kubernetes resources for our microservices that will make them work properly then we will configure build container images and deploy each of the microservices that we created on the local Kubernetes cluster.
Create a file named auth.yml that will configure the service role for microservices that have secret configurations.
Run the next command to create the resources described above:
kubectl apply -f auth.ymlBefore we start deploying each service, ensure that Docker daemon is configured to use Kubernetes. If you are using Minikube run the next command to switch the docker daemon to use Minikube.
eval $(minikube docker-env)Users Microservice
Build a Docker image of the users service with the name users.
Edit the file named k8s.yml inside the users microservice.
Run the next command to create the resources described above:
kubectl apply -f users/k8s.ymldeployment.apps/users created
service/users createdOrders Microservice
Build a Docker image of the orders service with the name orders.
Edit the file named k8s.yml inside the orders microservice.
Run the next command to create the resources described above:
kubectl apply -f orders/k8s.ymlAPI (Gateway) Microservice
Build a Docker image of the api service with the name api.
Edit the file named k8s.yml inside the api microservice.
Run the next command to create the resources described above:
kubectl apply -f api/k8s.ymlTest integration between applications deployed on Kubernetes
Run the next command to check status of the pods and make sure that all of them have the status "Running":
kubectl get pods -n=micronaut-k8sNAME READY STATUS RESTARTS AGE
api-774fd667b9-dmws4 1/1 Running 0 24s
orders-74ff4fcbc4-dnfbw 1/1 Running 0 19s
users-9f46dd7c6-vs8z7 1/1 Running 0 13sRun the next command to check the status of the microservices:
kubectl get services -n=micronaut-k8sMinikube
For Minikube the output should be similar to the following:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api LoadBalancer 10.110.42.201 <pending> 8080:32601/TCP 18s
orders NodePort 10.105.43.19 <none> 8080:31033/TCP 21s
users NodePort 10.104.130.114 <none> 8080:31482/TCP 26s|
Note
|
By default, the EXTERNAL-IP address of the LoadBalancer service inside Minikube will be in the <pending> state. If you want to assign an external IP, you have to run the minikube tunnel command.
|
Run the next command to retrieve the URL of the api microservice:
export API_URL=$(minikube service api -n=micronaut-k8s --url)Docker Desktop
For Docker Desktop’s Kubernetes integration the output should be similar to the following. Notice the external-ip is localhost:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api LoadBalancer 10.108.205.248 localhost 8080:31516/TCP 9m23s
orders NodePort 10.98.120.224 <none> 8080:31566/TCP 9m39s
users NodePort 10.109.155.86 <none> 8080:30545/TCP 10mSo for Docker Desktop the API_URL should be set to http://localhost:8080.
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" }'{"id":1,"username":"nmikic","first_name":"Nemanja","last_name":"Mikic"}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] }'{"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}Run a cURL command to list created orders:
curl "$API_URL/api/orders" -H 'Content-Type: application/json; charset=utf-8'[{"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}]We can try to place an order for a user who doesn’t 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] }'{"message":"Bad Request","_links":{"self":[{"href":"/api/orders","templated":false}]},"_embedded":{"errors":[{"message":"User with id 100 doesn't exist"}]}}Cleaning Up
To delete all resources that were created in this guide, run the next command.
kubectl delete namespaces micronaut-k8sNext Steps
Read more about Kubernetes.
Read more about Micronaut Kubernetes module.
License
|
Note
|
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images). |