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.