Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

When we build a microservices solution, both Spring Cloud and Kubernetes are optimal solutions, as they provide components for resolving the most common challenges. However, if we decide to choose Kubernetes as the main container manager and deployment platform for our solution, we can still use Spring Cloud’s interesting features mainly through the Spring Cloud Kubernetes project.

This relatively new project undoubtedly provides easy integration with Kubernetes for Spring Boot applications. Before starting, it may be helpful to look at how to deploy a Spring Boot application on Minikube, a local Kubernetes environment.

In this tutorial, we’ll:

  • Install Minikube on our local machine
  • Develop a microservices architecture example with two independent Spring Boot applications communicating through REST
  • Set up the application on a one-node cluster using Minikube
  • Deploy the application using YAML config files

2. Scenario

In our example, we’re using the scenario of travel agents offering various deals to clients who will query the travel agents service from time to time. We’ll use it to demonstrate:

  • service discovery through Spring Cloud Kubernetes
  • configuration management and injecting Kubernetes ConfigMaps and secrets to application pods using Spring Cloud Kubernetes Config
  • load balancing using Spring Cloud Kubernetes Ribbon

3. Environment Setup

First and foremost, we need to install Minikube on our local machine and preferably a VM driver such as VirtualBox. It’s also recommended to look at Kubernetes and its main features before following this environment setup.

Let’s start the local single-node Kubernetes cluster:

minikube start --vm-driver=virtualbox

This command creates a Virtual Machine that runs a Minikube cluster using the VirtualBox driver. The default context in kubectl will now be minikube. However, to be able to switch between contexts, we use:

kubectl config use-context minikube

After starting Minikube, we can connect to the Kubernetes dashboard to access the logs and monitor our services, pods, ConfigMaps, and Secrets easily:

minikube dashboard

3.1. Deployment

Firstly, let’s get our example from GitHub.

At this point, we can either run the “deployment-travel-client.sh” script from the parent folder, or else execute each instruction one by one to get a good grasp of the procedure:

### build the repository
mvn clean install

### set docker env
eval $(minikube docker-env)

### build the docker images on minikube
cd travel-agency-service
docker build -t travel-agency-service .
cd ../client-service
docker build -t client-service .
cd ..

### secret and mongodb
kubectl delete -f travel-agency-service/secret.yaml
kubectl delete -f travel-agency-service/mongo-deployment.yaml

kubectl create -f travel-agency-service/secret.yaml
kubectl create -f travel-agency-service/mongo-deployment.yaml

### travel-agency-service
kubectl delete -f travel-agency-service/travel-agency-deployment.yaml
kubectl create -f travel-agency-service/travel-agency-deployment.yaml

### client-service
kubectl delete configmap client-service
kubectl delete -f client-service/client-service-deployment.yaml

kubectl create -f client-service/client-config.yaml
kubectl create -f client-service/client-service-deployment.yaml

# Check that the pods are running
kubectl get pods

4. Service Discovery

This project provides us with an implementation for the ServiceDiscovery interface in Kubernetes. In a microservices environment, there are usually multiple pods running the same service. Kubernetes exposes the service as a collection of endpoints that can be fetched and reached from within a Spring Boot Application running in a pod in the same Kubernetes cluster.

For instance, in our example, we have multiple replicas of the travel agent service, which is accessed from our client service as http://travel-agency-service:8080. However, this internally would translate into accessing different pods such as travel-agency-service-7c9cfff655-4hxnp.

Spring Cloud Kubernetes Ribbon uses this feature to load balance between the different endpoints of a service.

We can easily use Service Discovery by adding the spring-cloud-starter-kubernetes dependency on our client application:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>

Also, we should add @EnableDiscoveryClient and inject the DiscoveryClient into the ClientController by using @Autowired in our class:

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@RestController
public class ClientController {
    @Autowired
    private DiscoveryClient discoveryClient;
}

5. ConfigMaps

Typically, microservices require some kind of configuration management. For instance, in Spring Cloud applications, we would use a Spring Cloud Config Server.

However, we can achieve this by using ConfigMaps provided by Kubernetes – provided that we intend to use it for non-sensitive, unencrypted information only. Alternatively, if the information we want to share is sensitive, then we should opt to use Secrets instead.

In our example, we’re using ConfigMaps on the client-service Spring Boot application. Let’s create a client-config.yaml file to define the ConfigMap of the client-service:

apiVersion: v1 by d
kind: ConfigMap
metadata:
  name: client-service
data:
  application.properties: |-
    bean.message=Testing reload! Message from backend is: %s <br/> Services : %s

It’s important that the name of the ConfigMap matches the name of the application as specified in our “application.properties” file. In this case, it’s client-service. Next, we should create the ConfigMap for client-service on Kubernetes:

kubectl create -f client-config.yaml

Now, let’s create a configuration class ClientConfig with the @Configuration and @ConfigurationProperties and inject into the ClientController:

