keepcalculator

06 Numbers and Powers

Build Your First Kubernetes Controller in Java

Card No. 06·41158 · Updated · Standard formula, shown below

This guide walks you through creating a basic Kubernetes controller in Java using the official client library. A controller watches for changes to a specific resource (like a custom object) and takes action to bring the cluster to a desired state. You’ll end with a working controller that logs events and can be extended to reconcile resources.

What You’ll Need

  • Java 11 or newer (OpenJDK or Oracle JDK)
  • Maven or Gradle for dependency management
  • A Kubernetes cluster (minikube, kind, or a remote cluster) with kubectl configured
  • Basic familiarity with Java, Maven/Gradle, and Kubernetes concepts (pods, deployments)

Step-by-Step

  1. Set up a new Java project. Create a directory and initialize a Maven project (or use your IDE’s wizard). Add the Kubernetes Java client dependency to your pom.xml:
  2. <dependency>
        <groupId>io.kubernetes</groupId>
        <artifactId>client-java</artifactId>
        <version>20.0.0</version>
    </dependency>
  3. Create a main class. In src/main/java/com/example/Controller.java, add a class with a main method that will bootstrap the controller.
  4. Initialize the Kubernetes client. In the main method, load the client from your kubeconfig (or use in-cluster config when running inside a pod):
  5. ApiClient client = Config.defaultClient();
    Configuration.setDefaultApiClient(client);
  6. Create a custom resource definition (CRD) for testing. For simplicity, we’ll watch a built-in resource like ConfigMap. Create a ConfigMap in your cluster to trigger events. If you want a custom resource, define a CRD YAML and apply it.
  7. Set up a watcher. Use the CoreV1Api to list and watch ConfigMaps. The Java client provides a Watch class that handles the watch stream:
  8. CoreV1Api api = new CoreV1Api();
    Watch<V1ConfigMap> watch = Watch.createWatch(
        client,
        api.listConfigMapForAllNamespacesCall(null, null, null, null, null, null, null, null, null, null, null),
        new TypeToken<Watch.Response<V1ConfigMap>>(){}.getType());
  9. Process watch events. Iterate over the watch stream and handle each event type (ADDED, MODIFIED, DELETED). For each event, print a log or perform a reconciliation action:
  10. for (Watch.Response<V1ConfigMap> item : watch) {
        V1ConfigMap cm = item.object;
        switch (item.type) {
            case "ADDED":
                System.out.println("ConfigMap added: " + cm.getMetadata().getName());
                break;
            case "MODIFIED":
                System.out.println("ConfigMap modified: " + cm.getMetadata().getName());
                break;
            case "DELETED":
                System.out.println("ConfigMap deleted: " + cm.getMetadata().getName());
                break;
        }
    }
  11. Add a reconciliation loop. In a real controller, you’d compare the desired state with the observed state and take corrective action. For this first controller, simply log the event. To make it more useful, you could create a Deployment when a ConfigMap with a specific label appears.
  12. Handle errors and resync. The watch stream can break. Wrap the loop in a try-catch and re-establish the watch after a short delay. Also, periodically list all resources to catch up on missed events (resync).
  13. Build and run. Package your project with mvn package. Run the resulting JAR with java -jar target/controller.jar. Ensure your kubeconfig points to a cluster you can access.
  14. Test the controller. In another terminal, create a ConfigMap: kubectl create configmap test-cm --from-literal=foo=bar. You should see a log line in your controller’s output. Delete it and observe the DELETED event.

Common Mistakes

  • Not handling the watch stream properly — forgetting to close it or not catching exceptions causes the controller to crash.
  • Using the wrong API version or method for the resource you’re watching. Check the client documentation for the correct class and method.
  • Ignoring the resourceVersion parameter — this can lead to missing events or duplicate processing.
  • Running the controller without proper RBAC permissions when inside a cluster. You need to grant permissions to list/watch the resources.
  • Blocking the main thread with a long-running operation inside the event handler, which stalls the watch loop.

Tips

  • Use a thread pool to process events asynchronously, but keep the watch loop responsive.
  • Implement a resync period (e.g., every 30 minutes) to reconcile the full state, catching any missed changes.
  • Use the io.kubernetes.client.extended.controller package for a higher-level controller framework with informers, workqueues, and leader election.
  • Log structured data (e.g., with SLF4J) to make debugging easier.
  • Test with a local cluster like minikube to avoid affecting production.

Troubleshooting

  • No events received: Check that your kubeconfig is correct and you have permissions. Run kubectl get configmaps to verify. Also ensure the watch is not filtered by field selectors incorrectly.
  • Watch stream closes immediately: This often happens due to a network timeout or an unauthorized request. Increase timeouts in the client configuration and verify RBAC.
  • ClassNotFoundException for TypeToken: Ensure you have the gson dependency (it comes with the client) and that you imported the correct TypeToken class.
  • Compilation errors with generics: The Watch class uses generics; make sure you use the exact type from the API (e.g., V1ConfigMap) and the TypeToken matches.
  • Controller runs but does nothing: Check that you are iterating over the watch correctly and that the event handler is not throwing an exception that is silently swallowed.

Frequently Asked Questions

Do I need a custom resource definition to build a controller?

No, you can watch built-in resources like ConfigMaps or Deployments. However, controllers are most useful when they watch custom resources that represent your application’s specific needs.

Can I run this controller outside a cluster?

Yes, as long as you have a kubeconfig file that grants access to a cluster. The controller will connect to the cluster’s API server from your local machine.

How do I make the controller reconcile continuously?

Implement a resync mechanism: periodically list all resources and compare them with the desired state. The Kubernetes Java client provides informers that do this automatically.

What is the difference between a controller and an operator?

An operator is a controller that manages a specific application or component, often using custom resources. All operators are controllers, but not all controllers are operators.

How do I handle multiple resource types in one controller?

You can set up multiple watches, each in its own thread, or use the controller framework’s informers to manage multiple resources with a shared workqueue.

What is the best way to process events without blocking?

Use a workqueue pattern: the watch handler adds the resource key to a queue, and worker goroutines/threads process the keys. This decouples the watch from the reconciliation logic.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →