Skip to main content
You will see three words appear over and over in operator code: Manager, Controller, and Reconciler. They can sound interchangeable at first, but each has a distinct responsibility. Once you can tell them apart, the rest of the codebase stops looking like a pile of helpers. This article builds a clear mental model for each piece and shows how they fit together in a controller-runtime based operator. Let’s start with the manager.
The image describes three roles—Manager, Controller, and Reconciler—each with distinct functions in system management. It emphasizes the importance of distinguishing these roles to improve codebase clarity.
What the manager is (and is not)
  • The manager is the front desk of the building your operator runs in: one process-wide owner of shared infrastructure.
  • Exactly one manager runs inside your operator binary. It bootstraps and owns the shared pieces that all controllers use.
The image illustrates a process flow from an "Operator binary" to a "Manager," indicating there is exactly one manager. It includes a command-line icon and a block labeled "MANAGER" with a crown symbol.
Core manager-owned resources
  • Shared client — the single connection used to read and write Kubernetes objects.
  • Shared cache — an in-memory, continuously-updated view of the objects your controllers care about (so you don’t query the API server for every read).
  • Scheme, logger, metrics server, health probes, webhook server, leader election — all registered and owned by the manager so controllers can share them.
The image illustrates a manager's responsibilities, including a "SHARED CACHE" with services labeled as "Running" or "Pending." An HQ icon is also shown.
The image is a diagram titled "The Manager Owns the Shared Machinery," highlighting components such as Scheme, Logger, Metrics server, Health probes, Webhook server, and Leader election around a central "Manager."
Because controllers share the manager-owned cache and client, multiple controllers watching the same resource type do not each open individual watches against the API server. They read from the same shared cache instead.
The image illustrates a setup with two controllers, A and B, both monitoring pods in a shared cache. The shared cache shows the status of three pods: two are running, and one is pending.
Next: the controller If the manager is the front desk, a controller is a staff member assigned to watch a specific set of rooms. You tell the controller: “watch WebApp objects and also watch the Deployments and Services I own.” Under the hood, a controller wires up informers and workqueues:
  • Informer: a live feed — a subscription that streams changes from the API server into the shared cache (no periodic polling required).
  • Predicate: a true/false filter (think of it as a bouncer). Only events that pass predicates get queued.
  • Workqueue: an ordered queue of work items with retries and rate-limiting. Items are typically just namespace/name keys — not the full object or the event payload.
The image illustrates a system where an API server watches and streams changes to an "Informer," which provides a live feed subscription to update a shared cache, replacing the need for frequent API polling.
Reconciler: the single method you implement You almost never instantiate a controller directly. Instead, you implement a reconciler that provides one method: Reconcile. The controller runtime calls this method with a context and a request; your job is to make the cluster match the desired state. Example Reconcile signature in Go:
Important: req is just a namespaced name (namespace/name) — no object, no event, no diff. This is deliberate.
The image explains a reconciler method called "Reconcile," showing a request with a namespace "webapp-ns" and name "webapp-a," along with notes saying "No object," "No event," "No diff," and "That is deliberate."
Think of the req like a kitchen ticket at a restaurant: it has a table number, not the meal. The controller gives you the table number; your Reconcile must look up the current state and ensure the right resources are present. If you return an error or a ctrl.Result that requests requeueing (for example, return ctrl.Result{RequeueAfter: time.Minute}, nil), the workqueue will schedule the request again. Putting it all together: the operator heartbeat
  • The manager starts and registers controllers.
  • Each controller’s informer keeps the shared cache fresh.
  • An API server change triggers an event; predicates filter it; the namespaced key is enqueued.
  • A worker pulls the key off the workqueue and calls your Reconcile.
  • Your Reconcile reads from the cache, uses the client to write, and returns a result.
The image is a flowchart titled "The Full Loop – The Heartbeat of an Operator," illustrating a sequence of steps in an operator's process, from "Manager" starting the controller to updating the "API Server."
This cycle repeats indefinitely; it is the heartbeat of every operator. Three essential rules for writing Reconcile
  • Reconcile is level-based, not edge-based — read the current state and make it match the desired state. You may be called once for many rapid events; that’s okay.
  • Reconcile must be idempotent — running it multiple times should be safe and converge to the same result.
  • Reconcile should be fast — long-running tasks belong in a goroutine, background job, or separate controller. Blocking Reconcile delays the workqueue.
The image illustrates the reconciliation process, showing that multiple changes trigger a single Reconcile function, which then reads the current state. It highlights three rules for writing reconcile: level-based, idempotent, and fast.
Three quick tips: 1) Design Reconcile to read current state (level-based). 2) Make it idempotent. 3) Keep it short — move long tasks out of the reconcile loop.
Quick reference: Manager vs Controller vs Reconciler Links and references That’s it for this lesson.

Watch Video