@Configuration
@ConfigurationProperties(prefix = "bean")
public class ClientConfig {

    private String message = "Message from backend is: %s <br/> Services : %s";

    // getters and setters
}
@RestController
public class ClientController {

    @Autowired
    private ClientConfig config;

    @GetMapping
    public String load() {
        return String.format(config.getMessage(), "", "");
    }
}

If we don’t specify a ConfigMap, then we should expect to see the default message, which is set in the class. However, when we create the ConfigMap, this default message gets overridden by that property.

Additionally, every time we decide to update the ConfigMap, the message on the page changes accordingly:

kubectl edit configmap client-service

6. Secrets

Let’s look at how Secrets work by looking at the specification of MongoDB connection settings in our example. We’re going to create environment variables on Kubernetes, which will then be injected into the Spring Boot application.

6.1. Create a Secret

The first step is to create a secret.yaml file, encoding the username and password to Base 64:

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
data:
  username: dXNlcg==
  password: cDQ1NXcwcmQ=

Let’s apply the Secret configuration on the Kubernetes cluster:

kubectl apply -f secret.yaml

6.2. Create a MongoDB Service

We should now create the MongoDB service and the deployment travel-agency-deployment.yaml file. In particular, in the deployment part, we’ll use the Secret username and password that we defined previously:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mongo
spec:
  replicas: 1
  template:
    metadata:
      labels:
        service: mongo
      name: mongodb-service
    spec:
      containers:
      - args:
        - mongod
        - --smallfiles
        image: mongo:latest
        name: mongo
        env:
          - name: MONGO_INITDB_ROOT_USERNAME
            valueFrom:
              secretKeyRef:
                name: db-secret
                key: username
          - name: MONGO_INITDB_ROOT_PASSWORD
            valueFrom:
              secretKeyRef:
                name: db-secret
                key: password

By default, the mongo:latest image will create a user with username and password on a database named admin.

6.3. Setup MongoDB on Travel Agency Service

It’s important to update the application properties to add the database related information. While we can freely specify the database name admin, here we’re hiding the most sensitive information such as the username and the password:

spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.secrets.name=db-secret
spring.data.mongodb.host=mongodb-service
spring.data.mongodb.port=27017
spring.data.mongodb.database=admin
spring.data.mongodb.username=${MONGO_USERNAME}
spring.data.mongodb.password=${MONGO_PASSWORD}

Now, let’s take a look at our travel-agency-deployment property file to update the services and deployments with the username and password information required to connect to the mongodb-service.

Here’s the relevant section of the file, with the part related to the MongoDB connection:

env:
  - name: MONGO_USERNAME
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: username
  - name: MONGO_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: password

7. Communication with Ribbon

In a microservices environment, we generally need the list of pods where our service is replicated in order to perform load-balancing. This is accomplished by using a mechanism provided by Spring Cloud Kubernetes Ribbon. This mechanism can automatically discover and reach all the endpoints of a specific service, and subsequently, it populates a Ribbon ServerList with information about the endpoints.

Let’s start by adding the spring-cloud-starter-kubernetes-ribbon dependency to our client-service pom.xml file:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
</dependency>

The next step is to add the annotation @RibbonClient to our client-service application:

@RibbonClient(name = "travel-agency-service")

When the list of the endpoints is populated, the Kubernetes client will search the registered endpoints living in the current namespace/project matching the service name defined using the @RibbonClient annotation.

We also need to enable the ribbon client in the application properties:

ribbon.http.client.enabled=true

8. Additional Features

8.1. Hystrix

Hystrix helps in building a fault-tolerant and resilient application. Its main aims are fail fast and rapid recovery.

In particular, in our example, we’re using Hystrix to implement the circuit breaker pattern on the client-server by annotating the Spring Boot application class with @EnableCircuitBreaker.

Additionally, we’re using the fallback functionality by annotating the method TravelAgencyService.getDeals() with @HystrixCommand(). This means that in case of fallback the getFallBackName() will be called and “Fallback” message returned:

@HystrixCommand(fallbackMethod = "getFallbackName", commandProperties = { 
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") })
public String getDeals() {
    return this.restTemplate.getForObject("http://travel-agency-service:8080/deals", String.class);
}

private String getFallbackName() {
    return "Fallback";
}

8.2. Pod Health Indicator

We can take advantage of Spring Boot HealthIndicator and Spring Boot Actuator to expose health-related information to the user.

In particular, the Kubernetes health indicator provides:

  • pod name
  • IP address
  • namespace
  • service account
  • node name
  • a flag that indicates whether the Spring Boot application is internal or external to Kubernetes

9. Conclusion

In this article, we provide a thorough overview of the Spring Cloud Kubernetes project.

So why should we use it? If we root for Kubernetes as a microservices platform but still appreciate the features of Spring Cloud, then Spring Cloud Kubernetes gives us the best of both worlds.

The full source code of the example is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE