Fabric8 Kubernetes Client in Java: Complete Guide

Introduction

Fabric8 Kubernetes Client is a powerful Java client for Kubernetes and OpenShift that provides a fluent DSL for working with Kubernetes resources. It offers a more Java-friendly alternative to the official Kubernetes client.

Setup and Dependencies

1. Maven Dependencies

<properties>
<fabric8.version>6.9.2</fabric8.version>
</properties>
<dependencies>
<!-- Core Kubernetes Client -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${fabric8.version}</version>
</dependency>
<!-- OpenShift Client (if needed) -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-client</artifactId>
<version>${fabric8.version}</version>
</dependency>
<!-- Mock Kubernetes Server for testing -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<version>${fabric8.version}</version>
<scope>test</scope>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>

2. Gradle Dependencies

dependencies {
implementation("io.fabric8:kubernetes-client:6.9.2")
implementation("io.fabric8:openshift-client:6.9.2")
testImplementation("io.fabric8:kubernetes-server-mock:6.9.2")
}

Client Configuration

3. Basic Client Setup

import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
public class KubernetesClientFactory {
public static KubernetesClient createDefaultClient() {
return new KubernetesClientBuilder().build();
}
public static KubernetesClient createCustomClient() {
Config config = new ConfigBuilder()
.withMasterUrl("https://kubernetes.example.com:6443")
.withNamespace("my-namespace")
.withTrustCerts(true)
.withOauthToken("your-token-here")
.withConnectionTimeout(10000)
.withRequestTimeout(10000)
.build();
return new KubernetesClientBuilder().withConfig(config).build();
}
public static KubernetesClient createClientFromKubeConfig() {
// Automatically loads from ~/.kube/config
return new KubernetesClientBuilder()
.withConfig(Config.autoConfigure(null))
.build();
}
}

4. Spring Boot Configuration

@Configuration
public class KubernetesConfig {
@Value("${kubernetes.namespace:default}")
private String namespace;
@Bean
@ConditionalOnMissingBean
public KubernetesClient kubernetesClient() {
return new KubernetesClientBuilder()
.withConfig(Config.autoConfigure(null))
.build();
}
@Bean
@ConditionalOnMissingBean
public OpenShiftClient openShiftClient() {
return new KubernetesClientBuilder().build().adapt(OpenShiftClient.class);
}
}
# application.yml
kubernetes:
namespace: my-application
client:
connection-timeout: 10000
request-timeout: 10000

Core Operations

5. Pod Operations

@Service
public class PodService {
private final KubernetesClient client;
private final String namespace;
public PodService(KubernetesClient client, 
@Value("${kubernetes.namespace}") String namespace) {
this.client = client;
this.namespace = namespace;
}
public List<Pod> listAllPods() {
return client.pods()
.inNamespace(namespace)
.list()
.getItems();
}
public List<Pod> listPodsWithLabels(Map<String, String> labels) {
return client.pods()
.inNamespace(namespace)
.withLabels(labels)
.list()
.getItems();
}
public Pod getPod(String podName) {
return client.pods()
.inNamespace(namespace)
.withName(podName)
.get();
}
public String getPodLogs(String podName) {
return client.pods()
.inNamespace(namespace)
.withName(podName)
.getLog();
}
public Pod createPod(Pod pod) {
return client.pods()
.inNamespace(namespace)
.resource(pod)
.create();
}
public boolean deletePod(String podName) {
return client.pods()
.inNamespace(namespace)
.withName(podName)
.delete();
}
public Pod updatePod(Pod pod) {
return client.pods()
.inNamespace(namespace)
.resource(pod)
.update();
}
}

6. Pod Creation Example

public class PodCreator {
public static Pod createSimplePod(String name, String image) {
return new PodBuilder()
.withNewMetadata()
.withName(name)
.addToLabels("app", name)
.addToLabels("version", "v1")
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(name)
.withImage(image)
.addNewPort()
.withContainerPort(8080)
.withProtocol("TCP")
.endPort()
.withNewResources()
.addToRequests("memory", new Quantity("512Mi"))
.addToRequests("cpu", new Quantity("500m"))
.addToLimits("memory", new Quantity("1Gi"))
.addToLimits("cpu", new Quantity("1"))
.endResources()
.endContainer()
.withRestartPolicy("Always")
.endSpec()
.build();
}
public static Pod createPodWithConfigMap(String name, String image, 
String configMapName) {
return new PodBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(name)
.withImage(image)
.addNewVolumeMount()
.withName("config-volume")
.withMountPath("/etc/config")
.endVolumeMount()
.endContainer()
.addNewVolume()
.withName("config-volume")
.withNewConfigMap()
.withName(configMapName)
.endConfigMap()
.endVolume()
.endSpec()
.build();
}
}

