CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-fabric8--kubernetes-client

Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL

Pending
Overview
Eval results
Files

watch-informers.mddocs/

Watch and Informers

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.

Watch Operations

Basic Watch Interface

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
    }
}

Resource Watch Methods

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);
}

Informer Framework

SharedInformerFactory

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();
}

SharedIndexInformer

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();
}

ResourceEventHandler

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) {}
}

Cache and Indexer

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);
}

Usage Examples

Basic Watch Operations

// 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 with ListOptions

// 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");
    }
});

Using Informers

// 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());
    }
}

Multiple Resource Informers

// 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();

Custom Resource Informers

// 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();

Namespace-Scoped Informers

// 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();

Advanced Cache Operations

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();

Error Handling and Recovery

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();

Best Practices

Resource Management

// 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 minutes

Error Handling

Watch 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

docs

api-groups.md

client-configuration.md

core-resources.md

custom-resources.md

exception-handling.md

index.md

pod-operations.md

utilities.md

watch-informers.md

tile.json