Skip to main content

Spring Cloud Kubernetes

Introduction

Spring Cloud Kubernetes is a specialized module within the Spring Cloud framework that bridges the gap between Spring Boot applications and Kubernetes orchestration. It enables applications to leverage Kubernetes-native features seamlessly, making your Spring applications truly cloud-native when deployed in Kubernetes environments.

In this guide, we'll explore how Spring Cloud Kubernetes allows your applications to:

  • Discover services within the Kubernetes cluster
  • Load configuration from Kubernetes ConfigMaps and Secrets
  • Implement health checks compatible with Kubernetes probes
  • Enable event-driven architecture using Kubernetes events

Whether you're new to Kubernetes or looking to optimize your Spring Boot applications for Kubernetes deployment, this guide will provide the foundation you need to get started.

Prerequisites

Before diving into Spring Cloud Kubernetes, you should have:

  • Basic knowledge of Spring Boot and Spring Cloud
  • Understanding of containerization concepts
  • Familiarity with Kubernetes basics (Pods, Services, ConfigMaps)
  • JDK 8 or higher
  • Maven or Gradle build tools
  • Access to a Kubernetes cluster (Minikube for local development)

Setting Up Spring Cloud Kubernetes

Adding Dependencies

First, let's add the necessary Spring Cloud Kubernetes dependencies to your Spring Boot project:

For Maven, add the following to your pom.xml:

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

For Gradle, add this to your build.gradle:

groovy
implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-all'

Additionally, you need to ensure your Spring Cloud version is set correctly in your dependency management section:

xml
<properties>
<spring-cloud.version>2021.0.0</spring-cloud.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Enabling Spring Cloud Kubernetes

Enable the Spring Cloud Kubernetes features in your application by adding the @EnableDiscoveryClient annotation to your main application class:

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class MyKubernetesApplication {
public static void main(String[] args) {
SpringApplication.run(MyKubernetesApplication.class, args);
}
}

Key Features of Spring Cloud Kubernetes

1. Service Discovery in Kubernetes

Spring Cloud Kubernetes automatically integrates with Kubernetes service discovery, allowing your applications to locate other services within the cluster without hardcoding URLs.

First, enable the Kubernetes discovery client in your application.properties or application.yml:

yaml
spring:
cloud:
kubernetes:
discovery:
enabled: true

Now you can use the DiscoveryClient to find and communicate with other services:

java
@RestController
public class ServiceController {

@Autowired
private DiscoveryClient discoveryClient;

@GetMapping("/services")
public List<String> getServices() {
return discoveryClient.getServices();
}

@GetMapping("/service/{name}")
public List<ServiceInstance> getInstances(@PathVariable String name) {
return discoveryClient.getInstances(name);
}
}

When one service needs to call another service, you can use Spring's RestTemplate with service discovery:

java
@Configuration
public class AppConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;

public User getUserDetails(String userId) {
// The service name directly corresponds to the Kubernetes service name
return restTemplate.getForObject("http://user-service/users/{id}", User.class, userId);
}
}

2. Configuration Management with ConfigMaps and Secrets

Spring Cloud Kubernetes allows your application to load configuration from Kubernetes ConfigMaps and Secrets automatically.

Using ConfigMaps

First, create a ConfigMap in your Kubernetes cluster:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
application.properties: |
app.message=Hello from Kubernetes ConfigMap
app.description=This config comes from Kubernetes

Enable ConfigMap loading in your application:

yaml
spring:
cloud:
kubernetes:
config:
enabled: true
name: my-app-config
namespace: default

Now you can access these properties in your Spring application:

java
@RestController
public class ConfigController {

@Value("${app.message}")
private String message;

@Value("${app.description}")
private String description;

@GetMapping("/config")
public Map<String, String> getConfig() {
Map<String, String> config = new HashMap<>();
config.put("message", message);
config.put("description", description);
return config;
}
}

Using Secrets

For sensitive information, use Kubernetes Secrets:

