Pod Disruption Budgets (PDBs) are Kubernetes resources that limit the number of concurrent disruptions to your application pods. This guide covers implementing and managing PDBs for Java applications using both declarative and programmatic approaches.
Core Concepts
What is a Pod Disruption Budget?
- Kubernetes resource that limits voluntary disruptions
- Ensures minimum available/healthy pods during maintenance
- Protects against node drains, cluster upgrades, etc.
- Works with both voluntary and involuntary disruptions
Key PDB Properties:
- minAvailable: Minimum number of pods that must be available
- maxUnavailable: Maximum number of pods that can be unavailable
- selector: Labels to identify pods covered by PDB
Dependencies and Setup
1. Maven Dependencies
<properties>
<fabric8.version>6.7.2</fabric8.version>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<dependencies>
<!-- Kubernetes Client -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${fabric8.version}</version>
</dependency>
<!-- Spring Boot (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Declarative PDB Configuration
1. Basic PDB Examples
# src/main/resources/k8s/pdb-basic.yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: java-app-pdb namespace: default labels: app: java-app component: pdb spec: minAvailable: 2 selector: matchLabels: app: java-app --- # Alternative using maxUnavailable apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: java-app-pdb-max spec: maxUnavailable: 1 selector: matchLabels: app: java-app
2. Multi-Tier Application PDBs
# src/main/resources/k8s/pdb-multi-tier.yaml # Web tier PDB - allow only 1 pod disruption apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-tier-pdb namespace: java-app spec: minAvailable: 2 selector: matchLabels: tier: web app: java-app --- # API tier PDB - more restrictive apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: api-tier-pdb namespace: java-app spec: minAvailable: 3 selector: matchLabels: tier: api app: java-app --- # Database tier PDB - highly available apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: db-tier-pdb namespace: java-app spec: minAvailable: 1 # For stateful applications selector: matchLabels: tier: database app: java-app
3. Stateful Application PDB
# src/main/resources/k8s/pdb-stateful.yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: kafka-pdb namespace: messaging spec: # For stateful apps like Kafka, ensure majority is available minAvailable: 2 selector: matchLabels: app: kafka component: broker --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: postgresql-pdb namespace: database spec: # For single-node databases, ensure at least one is always available maxUnavailable: 0 selector: matchLabels: app: postgresql role: master
4. Application-Specific PDB Strategies
# src/main/resources/k8s/pdb-strategies.yaml # Canary deployment strategy apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: canary-pdb annotations: strategy: canary spec: maxUnavailable: 10% selector: matchLabels: deployment: canary app: java-app --- # Blue-Green deployment strategy apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: blue-pdb annotations: strategy: blue-green spec: minAvailable: 100% # No disruptions during blue-green switch selector: matchLabels: version: blue app: java-app --- # Rolling update strategy apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: rolling-pdb annotations: strategy: rolling spec: maxUnavailable: 25% selector: matchLabels: app: java-app
Programmatic PDB Management
1. PDB Service Class
// src/main/java/com/example/pdb/service/PDBService.java
package com.example.pdb.service;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudgetBuilder;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudgetSpec;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudgetSpecBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class PDBService {
private static final Logger log = LoggerFactory.getLogger(PDBService.class);
private final KubernetesClient kubernetesClient;
public PDBService(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
/**
* Create a basic Pod Disruption Budget
*/
public PodDisruptionBudget createBasicPDB(String namespace, String name,
Map<String, String> selectorLabels,
Integer minAvailable, Integer maxUnavailable) {
PodDisruptionBudgetSpec spec = new PodDisruptionBudgetSpecBuilder()
.withNewSelector()
.withMatchLabels(selectorLabels)
.endSelector()
.withMinAvailable(minAvailable != null ? minAvailable.toString() : null)
.withMaxUnavailable(maxUnavailable != null ? maxUnavailable.toString() : null)
.build();
PodDisruptionBudget pdb = new PodDisruptionBudgetBuilder()
.withNewMetadata()
.withName(name)
.withNamespace(namespace)
.withLabels(Map.of("app", name, "managed-by", "java-pdb-service"))
.endMetadata()
.withSpec(spec)
.build();
return kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.resource(pdb)
.create();
}
/**
* Create PDB for stateless application
*/
public PodDisruptionBudget createStatelessPDB(String namespace, String appName,
int totalReplicas, double maxUnavailablePercentage) {
int maxUnavailable = (int) Math.ceil(totalReplicas * maxUnavailablePercentage / 100);
log.info("Creating stateless PDB for {}: maxUnavailable={} ({}% of {})",
appName, maxUnavailable, maxUnavailablePercentage, totalReplicas);
return createBasicPDB(
namespace,
appName + "-pdb",
Map.of("app", appName),
null, // minAvailable
maxUnavailable
);
}
/**
* Create PDB for stateful application
*/
public PodDisruptionBudget createStatefulPDB(String namespace, String appName,
int totalReplicas, double minAvailablePercentage) {
int minAvailable = (int) Math.ceil(totalReplicas * minAvailablePercentage / 100);
log.info("Creating stateful PDB for {}: minAvailable={} ({}% of {})",
appName, minAvailable, minAvailablePercentage, totalReplicas);
return createBasicPDB(
namespace,
appName + "-pdb",
Map.of("app", appName),
minAvailable,
null // maxUnavailable
);
}
/**
* Create PDB for critical application (zero downtime)
*/
public PodDisruptionBudget createCriticalPDB(String namespace, String appName) {
log.info("Creating critical PDB for {}: zero disruptions allowed", appName);
return createBasicPDB(
namespace,
appName + "-critical-pdb",
Map.of("app", appName),
1, // minAvailable - at least one pod must always be available
null
);
}
/**
* Get PDB by name
*/
public Optional<PodDisruptionBudget> getPDB(String namespace, String name) {
try {
PodDisruptionBudget pdb = kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.withName(name)
.get();
return Optional.ofNullable(pdb);
} catch (KubernetesClientException e) {
log.error("Error getting PDB {}/{}", namespace, name, e);
return Optional.empty();
}
}
/**
* List all PDBs in namespace
*/
public List<PodDisruptionBudget> listPDBs(String namespace) {
return kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.list()
.getItems();
}
/**
* Update PDB
*/
public PodDisruptionBudget updatePDB(String namespace, String name,
Integer minAvailable, Integer maxUnavailable) {
return kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.withName(name)
.edit(pdb -> {
PodDisruptionBudgetSpec updatedSpec = new PodDisruptionBudgetSpecBuilder(pdb.getSpec())
.withMinAvailable(minAvailable != null ? minAvailable.toString() : null)
.withMaxUnavailable(maxUnavailable != null ? maxUnavailable.toString() : null)
.build();
return new PodDisruptionBudgetBuilder(pdb)
.withSpec(updatedSpec)
.build();
});
}
/**
* Delete PDB
*/
public boolean deletePDB(String namespace, String name) {
try {
return kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.withName(name)
.delete().size() > 0;
} catch (KubernetesClientException e) {
log.error("Error deleting PDB {}/{}", namespace, name, e);
return false;
}
}
/**
* Check if PDB is healthy (not blocking disruptions unnecessarily)
*/
public PDBHealthStatus checkPDBHealth(String namespace, String name) {
Optional<PodDisruptionBudget> pdbOpt = getPDB(namespace, name);
if (pdbOpt.isEmpty()) {
return PDBHealthStatus.NOT_FOUND;
}
PodDisruptionBudget pdb = pdbOpt.get();
// Check if PDB status is available
if (pdb.getStatus() == null) {
return PDBHealthStatus.UNKNOWN;
}
int currentHealthy = pdb.getStatus().getCurrentHealthy() != null ?
pdb.getStatus().getCurrentHealthy() : 0;
int desiredHealthy = pdb.getStatus().getDesiredHealthy() != null ?
pdb.getStatus().getDesiredHealthy() : 0;
if (currentHealthy >= desiredHealthy) {
return PDBHealthStatus.HEALTHY;
} else {
return PDBHealthStatus.UNHEALTHY;
}
}
public enum PDBHealthStatus {
HEALTHY,
UNHEALTHY,
NOT_FOUND,
UNKNOWN
}
}
2. Advanced PDB Manager
// src/main/java/com/example/pdb/service/AdvancedPDBManager.java
package com.example.pdb.service;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class AdvancedPDBManager {
private static final Logger log = LoggerFactory.getLogger(AdvancedPDBManager.class);
private final KubernetesClient kubernetesClient;
private final PDBService pdbService;
// PDB strategies for different application types
private final Map<String, PDBStrategy> strategies;
public AdvancedPDBManager(KubernetesClient kubernetesClient, PDBService pdbService) {
this.kubernetesClient = kubernetesClient;
this.pdbService = pdbService;
this.strategies = initializeStrategies();
}
/**
* Create PDB based on application type and requirements
*/
public PodDisruptionBudget createApplicationPDB(String namespace, String appName,
String appType, int replicaCount,
Map<String, String> labels) {
PDBStrategy strategy = strategies.getOrDefault(appType, strategies.get("default"));
log.info("Creating PDB for {} application '{}' with {} replicas using {} strategy",
appType, appName, replicaCount, strategy.getName());
return strategy.createPDB(pdbService, namespace, appName, replicaCount, labels);
}
/**
* Adjust PDB during scaling events
*/
public PodDisruptionBudget adjustPDBForScale(String namespace, String appName,
int newReplicaCount) {
// Get current PDB
var currentPDB = pdbService.getPDB(namespace, appName + "-pdb");
if (currentPDB.isEmpty()) {
log.warn("PDB not found for {}/{}, creating new one", namespace, appName);
return createApplicationPDB(namespace, appName, "web", newReplicaCount,
Map.of("app", appName));
}
PodDisruptionBudget pdb = currentPDB.get();
// Determine scaling strategy based on replica count
PDBStrategy strategy = determineScalingStrategy(newReplicaCount);
log.info("Adjusting PDB for {}/{} from {} to {} replicas",
namespace, appName, getCurrentReplicaCount(pdb), newReplicaCount);
return strategy.adjustPDB(pdbService, namespace, appName, newReplicaCount, pdb);
}
/**
* Create PDB for canary deployment
*/
public PodDisruptionBudget createCanaryPDB(String namespace, String appName,
String canaryVersion, int canaryReplicas,
int stableReplicas) {
// More restrictive PDB for canary to ensure availability
Map<String, String> canaryLabels = Map.of(
"app", appName,
"version", canaryVersion,
"deployment", "canary"
);
log.info("Creating canary PDB for {}/{} with {} replicas",
namespace, appName, canaryReplicas);
// Canary should have zero disruptions allowed
return pdbService.createBasicPDB(
namespace,
appName + "-canary-pdb",
canaryLabels,
canaryReplicas, // minAvailable = all replicas
null
);
}
/**
* Emergency override - temporarily relax PDB for maintenance
*/
public PodDisruptionBudget emergencyOverride(String namespace, String appName,
boolean allowAllDisruptions) {
var currentPDB = pdbService.getPDB(namespace, appName + "-pdb");
if (currentPDB.isEmpty()) {
log.error("Cannot perform emergency override - PDB not found for {}/{}",
namespace, appName);
return null;
}
PodDisruptionBudget pdb = currentPDB.get();
if (allowAllDisruptions) {
log.warn("EMERGENCY OVERRIDE: Allowing all disruptions for {}/{}",
namespace, appName);
// Set maxUnavailable to 100% - allow all pods to be disrupted
return pdbService.updatePDB(namespace, appName + "-pdb", null, 100);
} else {
// Restore original PDB settings
return restorePDB(namespace, appName, pdb);
}
}
private Map<String, PDBStrategy> initializeStrategies() {
Map<String, PDBStrategy> strategies = new HashMap<>();
strategies.put("web", new WebApplicationStrategy());
strategies.put("api", new APIApplicationStrategy());
strategies.put("database", new DatabaseStrategy());
strategies.put("cache", new CacheStrategy());
strategies.put("critical", new CriticalApplicationStrategy());
strategies.put("default", new WebApplicationStrategy());
return strategies;
}
private PDBStrategy determineScalingStrategy(int replicaCount) {
if (replicaCount <= 2) {
return new SmallClusterStrategy();
} else if (replicaCount <= 10) {
return new MediumClusterStrategy();
} else {
return new LargeClusterStrategy();
}
}
private int getCurrentReplicaCount(PodDisruptionBudget pdb) {
// Extract replica count from PDB spec (this is simplified)
// In real implementation, you'd query the deployment
return 3; // Default
}
private PodDisruptionBudget restorePDB(String namespace, String appName,
PodDisruptionBudget originalPDB) {
log.info("Restoring original PDB settings for {}/{}", namespace, appName);
// Implementation to restore original PDB settings
return originalPDB;
}
// Strategy interfaces and implementations
public interface PDBStrategy {
String getName();
PodDisruptionBudget createPDB(PDBService pdbService, String namespace,
String appName, int replicaCount,
Map<String, String> labels);
PodDisruptionBudget adjustPDB(PDBService pdbService, String namespace,
String appName, int newReplicaCount,
PodDisruptionBudget currentPDB);
}
public static class WebApplicationStrategy implements PDBStrategy {
@Override
public String getName() { return "Web Application"; }
@Override
public PodDisruptionBudget createPDB(PDBService pdbService, String namespace,
String appName, int replicaCount,
Map<String, String> labels) {
// Web apps can tolerate some disruption
int maxUnavailable = Math.max(1, replicaCount / 3); // Max 1/3 unavailable
return pdbService.createBasicPDB(namespace, appName + "-pdb", labels, null, maxUnavailable);
}
@Override
public PodDisruptionBudget adjustPDB(PDBService pdbService, String namespace,
String appName, int newReplicaCount,
PodDisruptionBudget currentPDB) {
int maxUnavailable = Math.max(1, newReplicaCount / 3);
return pdbService.updatePDB(namespace, appName + "-pdb", null, maxUnavailable);
}
}
public static class DatabaseStrategy implements PDBStrategy {
@Override
public String getName() { return "Database"; }
@Override
public PodDisruptionBudget createPDB(PDBService pdbService, String namespace,
String appName, int replicaCount,
Map<String, String> labels) {
// Databases need high availability
int minAvailable = replicaCount > 1 ? replicaCount - 1 : 1;
return pdbService.createBasicPDB(namespace, appName + "-pdb", labels, minAvailable, null);
}
@Override
public PodDisruptionBudget adjustPDB(PDBService pdbService, String namespace,
String appName, int newReplicaCount,
PodDisruptionBudget currentPDB) {
int minAvailable = newReplicaCount > 1 ? newReplicaCount - 1 : 1;
return pdbService.updatePDB(namespace, appName + "-pdb", minAvailable, null);
}
}
// Additional strategy implementations...
public static class APIApplicationStrategy implements PDBStrategy {
@Override public String getName() { return "API Application"; }
@Override
public PodDisruptionBudget createPDB(PDBService pdbService, String namespace,
String appName, int replicaCount,
Map<String, String> labels) {
int maxUnavailable = Math.max(1, replicaCount / 4); // More restrictive than web
return pdbService.createBasicPDB(namespace, appName + "-pdb", labels, null, maxUnavailable);
}
@Override
public PodDisruptionBudget adjustPDB(PDBService pdbService, String namespace,
String appName, int newReplicaCount,
PodDisruptionBudget currentPDB) {
int maxUnavailable = Math.max(1, newReplicaCount / 4);
return pdbService.updatePDB(namespace, appName + "-pdb", null, maxUnavailable);
}
}
public static class SmallClusterStrategy implements PDBStrategy {
@Override public String getName() { return "Small Cluster"; }
@Override
public PodDisruptionBudget createPDB(PDBService pdbService, String namespace,
String appName, int replicaCount,
Map<String, String> labels) {
return pdbService.createBasicPDB(namespace, appName + "-pdb", labels, 1, null);
}
@Override
public PodDisruptionBudget adjustPDB(PDBService pdbService, String namespace,
String appName, int newReplicaCount,
PodDisruptionBudget currentPDB) {
return pdbService.updatePDB(namespace, appName + "-pdb", 1, null);
}
}
}
3. PDB Controller for Automated Management
// src/main/java/com/example/pdb/controller/PDBController.java
package com.example.pdb.controller;
import com.example.pdb.service.AdvancedPDBManager;
import com.example.pdb.service.PDBService;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/pdb")
public class PDBController {
private final PDBService pdbService;
private final AdvancedPDBManager advancedPDBManager;
public PDBController(PDBService pdbService, AdvancedPDBManager advancedPDBManager) {
this.pdbService = pdbService;
this.advancedPDBManager = advancedPDBManager;
}
@PostMapping("/{namespace}")
public ResponseEntity<PodDisruptionBudget> createPDB(
@PathVariable String namespace,
@RequestBody CreatePDBRequest request) {
try {
PodDisruptionBudget pdb = pdbService.createBasicPDB(
namespace,
request.getName(),
request.getSelectorLabels(),
request.getMinAvailable(),
request.getMaxUnavailable()
);
return ResponseEntity.ok(pdb);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/{namespace}/application")
public ResponseEntity<PodDisruptionBudget> createApplicationPDB(
@PathVariable String namespace,
@RequestBody CreateApplicationPDBRequest request) {
try {
PodDisruptionBudget pdb = advancedPDBManager.createApplicationPDB(
namespace,
request.getAppName(),
request.getAppType(),
request.getReplicaCount(),
request.getLabels()
);
return ResponseEntity.ok(pdb);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/{namespace}")
public ResponseEntity<List<PodDisruptionBudget>> listPDBs(@PathVariable String namespace) {
try {
List<PodDisruptionBudget> pdbs = pdbService.listPDBs(namespace);
return ResponseEntity.ok(pdbs);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/{namespace}/{name}")
public ResponseEntity<PodDisruptionBudget> getPDB(
@PathVariable String namespace,
@PathVariable String name) {
return pdbService.getPDB(namespace, name)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{namespace}/{name}/health")
public ResponseEntity<Map<String, String>> checkPDBHealth(
@PathVariable String namespace,
@PathVariable String name) {
PDBService.PDBHealthStatus health = pdbService.checkPDBHealth(namespace, name);
return ResponseEntity.ok(Map.of(
"status", health.name(),
"message", getHealthMessage(health)
));
}
@PutMapping("/{namespace}/{name}/scale")
public ResponseEntity<PodDisruptionBudget> adjustForScale(
@PathVariable String namespace,
@PathVariable String name,
@RequestParam int newReplicaCount) {
try {
PodDisruptionBudget pdb = advancedPDBManager.adjustPDBForScale(
namespace, name, newReplicaCount);
return ResponseEntity.ok(pdb);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/{namespace}/{name}/emergency")
public ResponseEntity<PodDisruptionBudget> emergencyOverride(
@PathVariable String namespace,
@PathVariable String name,
@RequestParam boolean allowDisruptions) {
try {
PodDisruptionBudget pdb = advancedPDBManager.emergencyOverride(
namespace, name, allowDisruptions);
return pdb != null ? ResponseEntity.ok(pdb) : ResponseEntity.notFound().build();
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@DeleteMapping("/{namespace}/{name}")
public ResponseEntity<Void> deletePDB(
@PathVariable String namespace,
@PathVariable String name) {
boolean deleted = pdbService.deletePDB(namespace, name);
return deleted ? ResponseEntity.ok().build() : ResponseEntity.notFound().build();
}
private String getHealthMessage(PDBService.PDBHealthStatus health) {
return switch (health) {
case HEALTHY -> "PDB is healthy and allowing appropriate disruptions";
case UNHEALTHY -> "PDB is unhealthy - too few pods available";
case NOT_FOUND -> "PDB not found";
case UNKNOWN -> "PDB status unknown";
};
}
// Request DTOs
public static class CreatePDBRequest {
private String name;
private Map<String, String> selectorLabels;
private Integer minAvailable;
private Integer maxUnavailable;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Map<String, String> getSelectorLabels() { return selectorLabels; }
public void setSelectorLabels(Map<String, String> selectorLabels) { this.selectorLabels = selectorLabels; }
public Integer getMinAvailable() { return minAvailable; }
public void setMinAvailable(Integer minAvailable) { this.minAvailable = minAvailable; }
public Integer getMaxUnavailable() { return maxUnavailable; }
public void setMaxUnavailable(Integer maxUnavailable) { this.maxUnavailable = maxUnavailable; }
}
public static class CreateApplicationPDBRequest {
private String appName;
private String appType;
private int replicaCount;
private Map<String, String> labels;
// Getters and setters
public String getAppName() { return appName; }
public void setAppName(String appName) { this.appName = appName; }
public String getAppType() { return appType; }
public void setAppType(String appType) { this.appType = appType; }
public int getReplicaCount() { return replicaCount; }
public void setReplicaCount(int replicaCount) { this.replicaCount = replicaCount; }
public Map<String, String> getLabels() { return labels; }
public void setLabels(Map<String, String> labels) { this.labels = labels; }
}
}
4. Kubernetes Client Configuration
// src/main/java/com/example/pdb/config/KubernetesConfig.java
package com.example.pdb.config;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KubernetesConfig {
@Bean
public KubernetesClient kubernetesClient() {
Config config = new ConfigBuilder()
.withRequestRetryBackoffLimit(3)
.withRequestTimeout(10000)
.build();
return new DefaultKubernetesClient(config);
}
}
Integration with Deployment Strategies
1. Deployment-Aware PDB Service
// src/main/java/com/example/pdb/service/DeploymentAwarePDBService.java
package com.example.pdb.service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeploymentAwarePDBService {
private static final Logger log = LoggerFactory.getLogger(DeploymentAwarePDBService.class);
private final KubernetesClient kubernetesClient;
private final AdvancedPDBManager pdbManager;
public DeploymentAwarePDBService(KubernetesClient kubernetesClient,
AdvancedPDBManager pdbManager) {
this.kubernetesClient = kubernetesClient;
this.pdbManager = pdbManager;
}
/**
* Automatically create PDB when deployment is created
*/
public void syncPDBWithDeployment(Deployment deployment) {
String namespace = deployment.getMetadata().getNamespace();
String name = deployment.getMetadata().getName();
int replicas = deployment.getSpec().getReplicas();
log.info("Syncing PDB for deployment {}/{} with {} replicas",
namespace, name, replicas);
// Determine application type from labels/annotations
String appType = determineApplicationType(deployment);
pdbManager.createApplicationPDB(
namespace,
name,
appType,
replicas,
deployment.getSpec().getSelector().getMatchLabels()
);
}
/**
* Scheduled task to ensure PDBs are in sync with deployments
*/
@Scheduled(fixedDelay = 300000) // Every 5 minutes
public void reconcilePDBs() {
log.info("Starting PDB reconciliation");
// Get all deployments across all namespaces
List<Deployment> deployments = kubernetesClient.apps().deployments()
.inAnyNamespace()
.list()
.getItems();
for (Deployment deployment : deployments) {
String namespace = deployment.getMetadata().getNamespace();
String name = deployment.getMetadata().getName();
// Check if PDB exists
boolean pdbExists = kubernetesClient.policy().v1().podDisruptionBudgets()
.inNamespace(namespace)
.withLabel("app", name)
.list()
.getItems()
.size() > 0;
if (!pdbExists) {
log.info("Creating missing PDB for deployment {}/{}", namespace, name);
syncPDBWithDeployment(deployment);
}
}
log.info("PDB reconciliation completed");
}
/**
* Handle deployment scaling events
*/
public void handleDeploymentScale(String namespace, String deploymentName, int newReplicas) {
log.info("Handling scale event for {}/{} to {} replicas",
namespace, deploymentName, newReplicas);
pdbManager.adjustPDBForScale(namespace, deploymentName, newReplicas);
}
private String determineApplicationType(Deployment deployment) {
// Determine application type from annotations or labels
Map<String, String> annotations = deployment.getMetadata().getAnnotations();
Map<String, String> labels = deployment.getMetadata().getLabels();
if (annotations != null && annotations.containsKey("app-type")) {
return annotations.get("app-type");
}
if (labels != null) {
if (labels.containsKey("tier")) {
return labels.get("tier");
}
if (labels.containsKey("app.kubernetes.io/component")) {
return labels.get("app.kubernetes.io/component");
}
}
// Default to web application
return "web";
}
}
2. Spring Boot Application
// src/main/java/com/example/pdb/PDBApplication.java
package com.example.pdb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class PDBApplication {
public static void main(String[] args) {
SpringApplication.run(PDBApplication.class, args);
}
}
Testing
1. Unit Tests
// src/test/java/com/example/pdb/service/PDBServiceTest.java
package com.example.pdb.service;
import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@EnableKubernetesMockClient
class PDBServiceTest {
private KubernetesClient client;
private PDBService pdbService;
@Test
void shouldCreateBasicPDB() {
// Given
pdbService = new PDBService(client);
// When
PodDisruptionBudget pdb = pdbService.createBasicPDB(
"default",
"test-pdb",
Map.of("app", "test-app"),
2,
null
);
// Then
assertNotNull(pdb);
assertEquals("test-pdb", pdb.getMetadata().getName());
assertEquals("2", pdb.getSpec().getMinAvailable().getIntVal().toString());
}
@Test
void shouldCreateStatelessPDB() {
// Given
pdbService = new PDBService(client);
// When
PodDisruptionBudget pdb = pdbService.createStatelessPDB(
"default",
"web-app",
6,
33.0
);
// Then
assertNotNull(pdb);
assertEquals("2", pdb.getSpec().getMaxUnavailable().getIntVal().toString());
}
}
Best Practices
1. PDB Configuration Guidelines
// src/main/java/com/example/pdb/config/PDBBestPractices.java
package com.example.pdb.config;
public class PDBBestPractices {
/**
* Recommended PDB configurations for different scenarios
*/
public static class Recommendations {
// Stateless web applications
public static PDBConfig WEB_APPLICATION = new PDBConfig(
"maxUnavailable", "25%", "Allow 25% disruption for web apps"
);
// API services
public static PDBConfig API_SERVICE = new PDBConfig(
"maxUnavailable", "20%", "Allow 20% disruption for API services"
);
// Stateful applications
public static PDBConfig STATEFUL_APPLICATION = new PDBConfig(
"minAvailable", "1", "Ensure at least one instance for stateful apps"
);
// Critical services
public static PDBConfig CRITICAL_SERVICE = new PDBConfig(
"minAvailable", "100%", "No disruptions allowed for critical services"
);
// Database clusters
public static PDBConfig DATABASE_CLUSTER = new PDBConfig(
"minAvailable", "quorum", "Ensure quorum for database clusters"
);
}
public static class PDBConfig {
private final String type;
private final String value;
private final String description;
public PDBConfig(String type, String value, String description) {
this.type = type;
this.value = value;
this.description = description;
}
// Getters
public String getType() { return type; }
public String getValue() { return value; }
public String getDescription() { return description; }
}
/**
* Calculate appropriate PDB values based on replica count
*/
public static String calculateMaxUnavailable(int replicas) {
if (replicas <= 1) {
return "0";
} else if (replicas <= 3) {
return "1";
} else if (replicas <= 10) {
return "25%";
} else {
return "10%";
}
}
public static String calculateMinAvailable(int replicas) {
if (replicas <= 1) {
return "1";
} else if (replicas <= 3) {
return String.valueOf(replicas - 1);
} else {
return "75%";
}
}
}
2. Monitoring and Alerting
# src/main/resources/k8s/pdb-monitoring.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: pdb-alerts
namespace: monitoring
data:
pdb-alerts.yaml: |
groups:
- name: pdb
rules:
- alert: PDBBlockingDisruptions
expr: kube_poddisruptionbudget_status_current_healthy < kube_poddisruptionbudget_status_desired_healthy
for: 5m
labels:
severity: warning
annotations:
summary: "Pod Disruption Budget is blocking disruptions"
description: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} has only {{ $value }} healthy pods, less than desired {{ $labels.desired_healthy }}"
- alert: PDBTooRestrictive
expr: kube_poddisruptionbudget_status_desired_healthy / kube_poddisruptionbudget_status_expected_pods > 0.8
for: 15m
labels:
severity: info
annotations:
summary: "PDB might be too restrictive"
description: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} requires {{ $value | humanizePercentage }} of pods to be available"
Conclusion
Pod Disruption Budgets in Java provide:
- Application Availability: Ensure minimum pod availability during disruptions
- Graceful Maintenance: Allow safe node drains and cluster upgrades
- Automated Management: Programmatic PDB creation and adjustment
- Best Practices: Pre-configured strategies for different application types
- Monitoring: Health checks and alerting for PDB status
By implementing the patterns shown above, you can ensure your Java applications maintain availability during Kubernetes cluster maintenance and upgrades, while providing automated management and monitoring of disruption budgets.
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.