Fabric8 Kubernetes Client provides a fluent DSL for working with Kubernetes resources in Java, offering a more intuitive alternative to the official Kubernetes client.
Core Concepts
What is Fabric8 Kubernetes Client?
- Fluent DSL for Kubernetes operations
- Type-safe Kubernetes resource management
- Support for Kubernetes and OpenShift
- Comprehensive CRD (Custom Resource Definition) support
- Watch and informer capabilities for real-time updates
Key Features:
- Fluent API: Builder pattern for resource creation
- Watch Support: Real-time resource monitoring
- Resource Scaling: Easy pod and deployment scaling
- Log Streaming: Real-time pod log access
- Exec Commands: Execute commands in pods
- YAML/JSON Parsing: Seamless resource serialization
Dependencies and Setup
Maven Dependencies
<properties> <kubernetes-client.version>6.7.2</kubernetes-client.version> <fabric8-maven-plugin.version>4.4.1</fabric8-maven-plugin.version> <spring-boot.version>3.1.0</spring-boot.version> </properties> <dependencies> <!-- Fabric8 Kubernetes Client --> <dependency> <groupId>io.fabric8</groupId> <artifactId>kubernetes-client</artifactId> <version>${kubernetes-client.version}</version> </dependency> <!-- OpenShift Client (if needed) --> <dependency> <groupId>io.fabric8</groupId> <artifactId>openshift-client</artifactId> <version>${kubernetes-client.version}</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- YAML Processing --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>2.15.2</version> </dependency> <!-- Testing --> <dependency> <groupId>io.fabric8</groupId> <artifactId>kubernetes-server-mock</artifactId> <version>${kubernetes-client.version}</version> <scope>test</scope> </dependency> </dependencies> Configuration Classes
@Configuration @ConfigurationProperties(prefix = "kubernetes") @Data public class KubernetesConfig { private String masterUrl; private String namespace; private String oauthToken; private String username; private String password; private boolean trustCerts = false; private int connectionTimeout = 10000; private int requestTimeout = 10000; // For in-cluster configuration private boolean inCluster = false; } @Configuration @EnableConfigurationProperties(KubernetesConfig.class) public class KubernetesClientConfig { @Bean @ConditionalOnMissingBean public KubernetesClient kubernetesClient(KubernetesConfig config) { ConfigBuilder configBuilder = new ConfigBuilder(); if (config.isInCluster()) { // In-cluster configuration try { return new DefaultKubernetesClient(Config.autoConfigure(null)); } catch (Exception e) { throw new KubernetesClientException("Failed to configure in-cluster client", e); } } // External cluster configuration if (config.getMasterUrl() != null) { configBuilder.withMasterUrl(config.getMasterUrl()); } if (config.getNamespace() != null) { configBuilder.withNamespace(config.getNamespace()); } if (config.getOauthToken() != null) { configBuilder.withOauthToken(config.getOauthToken()); } if (config.getUsername() != null && config.getPassword() != null) { configBuilder.withUsername(config.getUsername()) .withPassword(config.getPassword()); } configBuilder.withTrustCerts(config.isTrustCerts()) .withConnectionTimeout(config.getConnectionTimeout()) .withRequestTimeout(config.getRequestTimeout()); return new DefaultKubernetesClient(configBuilder.build()); } @Bean @ConditionalOnProperty(name = "kubernetes.openshift.enabled", havingValue = "true") public OpenShiftClient openShiftClient(KubernetesClient kubernetesClient) { return kubernetesClient.adapt(OpenShiftClient.class); } } Core Kubernetes Operations
1. Pod Operations Service
@Service @Slf4j public class PodOperationsService { private final KubernetesClient kubernetesClient; private final String namespace; public PodOperationsService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // List pods public List<Pod> listPods() { log.debug("Listing pods in namespace: {}", namespace); return kubernetesClient.pods() .inNamespace(namespace) .list() .getItems(); } public List<Pod> listPodsByLabel(String labelKey, String labelValue) { log.debug("Listing pods with label {}={} in namespace: {}", labelKey, labelValue, namespace); return kubernetesClient.pods() .inNamespace(namespace) .withLabel(labelKey, labelValue) .list() .getItems(); } public List<Pod> listPodsByLabels(Map<String, String> labels) { return kubernetesClient.pods() .inNamespace(namespace) .withLabels(labels) .list() .getItems(); } // Get pod public Pod getPod(String podName) { log.debug("Getting pod: {} in namespace: {}", podName, namespace); return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .get(); } // Create pod public Pod createPod(Pod pod) { log.info("Creating pod: {} in namespace: {}", pod.getMetadata().getName(), namespace); return kubernetesClient.pods() .inNamespace(namespace) .create(pod); } public Pod createPodFromYaml(String yamlContent) { log.debug("Creating pod from YAML in namespace: {}", namespace); return kubernetesClient.pods() .inNamespace(namespace) .load(new ByteArrayInputStream(yamlContent.getBytes())) .create(); } // Delete pod public boolean deletePod(String podName) { log.info("Deleting pod: {} in namespace: {}", podName, namespace); return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .delete(); } // Execute command in pod public String executeCommand(String podName, String containerName, String... command) { log.debug("Executing command in pod: {}, container: {}", podName, containerName); try { return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) .exec(command) .toString(); } catch (Exception e) { log.error("Failed to execute command in pod: {}", podName, e); throw new KubernetesOperationException("Command execution failed", e); } } // Get pod logs public String getPodLogs(String podName, String containerName) { log.debug("Getting logs for pod: {}, container: {}", podName, containerName); return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) .getLog(); } public InputStream getPodLogsStream(String podName, String containerName) { log.debug("Getting logs stream for pod: {}, container: {}", podName, containerName); return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) .watchLog() .getOutput(); } // Watch pods public void watchPods(Watcher<Pod> watcher) { log.debug("Starting pod watch in namespace: {}", namespace); kubernetesClient.pods() .inNamespace(namespace) .watch(watcher); } public void watchPodsByLabel(String labelKey, String labelValue, Watcher<Pod> watcher) { log.debug("Starting pod watch with label {}={} in namespace: {}", labelKey, labelValue, namespace); kubernetesClient.pods() .inNamespace(namespace) .withLabel(labelKey, labelValue) .watch(watcher); } // Pod status public boolean isPodReady(String podName) { Pod pod = getPod(podName); return pod != null && isPodReady(pod); } public boolean isPodReady(Pod pod) { return pod.getStatus().getConditions().stream() .filter(condition -> "Ready".equals(condition.getType())) .findFirst() .map(condition -> "True".equals(condition.getStatus())) .orElse(false); } public PodPhase getPodPhase(String podName) { Pod pod = getPod(podName); return pod != null ? PodPhase.fromString(pod.getStatus().getPhase()) : PodPhase.UNKNOWN; } // Wait for pod condition public boolean waitForPodReady(String podName, Duration timeout) { log.info("Waiting for pod {} to be ready, timeout: {}", podName, timeout); try { return kubernetesClient.pods() .inNamespace(namespace) .withName(podName) .waitUntilCondition(pod -> isPodReady(pod), timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("Error waiting for pod readiness: {}", podName, e); return false; } } } 2. Deployment Operations Service
@Service @Slf4j public class DeploymentOperationsService { private final KubernetesClient kubernetesClient; private final String namespace; public DeploymentOperationsService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // List deployments public List<Deployment> listDeployments() { log.debug("Listing deployments in namespace: {}", namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .list() .getItems(); } // Get deployment public Deployment getDeployment(String deploymentName) { log.debug("Getting deployment: {} in namespace: {}", deploymentName, namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deploymentName) .get(); } // Create deployment public Deployment createDeployment(Deployment deployment) { log.info("Creating deployment: {} in namespace: {}", deployment.getMetadata().getName(), namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .create(deployment); } public Deployment createDeploymentFromYaml(String yamlContent) { log.debug("Creating deployment from YAML in namespace: {}", namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .load(new ByteArrayInputStream(yamlContent.getBytes())) .create(); } // Update deployment public Deployment updateDeployment(Deployment deployment) { log.info("Updating deployment: {} in namespace: {}", deployment.getMetadata().getName(), namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deployment.getMetadata().getName()) .patch(deployment); } // Scale deployment public Deployment scaleDeployment(String deploymentName, int replicas) { log.info("Scaling deployment {} to {} replicas", deploymentName, replicas); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deploymentName) .scale(replicas); } // Rolling update (update image) public Deployment rollingUpdate(String deploymentName, String containerName, String newImage) { log.info("Performing rolling update for deployment: {}, container: {}, image: {}", deploymentName, containerName, newImage); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deploymentName) .updateImage(newImage); } // Delete deployment public boolean deleteDeployment(String deploymentName) { log.info("Deleting deployment: {} in namespace: {}", deploymentName, namespace); return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deploymentName) .delete(); } // Deployment status public DeploymentStatus getDeploymentStatus(String deploymentName) { Deployment deployment = getDeployment(deploymentName); return deployment != null ? DeploymentStatus.fromDeployment(deployment) : DeploymentStatus.unknown(deploymentName); } // Wait for deployment readiness public boolean waitForDeploymentReady(String deploymentName, Duration timeout) { log.info("Waiting for deployment {} to be ready, timeout: {}", deploymentName, timeout); try { return kubernetesClient.apps() .deployments() .inNamespace(namespace) .withName(deploymentName) .waitUntilCondition(deployment -> { Integer replicas = deployment.getStatus().getReplicas(); Integer readyReplicas = deployment.getStatus().getReadyReplicas(); return replicas != null && readyReplicas != null && replicas.equals(readyReplicas); }, timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("Error waiting for deployment readiness: {}", deploymentName, e); return false; } } // Get pods for deployment public List<Pod> getDeploymentPods(String deploymentName) { Deployment deployment = getDeployment(deploymentName); if (deployment == null) { return Collections.emptyList(); } Map<String, String> selector = deployment.getSpec().getSelector().getMatchLabels(); return kubernetesClient.pods() .inNamespace(namespace) .withLabels(selector) .list() .getItems(); } // Watch deployments public void watchDeployments(Watcher<Deployment> watcher) { log.debug("Starting deployment watch in namespace: {}", namespace); kubernetesClient.apps() .deployments() .inNamespace(namespace) .watch(watcher); } } 3. Service Operations Service
@Service @Slf4j public class ServiceOperationsService { private final KubernetesClient kubernetesClient; private final String namespace; public ServiceOperationsService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // List services public List<Service> listServices() { log.debug("Listing services in namespace: {}", namespace); return kubernetesClient.services() .inNamespace(namespace) .list() .getItems(); } // Get service public Service getService(String serviceName) { log.debug("Getting service: {} in namespace: {}", serviceName, namespace); return kubernetesClient.services() .inNamespace(namespace) .withName(serviceName) .get(); } // Create service public Service createService(Service service) { log.info("Creating service: {} in namespace: {}", service.getMetadata().getName(), namespace); return kubernetesClient.services() .inNamespace(namespace) .create(service); } // Delete service public boolean deleteService(String serviceName) { log.info("Deleting service: {} in namespace: {}", serviceName, namespace); return kubernetesClient.services() .inNamespace(namespace) .withName(serviceName) .delete(); } // Get service URL public String getServiceUrl(String serviceName, String portName) { Service service = getService(serviceName); if (service == null) { throw new KubernetesResourceNotFoundException("Service not found: " + serviceName); } Integer port = service.getSpec().getPorts().stream() .filter(portSpec -> portName.equals(portSpec.getName())) .map(ServicePort::getPort) .findFirst() .orElseThrow(() -> new KubernetesOperationException( "Port not found: " + portName + " in service: " + serviceName)); return String.format("http://%s.%s.svc.cluster.local:%d", serviceName, namespace, port); } // Expose deployment as service public Service exposeDeployment(String deploymentName, String serviceName, int port, int targetPort) { log.info("Exposing deployment {} as service {} on port {}", deploymentName, serviceName, port); Service service = new ServiceBuilder() .withNewMetadata() .withName(serviceName) .withNamespace(namespace) .endMetadata() .withNewSpec() .withSelector(Collections.singletonMap("app", deploymentName)) .addNewPort() .withPort(port) .withTargetPort(new IntOrString(targetPort)) .endPort() .endSpec() .build(); return createService(service); } } 4. ConfigMap and Secret Operations
@Service @Slf4j public class ConfigOperationsService { private final KubernetesClient kubernetesClient; private final String namespace; public ConfigOperationsService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // ConfigMap operations public ConfigMap createConfigMap(String name, Map<String, String> data) { log.info("Creating ConfigMap: {} in namespace: {}", name, namespace); return kubernetesClient.configMaps() .inNamespace(namespace) .create(new ConfigMapBuilder() .withNewMetadata() .withName(name) .withNamespace(namespace) .endMetadata() .withData(data) .build()); } public ConfigMap getConfigMap(String name) { return kubernetesClient.configMaps() .inNamespace(namespace) .withName(name) .get(); } public ConfigMap updateConfigMap(String name, Map<String, String> data) { log.info("Updating ConfigMap: {} in namespace: {}", name, namespace); return kubernetesClient.configMaps() .inNamespace(namespace) .withName(name) .edit() .withData(data) .done(); } // Secret operations public Secret createSecret(String name, Map<String, String> stringData) { log.info("Creating Secret: {} in namespace: {}", name, namespace); return kubernetesClient.secrets() .inNamespace(namespace) .create(new SecretBuilder() .withNewMetadata() .withName(name) .withNamespace(namespace) .endMetadata() .withStringData(stringData) .build()); } public Secret getSecret(String name) { return kubernetesClient.secrets() .inNamespace(namespace) .withName(name) .get(); } public String getSecretData(String secretName, String key) { Secret secret = getSecret(secretName); if (secret == null || secret.getData() == null) { return null; } String base64Data = secret.getData().get(key); return base64Data != null ? new String(Base64.getDecoder().decode(base64Data)) : null; } // Create Secret from literal values public Secret createSecretFromLiterals(String name, Map<String, String> literals) { Map<String, String> encodedData = literals.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> Base64.getEncoder().encodeToString(entry.getValue().getBytes()) )); return kubernetesClient.secrets() .inNamespace(namespace) .create(new SecretBuilder() .withNewMetadata() .withName(name) .withNamespace(namespace) .endMetadata() .withData(encodedData) .build()); } } 5. Namespace Operations Service
@Service @Slf4j public class NamespaceOperationsService { private final KubernetesClient kubernetesClient; public NamespaceOperationsService(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; } // List namespaces public List<Namespace> listNamespaces() { log.debug("Listing all namespaces"); return kubernetesClient.namespaces() .list() .getItems(); } // Get namespace public Namespace getNamespace(String namespaceName) { log.debug("Getting namespace: {}", namespaceName); return kubernetesClient.namespaces() .withName(namespaceName) .get(); } // Create namespace public Namespace createNamespace(String namespaceName) { log.info("Creating namespace: {}", namespaceName); return kubernetesClient.namespaces() .create(new NamespaceBuilder() .withNewMetadata() .withName(namespaceName) .endMetadata() .build()); } public Namespace createNamespaceWithLabels(String namespaceName, Map<String, String> labels) { log.info("Creating namespace: {} with labels: {}", namespaceName, labels); return kubernetesClient.namespaces() .create(new NamespaceBuilder() .withNewMetadata() .withName(namespaceName) .withLabels(labels) .endMetadata() .build()); } // Delete namespace public boolean deleteNamespace(String namespaceName) { log.info("Deleting namespace: {}", namespaceName); return kubernetesClient.namespaces() .withName(namespaceName) .delete(); } // Check if namespace exists public boolean namespaceExists(String namespaceName) { return getNamespace(namespaceName) != null; } // Switch context to different namespace public void switchNamespace(String namespaceName) { log.info("Switching to namespace: {}", namespaceName); // This would typically involve creating a new client instance // or using the existing client with the new namespace context } } Advanced Features
1. Resource Builder Factory
@Component public class KubernetesResourceBuilder { public DeploymentBuilder deployment() { return new DeploymentBuilder(); } public ServiceBuilder service() { return new ServiceBuilder(); } public ConfigMapBuilder configMap() { return new ConfigMapBuilder(); } public static class DeploymentBuilder { private String name; private String namespace = "default"; private Map<String, String> labels = new HashMap<>(); private int replicas = 1; private String image; private int containerPort = 8080; private Map<String, String> envVars = new HashMap<>(); public DeploymentBuilder withName(String name) { this.name = name; return this; } public DeploymentBuilder withNamespace(String namespace) { this.namespace = namespace; return this; } public DeploymentBuilder withLabel(String key, String value) { this.labels.put(key, value); return this; } public DeploymentBuilder withReplicas(int replicas) { this.replicas = replicas; return this; } public DeploymentBuilder withImage(String image) { this.image = image; return this; } public DeploymentBuilder withContainerPort(int port) { this.containerPort = port; return this; } public DeploymentBuilder withEnvVar(String key, String value) { this.envVars.put(key, value); return this; } public Deployment build() { if (name == null || image == null) { throw new IllegalStateException("Name and image are required"); } return new DeploymentBuilder() .withNewMetadata() .withName(name) .withNamespace(namespace) .withLabels(labels) .endMetadata() .withNewSpec() .withReplicas(replicas) .withNewSelector() .addToMatchLabels("app", name) .endSelector() .withNewTemplate() .withNewMetadata() .withLabels(labels) .endMetadata() .withNewSpec() .addNewContainer() .withName(name) .withImage(image) .addNewPort() .withContainerPort(containerPort) .endPort() .withEnv(envVars.entrySet().stream() .map(entry -> new EnvVarBuilder() .withName(entry.getKey()) .withValue(entry.getValue()) .build()) .collect(Collectors.toList())) .endContainer() .endSpec() .endTemplate() .endSpec() .build(); } } // Similar builder patterns for Service, ConfigMap, etc. } 2. Watch and Event Handling
@Service @Slf4j public class KubernetesWatcherService { private final KubernetesClient kubernetesClient; private final String namespace; private final Map<String, Watch> activeWatches = new ConcurrentHashMap<>(); public KubernetesWatcherService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // Pod watcher public String watchPods(PodWatcherListener listener) { String watchId = UUID.randomUUID().toString(); Watch watch = kubernetesClient.pods() .inNamespace(namespace) .watch(new Watcher<Pod>() { @Override public void eventReceived(Action action, Pod pod) { listener.onPodEvent(action, pod); } @Override public void onClose(WatcherException cause) { listener.onWatchClose(cause); activeWatches.remove(watchId); } }); activeWatches.put(watchId, watch); return watchId; } // Deployment watcher public String watchDeployments(DeploymentWatcherListener listener) { String watchId = UUID.randomUUID().toString(); Watch watch = kubernetesClient.apps() .deployments() .inNamespace(namespace) .watch(new Watcher<Deployment>() { @Override public void eventReceived(Action action, Deployment deployment) { listener.onDeploymentEvent(action, deployment); } @Override public void onClose(WatcherException cause) { listener.onWatchClose(cause); activeWatches.remove(watchId); } }); activeWatches.put(watchId, watch); return watchId; } // Stop watch public void stopWatch(String watchId) { Watch watch = activeWatches.get(watchId); if (watch != null) { watch.close(); activeWatches.remove(watchId); } } public void stopAllWatches() { activeWatches.values().forEach(Watch::close); activeWatches.clear(); } // Informer for more efficient watching public SharedInformerFactory createInformerFactory() { return kubernetesClient.informers(); } } // Listener interfaces public interface PodWatcherListener { void onPodEvent(Watcher.Action action, Pod pod); void onWatchClose(WatcherException cause); } public interface DeploymentWatcherListener { void onDeploymentEvent(Watcher.Action action, Deployment deployment); void onWatchClose(WatcherException cause); } 3. Custom Resource Definition (CRD) Support
// Custom Resource Model @Data public class MyCustomResource extends CustomResource { private MyCustomResourceSpec spec; private MyCustomResourceStatus status; @Data public static class MyCustomResourceSpec { private String message; private int replicas; private Map<String, String> config; } @Data public static class MyCustomResourceStatus { private String phase; private String message; private List<String> conditions; } } // CRD Operations Service @Service @Slf4j public class CustomResourceOperationsService { private final KubernetesClient kubernetesClient; private final String namespace; public CustomResourceOperationsService(KubernetesClient kubernetesClient, KubernetesConfig config) { this.kubernetesClient = kubernetesClient; this.namespace = config.getNamespace(); } // Create custom resource public MyCustomResource createCustomResource(String name, MyCustomResource resource) { log.info("Creating custom resource: {} in namespace: {}", name, namespace); return kubernetesClient.resources(MyCustomResource.class) .inNamespace(namespace) .create(resource); } // Get custom resource public MyCustomResource getCustomResource(String name) { return kubernetesClient.resources(MyCustomResource.class) .inNamespace(namespace) .withName(name) .get(); } // List custom resources public List<MyCustomResource> listCustomResources() { return kubernetesClient.resources(MyCustomResource.class) .inNamespace(namespace) .list() .getItems(); } // Update custom resource public MyCustomResource updateCustomResource(MyCustomResource resource) { log.info("Updating custom resource: {}", resource.getMetadata().getName()); return kubernetesClient.resources(MyCustomResource.class) .inNamespace(namespace) .withName(resource.getMetadata().getName()) .patch(resource); } // Watch custom resources public void watchCustomResources(Watcher<MyCustomResource> watcher) { kubernetesClient.resources(MyCustomResource.class) .inNamespace(namespace) .watch(watcher); } } REST Controller
@RestController @RequestMapping("/api/kubernetes") @Slf4j public class KubernetesController { private final PodOperationsService podService; private final DeploymentOperationsService deploymentService; private final ServiceOperationsService serviceService; private final ConfigOperationsService configService; private final NamespaceOperationsService namespaceService; public KubernetesController(PodOperationsService podService, DeploymentOperationsService deploymentService, ServiceOperationsService serviceService, ConfigOperationsService configService, NamespaceOperationsService namespaceService) { this.podService = podService; this.deploymentService = deploymentService; this.serviceService = serviceService; this.configService = configService; this.namespaceService = namespaceService; } // Pod endpoints @GetMapping("/pods") public ResponseEntity<List<Pod>> listPods() { try { List<Pod> pods = podService.listPods(); return ResponseEntity.ok(pods); } catch (Exception e) { log.error("Failed to list pods", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/pods/{name}") public ResponseEntity<Pod> getPod(@PathVariable String name) { try { Pod pod = podService.getPod(name); return pod != null ? ResponseEntity.ok(pod) : ResponseEntity.notFound().build(); } catch (Exception e) { log.error("Failed to get pod: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/pods/{name}/logs") public ResponseEntity<String> getPodLogs(@PathVariable String name, @RequestParam(required = false) String container) { try { String logs = podService.getPodLogs(name, container != null ? container : name); return ResponseEntity.ok(logs); } catch (Exception e) { log.error("Failed to get logs for pod: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Deployment endpoints @GetMapping("/deployments") public ResponseEntity<List<Deployment>> listDeployments() { try { List<Deployment> deployments = deploymentService.listDeployments(); return ResponseEntity.ok(deployments); } catch (Exception e) { log.error("Failed to list deployments", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/deployments/{name}/scale") public ResponseEntity<Deployment> scaleDeployment(@PathVariable String name, @RequestParam int replicas) { try { Deployment deployment = deploymentService.scaleDeployment(name, replicas); return ResponseEntity.ok(deployment); } catch (Exception e) { log.error("Failed to scale deployment: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/deployments/{name}/rolling-update") public ResponseEntity<Deployment> rollingUpdate(@PathVariable String name, @RequestParam String container, @RequestParam String image) { try { Deployment deployment = deploymentService.rollingUpdate(name, container, image); return ResponseEntity.ok(deployment); } catch (Exception e) { log.error("Failed to perform rolling update: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Service endpoints @GetMapping("/services") public ResponseEntity<List<Service>> listServices() { try { List<Service> services = serviceService.listServices(); return ResponseEntity.ok(services); } catch (Exception e) { log.error("Failed to list services", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Namespace endpoints @GetMapping("/namespaces") public ResponseEntity<List<Namespace>> listNamespaces() { try { List<Namespace> namespaces = namespaceService.listNamespaces(); return ResponseEntity.ok(namespaces); } catch (Exception e) { log.error("Failed to list namespaces", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Health check @GetMapping("/health") public ResponseEntity<Map<String, String>> health() { try { // Test connectivity by listing namespaces namespaceService.listNamespaces(); return ResponseEntity.ok(Map.of("status", "connected")); } catch (Exception e) { log.error("Kubernetes health check failed", e); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(Map.of("status", "disconnected", "error", e.getMessage())); } } } Error Handling and Custom Exceptions
public class KubernetesOperationException extends RuntimeException { public KubernetesOperationException(String message) { super(message); } public KubernetesOperationException(String message, Throwable cause) { super(message, cause); } } public class KubernetesResourceNotFoundException extends KubernetesOperationException { public KubernetesResourceNotFoundException(String message) { super(message); } public KubernetesResourceNotFoundException(String message, Throwable cause) { super(message, cause); } } @ControllerAdvice public class KubernetesExceptionHandler { @ExceptionHandler(KubernetesResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(KubernetesResourceNotFoundException e) { ErrorResponse error = new ErrorResponse("RESOURCE_NOT_FOUND", e.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); } @ExceptionHandler(KubernetesOperationException.class) public ResponseEntity<ErrorResponse> handleOperationException(KubernetesOperationException e) { ErrorResponse error = new ErrorResponse("KUBERNETES_OPERATION_FAILED", e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); } @ExceptionHandler(KubernetesClientException.class) public ResponseEntity<ErrorResponse> handleClientException(KubernetesClientException e) { ErrorResponse error = new ErrorResponse("KUBERNETES_CLIENT_ERROR", e.getMessage()); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error); } } @Data @AllArgsConstructor @NoArgsConstructor class ErrorResponse { private String code; private String message; private Instant timestamp; public ErrorResponse(String code, String message) { this.code = code; this.message = message; this.timestamp = Instant.now(); } } Testing with Kubernetes Server Mock
@SpringBootTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class KubernetesClientTest { @MockServer private KubernetesServer kubernetesServer; private KubernetesClient kubernetesClient; private PodOperationsService podService; @BeforeEach void setUp() { kubernetesClient = kubernetesServer.getClient(); KubernetesConfig config = new KubernetesConfig(); config.setNamespace("test"); podService = new PodOperationsService(kubernetesClient, config); } @Test void testListPods() { // Given kubernetesServer.expect().get() .withPath("/api/v1/namespaces/test/pods") .andReturn(200, new PodListBuilder() .addNewItem() .withNewMetadata() .withName("test-pod") .endMetadata() .endItem() .build()) .once(); // When List<Pod> pods = podService.listPods(); // Then assertThat(pods).hasSize(1); assertThat(pods.get(0).getMetadata().getName()).isEqualTo("test-pod"); } @Test void testCreatePod() { // Given Pod pod = new PodBuilder() .withNewMetadata() .withName("test-pod") .endMetadata() .build(); kubernetesServer.expect().post() .withPath("/api/v1/namespaces/test/pods") .andReturn(201, pod) .once(); // When Pod createdPod = podService.createPod(pod); // Then assertThat(createdPod).isNotNull(); assertThat(createdPod.getMetadata().getName()).isEqualTo("test-pod"); } } Configuration
# application.yml kubernetes: master-url: ${KUBERNETES_MASTER:https://kubernetes.default.svc} namespace: ${KUBERNETES_NAMESPACE:default} oauth-token: ${KUBERNETES_TOKEN:} trust-certs: ${KUBERNETES_TRUST_CERTS:false} connection-timeout: 10000 request-timeout: 10000 in-cluster: ${KUBERNETES_IN_CLUSTER:false} logging: level: io.fabric8: INFO com.example.kubernetes: DEBUG Best Practices
- Resource Cleanup: Always close watches and informers
- Error Handling: Implement comprehensive error handling
- Connection Management: Use connection pooling and timeouts
- Security: Use service accounts and RBAC appropriately
- Monitoring: Monitor Kubernetes API usage and limits
- Testing: Use Kubernetes mock server for testing
@Component public class KubernetesResourceCleanup { @PreDestroy public void cleanup() { // Close any open watches, informers, or exec sessions } } Conclusion
Fabric8 Kubernetes Client provides:
- Fluent API: Intuitive, type-safe Kubernetes operations
- Comprehensive Coverage: Support for all Kubernetes resource types
- Real-time Monitoring: Watch and informer capabilities
- CRD Support: Full custom resource definition support
- Production Ready: Robust error handling and connection management
This implementation enables seamless integration with Kubernetes clusters, providing programmatic control over container orchestration with a clean, fluent Java API. The combination of service classes, builders, and comprehensive error handling creates an enterprise-grade Kubernetes integration solution.
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.