yaml
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
type: Opaque
data:
# Values must be base64 encoded
db.username: YWRtaW4= # "admin" encoded in base64
db.password: cGFzc3dvcmQ= # "password" encoded in base64

Enable Secret loading:

yaml
spring:
cloud:
kubernetes:
secrets:
enabled: true
name: my-app-secrets
namespace: default

Access secret values in your application:

java
@Configuration
@ConfigurationProperties(prefix = "db")
public class DatabaseConfig {
private String username;
private String password;

// getters and setters

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

3. Health Checks with Kubernetes Probes

Spring Cloud Kubernetes integrates Spring Boot Actuator health endpoints with Kubernetes liveness and readiness probes.

Add the Actuator dependency:

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Configure Actuator endpoints:

yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
probes:
enabled: true

Create a custom health indicator:

java
@Component
public class CustomHealthIndicator implements HealthIndicator {

@Override
public Health health() {
// Add your health check logic
boolean isHealthy = checkDatabaseConnection();

if (isHealthy) {
return Health.up()
.withDetail("database", "Connected")
.build();
} else {
return Health.down()
.withDetail("database", "Disconnected")
.build();
}
}

private boolean checkDatabaseConnection() {
// Implement your database connection check
return true; // Simplified for this example
}
}

In your Kubernetes deployment manifest, configure the probes:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-spring-app
spec:
template:
spec:
containers:
- name: my-spring-app
image: my-spring-app:latest
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5

4. Using Kubernetes Events

Spring Cloud Kubernetes allows your application to react to Kubernetes events.

First, make sure your application has the proper permissions by creating a Role and RoleBinding:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

Configure the event handling in your application:

java
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class PodEventWatcher {

@Autowired
private KubernetesClient kubernetesClient;

@PostConstruct
public void init() {
kubernetesClient.pods().inNamespace("default").watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
System.out.println("Pod " + pod.getMetadata().getName() +
" received action: " + action.name());

// React to pod events here
if (action == Action.ADDED) {
System.out.println("New pod created: " + pod.getMetadata().getName());
} else if (action == Action.DELETED) {
System.out.println("Pod deleted: " + pod.getMetadata().getName());
}
}

@Override
public void onClose(WatcherException e) {
System.out.println("Watcher closed due to: " + e.getMessage());
}
});
}
}

Real-World Example: Building a Resilient Microservice

Let's put everything together to build a resilient microservice using Spring Cloud Kubernetes:

Step 1: Create a Basic Spring Boot Project

Start with a Spring Boot project with these dependencies:

  • Spring Web
  • Spring Cloud Kubernetes All
  • Spring Boot Actuator

Step 2: Configure the Application

Create an application.yml file:

yaml
spring:
application:
name: product-service
cloud:
kubernetes:
discovery:
enabled: true
config:
enabled: true
sources:
- name: ${spring.application.name}
reload:
enabled: true
mode: polling
period: 5000
secrets:
enabled: true
name: product-service-secrets

management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
probes:
enabled: true

Step 3: Create the Service Layer

java
@Service
public class ProductService {

@Value("${data.source:default}")
private String dataSource;

@Autowired
private RestTemplate restTemplate;

public List<Product> getAllProducts() {
System.out.println("Reading products from: " + dataSource);

// In a real application, fetch from a database or another service
List<Product> products = new ArrayList<>();
products.add(new Product("1", "Laptop", 999.99));
products.add(new Product("2", "Phone", 699.99));

// Call another service using discovery
if ("extended".equals(dataSource)) {
try {
Product[] additionalProducts = restTemplate.getForObject(
"http://inventory-service/products/premium", Product[].class);
products.addAll(Arrays.asList(additionalProducts));
} catch (Exception e) {
System.err.println("Failed to fetch premium products: " + e.getMessage());
}
}

return products;
}
}

Step 4: Create the Controller

