Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL
—
The Fabric8 Kubernetes Client provides powerful mechanisms for real-time monitoring of Kubernetes resources through watch operations and the informer pattern. These features enable efficient tracking of resource changes with caching and event-driven updates.
Direct watch operations for monitoring resource changes in real-time.
public interface Watch extends Closeable {
void close();
}
public interface Watcher<T> {
void eventReceived(Action action, T resource);
default void onClose() {}
void onClose(WatcherException cause);
default boolean reconnecting() { return false; }
enum Action {
ADDED, MODIFIED, DELETED, ERROR, BOOKMARK
}
}All resource operations support watching for changes.
// From Resource interface
public interface Resource<T extends HasMetadata> {
Watch watch(Watcher<T> watcher);
}
// From NonNamespaceOperation interface
public interface NonNamespaceOperation<T extends HasMetadata, L extends KubernetesResourceList<T>, R extends Resource<T>> {
Watch watch(Watcher<T> watcher);
Watch watch(String resourceVersion, Watcher<T> watcher);
Watch watch(ListOptions listOptions, Watcher<T> watcher);
}Factory for creating and managing informers with efficient resource caching.
public interface SharedInformerFactory {
// Create informers
<T extends HasMetadata> SharedIndexInformer<T> sharedIndexInformerFor(
Class<T> apiTypeClass, long resyncPeriodInMillis);
// Get existing informers
<T extends HasMetadata> SharedIndexInformer<T> getExistingSharedIndexInformer(Class<T> apiTypeClass);
// Lifecycle management
void startAllRegisteredInformers();
void stopAllRegisteredInformers();
// Event listeners
void addSharedInformerEventListener(SharedInformerEventListener event);
}
// Access via client
public interface KubernetesClient {
SharedInformerFactory informers();
}Individual informer for a specific resource type with caching and indexing.
public interface SharedIndexInformer<T> extends AutoCloseable {
// Event handlers
SharedIndexInformer<T> addEventHandler(ResourceEventHandler<? super T> handler);
SharedIndexInformer<T> addEventHandlerWithResyncPeriod(ResourceEventHandler<? super T> handler, long resyncPeriod);
SharedIndexInformer<T> removeEventHandler(ResourceEventHandler<? super T> handler);
// Indexing and caching
Indexer<T> getIndexer();
SharedIndexInformer<T> addIndexers(Map<String, Function<T, List<String>>> indexers);
SharedIndexInformer<T> removeIndexer(String name);
SharedIndexInformer<T> removeNamespaceIndex();
// Lifecycle
SharedIndexInformer<T> run();
void stop();
void close();
boolean hasSynced();
String lastSyncResourceVersion();
}Handler interface for processing resource change events.
public interface ResourceEventHandler<T> {
void onAdd(T obj);
void onUpdate(T oldObj, T newObj);
void onDelete(T obj, boolean deletedFinalStateUnknown);
// Default empty implementations
default void onAdd(T obj) {}
default void onUpdate(T oldObj, T newObj) {}
default void onDelete(T obj, boolean deletedFinalStateUnknown) {}
}Caching and indexing interfaces for efficient resource lookups.
public interface Store<T> {
void add(T obj);
void update(T obj);
void delete(T obj);
List<T> list();
List<String> listKeys();
T get(T obj);
T getByKey(String key);
void replace(List<T> objects, String resourceVersion);
}
public interface Indexer<T> extends Store<T> {
List<String> index(String indexName, T obj);
List<String> indexKeys(String indexName, String indexKey);
List<T> getByIndex(String indexName, String indexKey);
void addIndexers(Map<String, Function<T, List<String>>> indexers);
}// Watch pods in current namespace
Watch podWatch = client.pods().watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
switch (action) {
case ADDED:
System.out.println("Pod added: " + pod.getMetadata().getName());
break;
case MODIFIED:
System.out.println("Pod modified: " + pod.getMetadata().getName() +
" phase: " + pod.getStatus().getPhase());
break;
case DELETED:
System.out.println("Pod deleted: " + pod.getMetadata().getName());
break;
case ERROR:
System.out.println("Watch error for pod: " + pod);
break;
}
}
@Override
public void onClose(WatcherException cause) {
if (cause != null) {
System.out.println("Pod watch closed with error: " + cause.getMessage());
if (cause.isHttpGone()) {
System.out.println("Resource version too old, need to relist");
}
} else {
System.out.println("Pod watch closed normally");
}
}
});
// Watch with label selector
Watch labelWatch = client.pods()
.withLabel("app", "my-app")
.watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
System.out.println(action + " pod with app=my-app: " + pod.getMetadata().getName());
}
@Override
public void onClose(WatcherException cause) {
System.out.println("Labeled pod watch closed");
}
});
// Remember to close watches
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
podWatch.close();
labelWatch.close();
}));// Watch from specific resource version
ListOptions listOptions = new ListOptionsBuilder()
.withResourceVersion("12345")
.withTimeoutSeconds(300L)
.build();
Watch versionWatch = client.services().watch(listOptions, new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service service) {
System.out.println("Service " + action.name().toLowerCase() + ": " +
service.getMetadata().getName());
}
@Override
public void onClose(WatcherException cause) {
System.out.println("Service watch closed");
}
});// Create informer for pods with 30 second resync period
SharedIndexInformer<Pod> podInformer = client.informers()
.sharedIndexInformerFor(Pod.class, 30 * 1000);
// Add event handler
podInformer.addEventHandler(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
System.out.println("Informer - Pod added: " + pod.getMetadata().getName());
// Access cached data
List<Pod> allPods = podInformer.getIndexer().list();
System.out.println("Total cached pods: " + allPods.size());
}
@Override
public void onUpdate(Pod oldPod, Pod newPod) {
String name = newPod.getMetadata().getName();
String oldPhase = oldPod.getStatus() != null ? oldPod.getStatus().getPhase() : "Unknown";
String newPhase = newPod.getStatus() != null ? newPod.getStatus().getPhase() : "Unknown";
if (!oldPhase.equals(newPhase)) {
System.out.println("Pod " + name + " phase changed: " + oldPhase + " -> " + newPhase);
}
}
@Override
public void onDelete(Pod pod, boolean deletedFinalStateUnknown) {
System.out.println("Informer - Pod deleted: " + pod.getMetadata().getName());
if (deletedFinalStateUnknown) {
System.out.println("Delete event may have been missed");
}
}
});
// Add another handler with different resync period
podInformer.addEventHandlerWithResyncPeriod(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
// This handler gets periodic resync events even if pod hasn't changed
System.out.println("Resync handler - Pod: " + pod.getMetadata().getName());
}
}, 60 * 1000); // 60 second resync
// Start all informers
client.informers().startAllRegisteredInformers();
// Wait for initial sync
CompletableFuture<Void> syncFuture = client.informers().startAllRegisteredInformers(true);
syncFuture.get(30, TimeUnit.SECONDS); // Wait up to 30 seconds for sync
// Check sync status
if (podInformer.hasSynced()) {
System.out.println("Pod informer synced, cache is ready");
// Get cached pods
List<Pod> cachedPods = podInformer.getIndexer().list();
System.out.println("Cached pods: " + cachedPods.size());
// Get specific pod from cache
Pod cachedPod = podInformer.getIndexer().getByKey("default/my-pod");
if (cachedPod != null) {
System.out.println("Found cached pod: " + cachedPod.getMetadata().getName());
}
}// Create informers for multiple resource types
SharedIndexInformer<Pod> podInformer =
client.informers().sharedIndexInformerFor(Pod.class, 30 * 1000);
SharedIndexInformer<Service> serviceInformer =
client.informers().sharedIndexInformerFor(Service.class, 30 * 1000);
SharedIndexInformer<Deployment> deploymentInformer =
client.informers().sharedIndexInformerFor(Deployment.class, 30 * 1000);
// Add handlers
podInformer.addEventHandler(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
System.out.println("New pod: " + pod.getMetadata().getName());
}
});
serviceInformer.addEventHandler(new ResourceEventHandler<Service>() {
@Override
public void onAdd(Service service) {
System.out.println("New service: " + service.getMetadata().getName());
}
});
deploymentInformer.addEventHandler(new ResourceEventHandler<Deployment>() {
@Override
public void onUpdate(Deployment oldDeployment, Deployment newDeployment) {
Integer oldReplicas = oldDeployment.getSpec().getReplicas();
Integer newReplicas = newDeployment.getSpec().getReplicas();
if (!Objects.equals(oldReplicas, newReplicas)) {
System.out.println("Deployment " + newDeployment.getMetadata().getName() +
" scaled: " + oldReplicas + " -> " + newReplicas);
}
}
});
// Start all informers at once
client.informers().startAllRegisteredInformers();// Assuming you have a custom Database resource class
SharedIndexInformer<Database> databaseInformer = client.informers()
.sharedIndexInformerForCustomResource(Database.class, 30 * 1000);
databaseInformer.addEventHandler(new ResourceEventHandler<Database>() {
@Override
public void onAdd(Database database) {
System.out.println("New database: " + database.getMetadata().getName() +
" version: " + database.getSpec().getVersion());
}
@Override
public void onUpdate(Database oldDatabase, Database newDatabase) {
DatabaseStatus oldStatus = oldDatabase.getStatus();
DatabaseStatus newStatus = newDatabase.getStatus();
String oldPhase = oldStatus != null ? oldStatus.getPhase() : "Unknown";
String newPhase = newStatus != null ? newStatus.getPhase() : "Unknown";
if (!oldPhase.equals(newPhase)) {
System.out.println("Database " + newDatabase.getMetadata().getName() +
" status: " + oldPhase + " -> " + newPhase);
}
}
});
client.informers().startAllRegisteredInformers();// Create informer for specific namespace
SharedIndexInformer<Pod> namespacedInformer = client.informers()
.sharedIndexInformerFor(Pod.class, 30 * 1000, "production");
namespacedInformer.addEventHandler(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
System.out.println("Production pod added: " + pod.getMetadata().getName());
}
});
client.informers().startAllRegisteredInformers();SharedIndexInformer<Pod> podInformer =
client.informers().sharedIndexInformerFor(Pod.class, 30 * 1000);
// Add custom indexers
Map<String, Function<Pod, List<String>>> indexers = new HashMap<>();
// Index pods by node name
indexers.put("byNode", pod -> {
String nodeName = pod.getSpec().getNodeName();
return nodeName != null ? Collections.singletonList(nodeName) : Collections.emptyList();
});
// Index pods by owner reference
indexers.put("byOwner", pod -> {
return pod.getMetadata().getOwnerReferences().stream()
.map(ref -> ref.getKind() + "/" + ref.getName())
.collect(Collectors.toList());
});
podInformer.getIndexer().addIndexers(indexers);
podInformer.addEventHandler(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
Indexer<Pod> indexer = podInformer.getIndexer();
// Get all pods on the same node
String nodeName = pod.getSpec().getNodeName();
if (nodeName != null) {
List<Pod> podsOnNode = indexer.getByIndex("byNode", nodeName);
System.out.println("Pods on node " + nodeName + ": " + podsOnNode.size());
}
// Get pods with same owner
pod.getMetadata().getOwnerReferences().forEach(owner -> {
String ownerKey = owner.getKind() + "/" + owner.getName();
List<Pod> podsWithSameOwner = indexer.getByIndex("byOwner", ownerKey);
System.out.println("Pods owned by " + ownerKey + ": " + podsWithSameOwner.size());
});
}
});
client.informers().startAllRegisteredInformers();SharedIndexInformer<Pod> podInformer =
client.informers().sharedIndexInformerFor(Pod.class, 30 * 1000);
// Add event listener for informer lifecycle events
client.informers().addSharedInformerEventListener(new SharedInformerEventListener() {
@Override
public void onAdd(SharedIndexInformer informer) {
System.out.println("Informer started: " + informer.getClass().getSimpleName());
}
@Override
public void onUpdate(SharedIndexInformer informer) {
System.out.println("Informer updated: " + informer.getClass().getSimpleName());
}
@Override
public void onDelete(SharedIndexInformer informer) {
System.out.println("Informer stopped: " + informer.getClass().getSimpleName());
}
});
podInformer.addEventHandler(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
// Check if informer is still running
if (!podInformer.isRunning()) {
System.out.println("Warning: Informer is not running");
return;
}
// Check sync status
if (!podInformer.hasSynced()) {
System.out.println("Warning: Informer not yet synced");
return;
}
System.out.println("Pod added: " + pod.getMetadata().getName());
}
});
// Graceful shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutting down informers...");
client.informers().stopAllRegisteredInformers();
}));
client.informers().startAllRegisteredInformers();// Always close watches
try (Watch watch = client.pods().watch(watcher)) {
// Watch is automatically closed when leaving try block
Thread.sleep(60000); // Watch for 1 minute
}
// Use informers for long-running applications
SharedInformerFactory informers = client.informers();
// Informers are more efficient than watches for long-term monitoring
// Set appropriate resync periods
// Too short: unnecessary CPU usage
// Too long: delayed detection of missed events
SharedIndexInformer<Pod> informer =
informers.sharedIndexInformerFor(Pod.class, 5 * 60 * 1000); // 5 minutesWatch watch = client.pods().watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
try {
// Process event
processEvent(action, pod);
} catch (Exception e) {
System.err.println("Error processing pod event: " + e.getMessage());
}
}
@Override
public void onClose(WatcherException cause) {
if (cause != null) {
if (cause.isHttpGone()) {
// Resource version too old, need to restart watch
System.out.println("Restarting watch due to HTTP 410 Gone");
restartWatch();
} else {
System.err.println("Watch error: " + cause.getMessage());
}
}
}
});Install with Tessl CLI
npx tessl i tessl/maven-io-fabric8--kubernetes-client