7. Deployment Operations

@Service
public class DeploymentService {
private final KubernetesClient client;
private final String namespace;
public Deployment createDeployment(String name, String image, int replicas) {
Deployment deployment = new DeploymentBuilder()
.withNewMetadata()
.withName(name)
.addToLabels("app", name)
.endMetadata()
.withNewSpec()
.withReplicas(replicas)
.withNewSelector()
.addToMatchLabels("app", name)
.endSelector()
.withNewTemplate()
.withNewMetadata()
.addToLabels("app", name)
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(name)
.withImage(image)
.addNewPort()
.withContainerPort(8080)
.endPort()
.withNewReadinessProbe()
.withNewHttpGet()
.withPath("/health")
.withPort(new IntOrString(8080))
.endHttpGet()
.withInitialDelaySeconds(5)
.withPeriodSeconds(10)
.endReadinessProbe()
.withNewLivenessProbe()
.withNewHttpGet()
.withPath("/health")
.withPort(new IntOrString(8080))
.endHttpGet()
.withInitialDelaySeconds(15)
.withPeriodSeconds(20)
.endLivenessProbe()
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build();
return client.apps().deployments()
.inNamespace(namespace)
.resource(deployment)
.create();
}
public Deployment scaleDeployment(String name, int replicas) {
return client.apps().deployments()
.inNamespace(namespace)
.withName(name)
.scale(replicas);
}
public Deployment updateDeploymentImage(String name, String newImage) {
return client.apps().deployments()
.inNamespace(namespace)
.withName(name)
.edit()
.editSpec()
.editTemplate()
.editSpec()
.editFirstContainer()
.withImage(newImage)
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.done();
}
public boolean restartDeployment(String name) {
return client.apps().deployments()
.inNamespace(namespace)
.withName(name)
.rolling()
.restart();
}
public List<Deployment> listDeployments() {
return client.apps().deployments()
.inNamespace(namespace)
.list()
.getItems();
}
}

8. Service Operations

@Service
public class ServiceService {
private final KubernetesClient client;
private final String namespace;
public Service createService(String name, Map<String, String> selector, 
int port, int targetPort) {
Service service = new ServiceBuilder()
.withNewMetadata()
.withName(name)
.addToLabels("app", name)
.endMetadata()
.withNewSpec()
.withSelector(selector)
.addNewPort()
.withPort(port)
.withTargetPort(new IntOrString(targetPort))
.withProtocol("TCP")
.endPort()
.withType("ClusterIP")
.endSpec()
.build();
return client.services()
.inNamespace(namespace)
.resource(service)
.create();
}
public Service createLoadBalancer(String name, Map<String, String> selector,
int port, int targetPort) {
Service service = new ServiceBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withNewSpec()
.withSelector(selector)
.addNewPort()
.withPort(port)
.withTargetPort(new IntOrString(targetPort))
.endPort()
.withType("LoadBalancer")
.endSpec()
.build();
return client.services()
.inNamespace(namespace)
.resource(service)
.create();
}
}

Advanced Features

9. Watch and Event Handling