java
@RestController
@RequestMapping("/products")
public class ProductController {

@Autowired
private ProductService productService;

@GetMapping
public List<Product> getProducts() {
return productService.getAllProducts();
}

@GetMapping("/health")
public Map<String, String> health() {
Map<String, String> status = new HashMap<>();
status.put("status", "UP");
status.put("timestamp", new Date().toString());
return status;
}
}

Step 5: Create a Custom Health Indicator

java
@Component
public class ProductServiceHealthIndicator implements HealthIndicator {

@Override
public Health health() {
boolean serviceHealthy = checkServiceHealth();

if (serviceHealthy) {
return Health.up()
.withDetail("service", "Operational")
.build();
} else {
return Health.down()
.withDetail("service", "Not Operational")
.build();
}
}

private boolean checkServiceHealth() {
// Implement real health check logic
return true;
}
}

Step 6: Create Kubernetes Manifests

ConfigMap:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: product-service
data:
application.yml: |
data:
source: extended

Secret:

yaml
apiVersion: v1
kind: Secret
metadata:
name: product-service-secrets
type: Opaque
data:
database.password: cGFzc3dvcmQxMjM=

Deployment:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 2
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product-service
image: product-service:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5

Service:

yaml
apiVersion: v1
kind: Service
metadata:
name: product-service
spec:
selector:
app: product-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP

Common Challenges and Solutions

Challenge 1: Unauthorized Access to Kubernetes API

Symptom: Your application logs show 403 Forbidden errors when trying to access the Kubernetes API.

Solution: Create proper RBAC rules for your application's service account:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: spring-cloud-kubernetes
rules:
- apiGroups: [""]
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: spring-cloud-kubernetes
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: spring-cloud-kubernetes
subjects:
- kind: ServiceAccount
name: default

Challenge 2: ConfigMap Changes Not Reflected

Symptom: Updates to ConfigMaps are not automatically reflected in your application.

Solution: Enable reload capability in your application:

yaml
spring:
cloud:
kubernetes:
reload:
enabled: true
mode: polling
period: 5000

Also, add the Spring Cloud Kubernetes Config Watcher dependency:

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-fabric8-config</artifactId>
</dependency>

Challenge 3: Service Discovery Not Working

Symptom: Your application cannot discover other services in the cluster.

Solution: Ensure your services have the correct labels and selectors:

yaml
apiVersion: v1
kind: Service
metadata:
name: your-service-name
labels:
app: your-service-name
spec:
selector:
app: your-service-name

And verify your application configuration:

yaml
spring:
cloud:
kubernetes:
discovery:
enabled: true
all-namespaces: false # Set to true if services are in different namespaces

Summary

Spring Cloud Kubernetes serves as a critical bridge between the Spring Boot ecosystem and Kubernetes, allowing developers to build cloud-native applications that leverage the best of both worlds. We've explored how to:

  • Set up Spring Cloud Kubernetes in your Spring Boot applications
  • Implement service discovery using Kubernetes services
  • Load configuration from Kubernetes ConfigMaps and Secrets
  • Create health checks compatible with Kubernetes probes
  • React to Kubernetes events from within your application

By using Spring Cloud Kubernetes, you can create applications that are truly cloud-native and take full advantage of the Kubernetes orchestration platform while maintaining the development experience provided by Spring Boot.

Additional Resources

Exercises

  1. Basic Practice: Create a simple Spring Boot application with Spring Cloud Kubernetes and deploy it to a Kubernetes cluster (or Minikube). Make sure it can read configuration from a ConfigMap.

  2. Service Discovery: Build two microservices that communicate with each other using Kubernetes service discovery.

  3. Advanced Configuration: Create an application that loads some configuration from ConfigMaps and sensitive data from Secrets. Implement a mechanism to reload configuration without restarting the application.

  4. Health Checks: Implement custom health indicators in your application and configure proper liveness and readiness probes in your Kubernetes deployment.

  5. Event Handling: Build an application that watches for changes to ConfigMaps in its namespace and logs when changes occur.

By following this guide and working through these exercises, you'll gain a solid foundation in using Spring Cloud Kubernetes for your cloud-native applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)