Introduction
Spring Cloud Kubernetes provides Spring Cloud common interface implementations that leverage Kubernetes native services. It allows Spring Boot applications to seamlessly integrate with Kubernetes features like ConfigMaps, Secrets, Service Discovery, and Load Balancing.
Dependencies and Setup
1. Maven Dependencies
<properties>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<spring-cloud-kubernetes.version>3.1.0</spring-cloud-kubernetes.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Cloud Kubernetes Core -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-fabric8</artifactId>
<version>${spring-cloud-kubernetes.version}</version>
</dependency>
<!-- Spring Cloud Kubernetes Config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-fabric8-config</artifactId>
<version>${spring-cloud-kubernetes.version}</version>
</dependency>
<!-- Spring Cloud Kubernetes Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-fabric8-discovery</artifactId>
<version>${spring-cloud-kubernetes.version}</version>
</dependency>
<!-- Spring Cloud Kubernetes LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-fabric8-loadbalancer</artifactId>
<version>${spring-cloud-kubernetes.version}</version>
</dependency>
<!-- Spring Cloud Starter Bootstrap (for bootstrap.yml) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Spring Cloud Circuit Breaker -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- Micrometer Tracing -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
</dependencies>
<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>
2. Application Configuration
# bootstrap.yml
spring:
application:
name: user-service
cloud:
kubernetes:
config:
name: ${spring.application.name}
namespace: default
sources:
- name: ${spring.application.name}
- name: common-config
enable-api: true
fail-fast: true
retry:
max-attempts: 6
max-interval: 2000
discovery:
all-namespaces: false
primary-port-name: http
reload:
enabled: true
mode: event
strategy: refresh
management:
endpoints:
web:
exposure:
include: health,info,metrics,configmaps,services
endpoint:
health:
show-details: always
configmaps:
enabled: true
services:
enabled: true
Core Configuration Management
1. ConfigMap and Secret Integration
@Configuration
@ConfigurationProperties(prefix = "app")
@Data
public class ApplicationProperties {
private DatabaseProperties database;
private SecurityProperties security;
private CacheProperties cache;
private ExternalServices external;
@Data
public static class DatabaseProperties {
private String url;
private String username;
private String password;
private int connectionTimeout;
private int maxPoolSize;
}
@Data
public static class SecurityProperties {
private String jwtSecret;
private long jwtExpirationMs;
private String corsAllowedOrigins;
}
@Data
public static class CacheProperties {
private long ttlMinutes;
private int maxSize;
private boolean enabled;
}
@Data
public static class ExternalServices {
private String paymentServiceUrl;
private String notificationServiceUrl;
private int timeoutMs;
}
}
2. Kubernetes ConfigMap Example
# k8s/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: user-service namespace: default labels: app: user-service version: v1 data: application.yml: | app: database: url: jdbc:postgresql://postgresql:5432/users connection-timeout: 30000 max-pool-size: 20 security: jwt-expiration-ms: 86400000 cors-allowed-origins: "http://localhost:3000,https://myapp.com" cache: ttl-minutes: 30 max-size: 1000 enabled: true external: payment-service-url: http://payment-service:8080 notification-service-url: http://notification-service:8080 timeout-ms: 5000 logging: level: com.example.userservice: DEBUG management: health: probes: enabled: true livenessstate: enabled: true readinessstate: enabled: true --- # Secret for sensitive data apiVersion: v1 kind: Secret metadata: name: user-service-secrets namespace: default type: Opaque data: database-password: cG9zdGdyZXNfcGFzc3dvcmQ= # base64 encoded jwt-secret: c3VwZXJfc2VjcmV0X2tleQ== # base64 encoded
Service Discovery and Load Balancing
1. Service Discovery Client
@Service
@Slf4j
public class ServiceDiscoveryClient {
private final DiscoveryClient discoveryClient;
private final LoadBalancerClient loadBalancerClient;
private final ApplicationProperties properties;
public ServiceDiscoveryClient(DiscoveryClient discoveryClient,
LoadBalancerClient loadBalancerClient,
ApplicationProperties properties) {
this.discoveryClient = discoveryClient;
this.loadBalancerClient = loadBalancerClient;
this.properties = properties;
}
public List<ServiceInstance> getAvailableInstances(String serviceName) {
return discoveryClient.getInstances(serviceName);
}
public List<String> getAvailableServices() {
return discoveryClient.getServices();
}
public URI getServiceUrl(String serviceName) {
ServiceInstance instance = loadBalancerClient.choose(serviceName);
if (instance != null) {
return instance.getUri();
}
throw new ServiceUnavailableException("Service not available: " + serviceName);
}
public Map<String, ServiceInstance> getServiceInstances() {
Map<String, ServiceInstance> instances = new HashMap<>();
getAvailableServices().forEach(service -> {
List<ServiceInstance> serviceInstances = getAvailableInstances(service);
if (!serviceInstances.isEmpty()) {
instances.put(service, serviceInstances.get(0));
}
});
return instances;
}
}
2. REST Client with Load Balancing
@Configuration
public class RestClientConfig {
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
@Service
@Slf4j
public class PaymentServiceClient {
private final RestTemplate restTemplate;
private final WebClient webClient;
private final ApplicationProperties properties;
private final CircuitBreakerFactory circuitBreakerFactory;
public PaymentServiceClient(@LoadBalanced RestTemplate restTemplate,
@LoadBalanced WebClient.Builder webClientBuilder,
ApplicationProperties properties,
CircuitBreakerFactory circuitBreakerFactory) {
this.restTemplate = restTemplate;
this.webClient = webClientBuilder.build();
this.properties = properties;
this.circuitBreakerFactory = circuitBreakerFactory;
}
public PaymentResponse processPayment(PaymentRequest request) {
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("payment-service");
return circuitBreaker.run(() -> {
String url = properties.getExternal().getPaymentServiceUrl() + "/api/payments";
ResponseEntity<PaymentResponse> response = restTemplate.postForEntity(
url, request, PaymentResponse.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
}
throw new PaymentServiceException("Payment service returned: " + response.getStatusCode());
}, throwable -> {
log.error("Payment service call failed, using fallback", throwable);
return getFallbackPaymentResponse(request);
});
}
public Mono<PaymentResponse> processPaymentAsync(PaymentRequest request) {
return webClient.post()
.uri(properties.getExternal().getPaymentServiceUrl() + "/api/payments")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResponse.class)
.timeout(Duration.ofMillis(properties.getExternal().getTimeoutMs()))
.onErrorResume(throwable -> {
log.error("Async payment service call failed", throwable);
return Mono.just(getFallbackPaymentResponse(request));
});
}
private PaymentResponse getFallbackPaymentResponse(PaymentRequest request) {
return PaymentResponse.builder()
.paymentId("fallback-" + UUID.randomUUID())
.status("PENDING")
.message("Payment queued for processing")
.build();
}
}
Configuration Reloading
1. Configuration Change Detection
@Component
@Slf4j
public class ConfigurationChangeListener {
private final ApplicationProperties applicationProperties;
private final ContextRefresher contextRefresher;
public ConfigurationChangeListener(ApplicationProperties applicationProperties,
ContextRefresher contextRefresher) {
this.applicationProperties = applicationProperties;
this.contextRefresher = contextRefresher;
}
@EventListener
public void onConfigMapChange(ConfigMapChangeEvent event) {
log.info("ConfigMap changed: {}", event.getConfigMapName());
if (event.getConfigMapName().equals("user-service")) {
log.info("Reloading application configuration...");
contextRefresher.refresh();
// Log the updated configuration
log.info("Updated database URL: {}", applicationProperties.getDatabase().getUrl());
log.info("Updated cache TTL: {} minutes", applicationProperties.getCache().getTtlMinutes());
}
}
@EventListener
public void onSecretChange(SecretChangeEvent event) {
log.info("Secret changed: {}", event.getSecretName());
if (event.getSecretName().equals("user-service-secrets")) {
log.info("Reloading sensitive configuration...");
contextRefresher.refresh();
// Note: Be careful about logging sensitive data
log.info("Security configuration reloaded");
}
}
}
2. Dynamic Configuration Updates
@Service
@Slf4j
@RefreshScope
public class DynamicConfigurationService {
private final ApplicationProperties properties;
private final CacheManager cacheManager;
@Value("${app.cache.ttl-minutes:30}")
private Integer cacheTtlMinutes;
@Value("${app.external.timeout-ms:5000}")
private Integer externalTimeoutMs;
public DynamicConfigurationService(ApplicationProperties properties,
CacheManager cacheManager) {
this.properties = properties;
this.cacheManager = cacheManager;
}
@EventListener
public void onApplicationEvent(EnvironmentChangeEvent event) {
log.info("Environment changed: {}", event.getKeys());
// Update cache configuration if TTL changed
if (event.getKeys().contains("app.cache.ttl-minutes")) {
updateCacheConfiguration();
}
// Update HTTP client timeouts if changed
if (event.getKeys().contains("app.external.timeout-ms")) {
updateHttpClientTimeouts();
}
}
private void updateCacheConfiguration() {
log.info("Updating cache TTL to: {} minutes", cacheTtlMinutes);
Cache usersCache = cacheManager.getCache("users");
if (usersCache instanceof RedisCache redisCache) {
// Update Redis cache configuration
// Implementation depends on your cache provider
}
}
private void updateHttpClientTimeouts() {
log.info("Updating external service timeout to: {} ms", externalTimeoutMs);
// Update any configured HTTP clients with new timeout
}
@Scheduled(fixedRate = 30000) // Check every 30 seconds
public void validateConfiguration() {
try {
validateDatabaseConfiguration();
validateExternalServices();
} catch (Exception e) {
log.warn("Configuration validation failed", e);
}
}
private void validateDatabaseConfiguration() {
// Validate database connection and configuration
}
private void validateExternalServices() {
// Validate external service endpoints
}
}
Kubernetes Probes and Health Checks
1. Custom Health Indicators
@Component
@Slf4j
public class KubernetesHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
private final ApplicationProperties properties;
private final ServiceDiscoveryClient serviceDiscovery;
public KubernetesHealthIndicator(DataSource dataSource,
ApplicationProperties properties,
ServiceDiscoveryClient serviceDiscovery) {
this.dataSource = dataSource;
this.properties = properties;
this.serviceDiscovery = serviceDiscovery;
}
@Override
public Health health() {
Health.Builder status = Health.up();
// Check database connectivity
if (!isDatabaseHealthy()) {
status.down().withDetail("database", "Unable to connect to database");
}
// Check external service connectivity
Map<String, String> externalServices = checkExternalServices();
externalServices.forEach(status::withDetail);
// Check disk space
checkDiskSpace().ifPresent(diskStatus ->
status.withDetail("disk", diskStatus));
return status.build();
}
private boolean isDatabaseHealthy() {
try (Connection connection = dataSource.getConnection()) {
return connection.isValid(5); // 5 second timeout
} catch (Exception e) {
log.warn("Database health check failed", e);
return false;
}
}
private Map<String, String> checkExternalServices() {
Map<String, String> serviceStatus = new HashMap<>();
try {
List<String> services = serviceDiscovery.getAvailableServices();
serviceStatus.put("discoveredServices", String.valueOf(services.size()));
// Check critical external services
if (isServiceAvailable("payment-service")) {
serviceStatus.put("paymentService", "AVAILABLE");
} else {
serviceStatus.put("paymentService", "UNAVAILABLE");
}
} catch (Exception e) {
log.warn("Service discovery health check failed", e);
serviceStatus.put("serviceDiscovery", "FAILED");
}
return serviceStatus;
}
private boolean isServiceAvailable(String serviceName) {
try {
List<ServiceInstance> instances = serviceDiscovery.getAvailableInstances(serviceName);
return !instances.isEmpty();
} catch (Exception e) {
return false;
}
}
private Optional<String> checkDiskSpace() {
File root = new File("/");
long freeSpace = root.getFreeSpace();
long totalSpace = root.getTotalSpace();
double freePercentage = (double) freeSpace / totalSpace * 100;
if (freePercentage < 10.0) {
return Optional.of(String.format("LOW: %.1f%% free", freePercentage));
}
return Optional.empty();
}
}
@Component
@Slf4j
public class LivenessProbeIndicator implements HealthIndicator {
private final AtomicBoolean isLive = new AtomicBoolean(true);
@Override
public Health health() {
if (isLive.get()) {
return Health.up().withDetail("liveness", "Application is live").build();
} else {
return Health.down().withDetail("liveness", "Application is not live").build();
}
}
public void setLive(boolean live) {
isLive.set(live);
log.info("Liveness state changed to: {}", live);
}
}
@Component
@Slf4j
public class ReadinessProbeIndicator implements HealthIndicator {
private final AtomicBoolean isReady = new AtomicBoolean(true);
private final Set<String> blockingServices = ConcurrentHashMap.newKeySet();
@Override
public Health health() {
if (isReady.get() && blockingServices.isEmpty()) {
return Health.up()
.withDetail("readiness", "Application is ready to receive traffic")
.build();
} else {
return Health.down()
.withDetail("readiness", "Application is not ready")
.withDetail("blockingServices", blockingServices)
.build();
}
}
public void setReady(boolean ready) {
isReady.set(ready);
log.info("Readiness state changed to: {}", ready);
}
public void addBlockingService(String service) {
blockingServices.add(service);
log.info("Added blocking service: {}", service);
}
public void removeBlockingService(String service) {
blockingServices.remove(service);
log.info("Removed blocking service: {}", service);
}
}
Kubernetes Deployment Manifests
1. Complete Deployment Configuration
# k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: default labels: app: user-service version: v1 spec: replicas: 3 selector: matchLabels: app: user-service strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: user-service version: v1 annotations: config.reload.stakater.com/reload: "user-service" spec: serviceAccountName: user-service-account containers: - name: user-service image: myregistry/user-service:1.0.0 ports: - name: http containerPort: 8080 protocol: TCP - name: management containerPort: 8081 protocol: TCP env: - name: SPRING_PROFILES_ACTIVE value: "kubernetes" - name: JAVA_OPTS value: "-Xmx512m -Xms256m -XX:+UseG1GC" resources: requests: memory: "512Mi" cpu: "200m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health/liveness port: 8081 scheme: HTTP initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8081 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 startupProbe: httpGet: path: /actuator/health/startup port: 8081 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 10 volumeMounts: - name: config-volume mountPath: /config readOnly: true volumes: - name: config-volume configMap: name: user-service items: - key: application.yml path: application.yml imagePullSecrets: - name: registry-credentials --- # Service apiVersion: v1 kind: Service metadata: name: user-service namespace: default labels: app: user-service spec: selector: app: user-service ports: - name: http port: 8080 targetPort: 8080 protocol: TCP - name: management port: 8081 targetPort: 8081 protocol: TCP type: ClusterIP --- # Service Account for Kubernetes API access apiVersion: v1 kind: ServiceAccount metadata: name: user-service-account namespace: default --- # Role-based access control apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: user-service-role rules: - apiGroups: [""] resources: ["configmaps", "secrets", "services", "pods"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps"] resourceNames: ["user-service", "common-config"] verbs: ["get"] - apiGroups: [""] resources: ["secrets"] resourceNames: ["user-service-secrets"] verbs: ["get"] --- # Role binding apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: user-service-role-binding namespace: default subjects: - kind: ServiceAccount name: user-service-account namespace: default roleRef: kind: Role name: user-service-role apiGroup: rbac.authorization.k8s.io
Advanced Features
1. Leader Election
@Component
@Slf4j
public class KubernetesLeaderElection {
private final KubernetesClient kubernetesClient;
private final String namespace;
private final String lockName;
private final String podName;
private volatile boolean isLeader = false;
public KubernetesLeaderElection(KubernetesClient kubernetesClient,
@Value("${spring.cloud.kubernetes.config.namespace:default}") String namespace,
@Value("${POD_NAME:unknown}") String podName) {
this.kubernetesClient = kubernetesClient;
this.namespace = namespace;
this.lockName = "user-service-leader";
this.podName = podName;
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
startLeaderElection();
}
private void startLeaderElection() {
Thread leaderElectionThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
attemptLeadership();
Thread.sleep(10000); // Check every 10 seconds
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.error("Leader election error", e);
}
}
});
leaderElectionThread.setDaemon(true);
leaderElectionThread.setName("leader-election");
leaderElectionThread.start();
}
private void attemptLeadership() {
try {
Lease lease = kubernetesClient.leases()
.inNamespace(namespace)
.withName(lockName)
.get();
if (lease == null) {
createLease();
return;
}
// Check if we can acquire the lease
if (canAcquireLease(lease)) {
acquireLease(lease);
} else {
releaseLeadership();
}
} catch (Exception e) {
log.error("Failed to check lease", e);
releaseLeadership();
}
}
private boolean canAcquireLease(Lease lease) {
// Implement lease acquisition logic
// Check lease expiration, current holder, etc.
return true; // Simplified
}
private void createLease() {
// Create initial lease
log.info("Creating new lease as leader: {}", podName);
isLeader = true;
onLeadershipAcquired();
}
private void acquireLease(Lease lease) {
if (!isLeader) {
log.info("Acquired leadership: {}", podName);
isLeader = true;
onLeadershipAcquired();
}
}
private void releaseLeadership() {
if (isLeader) {
log.info("Releasing leadership: {}", podName);
isLeader = false;
onLeadershipLost();
}
}
private void onLeadershipAcquired() {
// Start leader-specific tasks
log.info("Starting leader-specific tasks");
}
private void onLeadershipLost() {
// Stop leader-specific tasks
log.info("Stopping leader-specific tasks");
}
public boolean isLeader() {
return isLeader;
}
}
2. Pod Information Access
@Service
@Slf4j
public class KubernetesPodInfoService {
private final KubernetesClient kubernetesClient;
private final String namespace;
private final String podName;
public KubernetesPodInfoService(KubernetesClient kubernetesClient,
@Value("${spring.cloud.kubernetes.config.namespace:default}") String namespace,
@Value("${POD_NAME:unknown}") String podName) {
this.kubernetesClient = kubernetesClient;
this.namespace = namespace;
this.podName = podName;
}
public Pod getCurrentPod() {
return kubernetesClient.pods()
.inNamespace(namespace)
.withName(podName)
.get();
}
public Map<String, String> getPodLabels() {
Pod pod = getCurrentPod();
return pod != null ? pod.getMetadata().getLabels() : Map.of();
}
public List<Pod> getPodsForService(String appLabel) {
return kubernetesClient.pods()
.inNamespace(namespace)
.withLabels(Map.of("app", appLabel))
.list()
.getItems();
}
public PodInfo getPodInfo() {
Pod pod = getCurrentPod();
if (pod == null) {
return PodInfo.unknown();
}
return PodInfo.builder()
.name(pod.getMetadata().getName())
.namespace(pod.getMetadata().getNamespace())
.ip(pod.getStatus().getPodIP())
.nodeName(pod.getSpec().getNodeName())
.startTime(pod.getStatus().getStartTime())
.phase(pod.getStatus().getPhase())
.labels(pod.getMetadata().getLabels())
.build();
}
@Data
@Builder
public static class PodInfo {
private String name;
private String namespace;
private String ip;
private String nodeName;
private String startTime;
private String phase;
private Map<String, String> labels;
public static PodInfo unknown() {
return PodInfo.builder()
.name("unknown")
.namespace("unknown")
.phase("Unknown")
.labels(Map.of())
.build();
}
}
}
Testing
1. Spring Cloud Kubernetes Test
@SpringBootTest
@EnableConfigurationProperties
@TestPropertySource(properties = {
"spring.cloud.kubernetes.enabled=true",
"spring.cloud.kubernetes.config.enabled=true",
"spring.main.allow-bean-definition-overriding=true"
})
@DirtiesContext
class SpringCloudKubernetesIntegrationTest {
@MockBean
private KubernetesClient kubernetesClient;
@Autowired
private ApplicationProperties applicationProperties;
@Autowired
private ServiceDiscoveryClient serviceDiscovery;
@Test
void shouldLoadConfigurationFromKubernetes() {
assertNotNull(applicationProperties);
assertNotNull(applicationProperties.getDatabase());
assertNotNull(applicationProperties.getSecurity());
}
@Test
void shouldDiscoverServices() {
List<String> services = serviceDiscovery.getAvailableServices();
assertNotNull(services);
}
}
@SpringBootTest
@ActiveProfiles("test")
@Testcontainers
class KubernetesConfigurationTest {
@Container
static KubernetesServer kubernetesServer = new KubernetesServer();
@DynamicPropertySource
static void kubernetesProperties(DynamicPropertyRegistry registry) {
registry.add("spring.cloud.kubernetes.config.enabled", () -> true);
registry.add("spring.cloud.kubernetes.discovery.enabled", () -> true);
}
@Test
void shouldWorkWithTestContainers() {
// Test with embedded Kubernetes
}
}
Best Practices
1. Security Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health/**").permitAll()
.requestMatchers("/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
}
2. Resource Management
@Component
@Slf4j
public class ResourceCleanupService {
private final KubernetesClient kubernetesClient;
@PreDestroy
public void cleanup() {
log.info("Cleaning up Kubernetes resources...");
// Clean up any dynamically created resources
}
@EventListener
public void onShutdown(ContextClosedEvent event) {
log.info("Application context closed, performing final cleanup...");
}
}
Conclusion
Spring Cloud Kubernetes provides powerful integration between Spring Boot applications and Kubernetes, offering:
- Dynamic configuration management with ConfigMaps and Secrets
- Service discovery and load balancing
- Health checks and readiness/liveness probes
- Leader election for distributed coordination
- Seamless integration with Spring ecosystem
By leveraging these features, Java applications can fully embrace cloud-native patterns and take advantage of Kubernetes' powerful orchestration capabilities while maintaining the developer-friendly Spring programming model.
Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/
OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/
OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/
Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.
https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics
Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2
Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide
Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2
Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide
Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.
https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server
Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.