@Service
public class PodWatcherService {
private final KubernetesClient client;
private final String namespace;
private Watch podWatch;
public PodWatcherService(KubernetesClient client,
@Value("${kubernetes.namespace}") String namespace) {
this.client = client;
this.namespace = namespace;
}
public void startPodWatch() {
podWatch = client.pods()
.inNamespace(namespace)
.watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
String podName = pod.getMetadata().getName();
System.out.println("Pod " + podName + " received action: " + action);
switch (action) {
case ADDED:
handlePodAdded(pod);
break;
case MODIFIED:
handlePodModified(pod);
break;
case DELETED:
handlePodDeleted(pod);
break;
case ERROR:
handlePodError(pod);
break;
}
}
@Override
public void onClose(WatcherException cause) {
if (cause != null) {
System.err.println("Watch closed with error: " + cause.getMessage());
// Implement reconnection logic
reconnectWatch();
}
}
private void handlePodAdded(Pod pod) {
// Handle pod creation
System.out.println("Pod created: " + pod.getMetadata().getName());
}
private void handlePodModified(Pod pod) {
// Handle pod modifications
PodStatus status = pod.getStatus();
System.out.println("Pod modified: " + pod.getMetadata().getName() + 
" Phase: " + status.getPhase());
}
private void handlePodDeleted(Pod pod) {
// Handle pod deletion
System.out.println("Pod deleted: " + pod.getMetadata().getName());
}
private void handlePodError(Pod pod) {
// Handle pod errors
System.err.println("Error with pod: " + pod.getMetadata().getName());
}
private void reconnectWatch() {
try {
Thread.sleep(5000);
startPodWatch();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
}
public void stopPodWatch() {
if (podWatch != null) {
podWatch.close();
}
}
}

10. ConfigMap and Secret Operations

@Service
public class ConfigMapService {
private final KubernetesClient client;
private final String namespace;
public ConfigMap createConfigMap(String name, Map<String, String> data) {
ConfigMap configMap = new ConfigMapBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withData(data)
.build();
return client.configMaps()
.inNamespace(namespace)
.resource(configMap)
.create();
}
public ConfigMap updateConfigMap(String name, Map<String, String> newData) {
return client.configMaps()
.inNamespace(namespace)
.withName(name)
.edit()
.withData(newData)
.done();
}
public String getConfigMapValue(String configMapName, String key) {
ConfigMap configMap = client.configMaps()
.inNamespace(namespace)
.withName(configMapName)
.get();
return configMap != null ? configMap.getData().get(key) : null;
}
}
@Service
public class SecretService {
private final KubernetesClient client;
private final String namespace;
public Secret createSecret(String name, Map<String, String> stringData) {
Secret secret = new SecretBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withStringData(stringData)
.withType("Opaque")
.build();
return client.secrets()
.inNamespace(namespace)
.resource(secret)
.create();
}
public String getSecretValue(String secretName, String key) {
Secret secret = client.secrets()
.inNamespace(namespace)
.withName(secretName)
.get();
if (secret != null && secret.getData() != null) {
String base64Value = secret.getData().get(key);
return base64Value != null ? 
new String(Base64.getDecoder().decode(base64Value)) : null;
}
return null;
}
}

11. Custom Resource Definitions (CRD)

// Custom Resource Definition
@Group("example.com")
@Version("v1")
public class MyCustomResource extends CustomResource<MyCustomResourceSpec, MyCustomResourceStatus> 
implements Namespaced {
}
public class MyCustomResourceSpec {
private String image;
private int replicas;
private Map<String, String> config;
// getters and setters
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
public int getReplicas() { return replicas; }
public void setReplicas(int replicas) { this.replicas = replicas; }
public Map<String, String> getConfig() { return config; }
public void setConfig(Map<String, String> config) { this.config = config; }
}
public class MyCustomResourceStatus {
private String phase;
private String message;
private List<String> conditions;
// getters and setters
public String getPhase() { return phase; }
public void setPhase(String phase) { this.phase = phase; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public List<String> getConditions() { return conditions; }
public void setConditions(List<String> conditions) { this.conditions = conditions; }
}
@Service
public class CustomResourceService {
private final KubernetesClient client;
private final String namespace;
public MyCustomResource createCustomResource(String name, String image, int replicas) {
MyCustomResource resource = new MyCustomResource();
resource.setMetadata(new ObjectMetaBuilder()
.withName(name)
.withNamespace(namespace)
.build());
MyCustomResourceSpec spec = new MyCustomResourceSpec();
spec.setImage(image);
spec.setReplicas(replicas);
spec.setConfig(Map.of("key", "value"));
resource.setSpec(spec);
return client.resources(MyCustomResource.class)
.inNamespace(namespace)
.resource(resource)
.create();
}
public List<MyCustomResource> listCustomResources() {
return client.resources(MyCustomResource.class)
.inNamespace(namespace)
.list()
.getItems();
}
public MyCustomResource updateCustomResourceReplicas(String name, int replicas) {
return client.resources(MyCustomResource.class)
.inNamespace(namespace)
.withName(name)
.edit()
.editSpec()
.withReplicas(replicas)
.endSpec()
.done();
}
}

Testing with Mock Kubernetes Server

12. Unit Testing with Mock Server

@ExtendWith(MockitoExtension.class)
class KubernetesServiceTest {
@MockKubernetesServer
private KubernetesServer mockServer;
private KubernetesClient client;
private PodService podService;
@BeforeEach
void setUp() {
client = mockServer.getClient();
podService = new PodService(client, "test-namespace");
// Set up initial state
mockServer.expect().get()
.withPath("/api/v1/namespaces/test-namespace/pods/test-pod")
.andReturn(200, createTestPod("test-pod"))
.always();
}
@Test
void testGetPod() {
Pod pod = podService.getPod("test-pod");
assertNotNull(pod);
assertEquals("test-pod", pod.getMetadata().getName());
}
@Test
void testCreatePod() {
Pod newPod = createTestPod("new-pod");
mockServer.expect().post()
.withPath("/api/v1/namespaces/test-namespace/pods")
.andReturn(201, newPod)
.once();
Pod createdPod = podService.createPod(newPod);
assertNotNull(createdPod);
assertEquals("new-pod", createdPod.getMetadata().getName());
}
private Pod createTestPod(String name) {
return new PodBuilder()
.withNewMetadata()
.withName(name)
.withNamespace("test-namespace")
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName("test-container")
.withImage("test-image:latest")
.endContainer()
.endSpec()
.build();
}
}

13. Integration Test Configuration

@SpringBootTest
@TestPropertySource(properties = {
"kubernetes.namespace=test-namespace",
"kubernetes.client.trust-certs=true"
})
@ActiveProfiles("test")
class KubernetesIntegrationTest {
@Autowired
private KubernetesClient client;
@Autowired
private PodService podService;
@Test
void testPodOperations() {
Pod pod = PodCreator.createSimplePod("test-pod", "nginx:latest");
Pod created = podService.createPod(pod);
assertNotNull(created);
Pod found = podService.getPod("test-pod");
assertEquals("test-pod", found.getMetadata().getName());
boolean deleted = podService.deletePod("test-pod");
assertTrue(deleted);
}
}

Error Handling and Best Practices

14. Comprehensive Error Handling

@Service
public class SafeKubernetesOperations {
private final KubernetesClient client;
private final String namespace;
public Optional<Pod> getPodSafely(String podName) {
try {
Pod pod = client.pods()
.inNamespace(namespace)
.withName(podName)
.get();
return Optional.ofNullable(pod);
} catch (KubernetesClientException e) {
log.error("Error getting pod: {}", podName, e);
return Optional.empty();
}
}
public boolean createPodSafely(Pod pod) {
try {
client.pods()
.inNamespace(namespace)
.resource(pod)
.create();
return true;
} catch (KubernetesClientException e) {
if (e.getCode() == 409) {
log.warn("Pod already exists: {}", pod.getMetadata().getName());
} else {
log.error("Error creating pod: {}", pod.getMetadata().getName(), e);
}
return false;
}
}
public boolean deletePodSafely(String podName) {
try {
return client.pods()
.inNamespace(namespace)
.withName(podName)
.delete();
} catch (KubernetesClientException e) {
if (e.getCode() == 404) {
log.warn("Pod not found for deletion: {}", podName);
return true; // Consider already deleted as success
} else {
log.error("Error deleting pod: {}", podName, e);
return false;
}
}
}
public List<Pod> listPodsWithRetry(int maxRetries) {
int attempt = 0;
while (attempt < maxRetries) {
try {
return client.pods()
.inNamespace(namespace)
.list()
.getItems();
} catch (KubernetesClientException e) {
attempt++;
if (attempt == maxRetries) {
throw e;
}
try {
Thread.sleep(1000 * attempt); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", ie);
}
}
}
return Collections.emptyList();
}
}

15. Resource Cleanup

@Component
public class ResourceCleanupService {
private final KubernetesClient client;
private final String namespace;
@PreDestroy
public void cleanup() {
// Close the client properly
if (client != null) {
client.close();
}
}
public void cleanupResources(String labelSelector) {
try {
// Delete deployments
client.apps().deployments()
.inNamespace(namespace)
.withLabelSelector(labelSelector)
.delete();
// Delete services
client.services()
.inNamespace(namespace)
.withLabelSelector(labelSelector)
.delete();
// Delete config maps
client.configMaps()
.inNamespace(namespace)
.withLabelSelector(labelSelector)
.delete();
} catch (KubernetesClientException e) {
log.warn("Error during resource cleanup: {}", e.getMessage());
}
}
}

Performance Optimization

16. Client Configuration for Performance

@Configuration
public class PerformanceKubernetesConfig {
@Bean
public KubernetesClient highPerformanceClient() {
Config config = new ConfigBuilder()
.withMasterUrl("https://kubernetes.example.com")
.withConnectionTimeout(30000)
.withRequestTimeout(30000)
.withWatchReconnectInterval(5000)
.withWatchReconnectLimit(5)
.withMaxConcurrentRequests(100)
.withMaxConcurrentRequestsPerHost(10)
.withRollingTimeout(120000)
.withWebsocketTimeout(15000)
.withWebsocketPingInterval(30000)
.withUploadRequestTimeout(120000)
.withLoggingInterval(0) // Disable logging for performance
.build();
return new KubernetesClientBuilder().withConfig(config).build();
}
}

Conclusion

Fabric8 Kubernetes Client provides a comprehensive, Java-friendly way to interact with Kubernetes clusters. Key features include:

  • Fluent DSL for intuitive resource operations
  • Strong typing with Java objects for all Kubernetes resources
  • Watch support for real-time event handling
  • Custom Resource support for extending Kubernetes
  • Testing utilities with mock server
  • Spring Boot integration for easy configuration

The client handles connection management, authentication, and error handling, allowing developers to focus on application logic rather than infrastructure concerns.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper