> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Inside the Reconcile Loop

> Explains the Kubernetes controller reconcile loop, level triggered reconciliation, idempotent design, fetching and handling deletions, requeue semantics, and managing desired versus actual child resources

Think of a thermostat on the wall.

You set it to seventy degrees — that's the desired state. The thermostat reads the room temperature — that's the actual state. If the two don't match, the thermostat turns the heat on or off, then checks again and again, forever.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/euSqH_V95DCdREv4/images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/thermostat-process-heat-control-diagram.jpg?fit=max&auto=format&n=euSqH_V95DCdREv4&q=85&s=61226833643a38709c434e03a4430a1e" alt="The image illustrates a thermostat process on the wall, showing a desired state of 70 degrees, an actual state of 68 degrees, and an action to turn the heat on." width="1920" height="1080" data-path="images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/thermostat-process-heat-control-diagram.jpg" />
</Frame>

Reconcile is exactly that loop applied to Kubernetes objects.

Your WebApp's spec is the set point; the cluster's actual state — the Deployment, Service, ConfigMap, Pods, and so on — is the room temperature. The controller closes that gap on every event, every resync, and every restart.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/euSqH_V95DCdREv4/images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/kubernetes-control-loop-desired-state.jpg?fit=max&auto=format&n=euSqH_V95DCdREv4&q=85&s=2b4d6c14d991460cd348843a724ae06f" alt="The image illustrates the Kubernetes control loop, showing the relationship between the Desired State, Controller, and Actual State." width="1920" height="1080" data-path="images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/kubernetes-control-loop-desired-state.jpg" />
</Frame>

Reconcile is the heart of every controller you write. When implemented correctly, the rest of the operator becomes straightforward; when it's wrong, you'll be struggling with unpredictable behavior.

Here is the Reconcile signature provided by controller-runtime — it has two inputs and two outputs:

```go theme={null}
func (r *WebAppReconciler) Reconcile(
	ctx context.Context,
	req ctrl.Request,
) (ctrl.Result, error) {
	// ...
}
```

The `req` contains a `NamespacedName` (the key of the object that needs attention). Notice what it does not contain: neither the object itself nor the event that triggered the call. This is intentional.

Controllers in Kubernetes are level-triggered, not edge-triggered. You do not react to "a WebApp was created"; you react to "the WebApp named `foo` in namespace `bar` might need work — go look." Embracing this mental model is essential.

<Callout icon="lightbulb" color="#1CB2FE">
  Controllers are level-triggered: always reconcile to the current desired state by looking up the object by key, not by relying on the event payload.
</Callout>

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/euSqH_V95DCdREv4/images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/edge-triggered-level-triggered-concepts-contrast.jpg?fit=max&auto=format&n=euSqH_V95DCdREv4&q=85&s=e2f163fbcd8c0ab640ff5055e9cdd01e" alt="The image contrasts edge-triggered and level-triggered concepts, suggesting that level-triggered is more important in the lesson." width="1920" height="1080" data-path="images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/edge-triggered-level-triggered-concepts-contrast.jpg" />
</Frame>

Every reconcile starts by answering three questions:

* What's the desired state?
* What's the actual state?
* What's the diff (what needs to change)?

A typical reconcile body follows these four steps:

1. Fetch the parent resource (the WebApp) from the cache using `r.Get` with the request's `NamespacedName`.
2. Compute the desired child objects (Deployment, Service, ConfigMap, etc.) from the WebApp spec.
3. Create or update those child objects so they match the desired state.
4. Update the WebApp's status to reflect the observed (actual) state.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/euSqH_V95DCdREv4/images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/reconciliation-questions-desired-actual-diff.jpg?fit=max&auto=format&n=euSqH_V95DCdREv4&q=85&s=793f53b0e3987de99397cc1a77c6772f" alt="The image outlines three questions for reconciliation: &#x22;Desired? What should exist,&#x22; &#x22;Actual? What does exist,&#x22; and &#x22;Diff? What needs to change.&#x22;" width="1920" height="1080" data-path="images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/reconciliation-questions-desired-actual-diff.jpg" />
</Frame>

The first step — fetching the WebApp safely — is a small, important pattern. Use the API to get the object, and treat a not-found as a normal deletion case:

```go theme={null}
webapp := &webappv1.WebApp{}
if err := r.Get(ctx, req.NamespacedName, webapp); err != nil {
    if apierrors.IsNotFound(err) {
        // The WebApp was deleted. Owned children will be cleaned up by GC via OwnerReferences.
        return ctrl.Result{}, nil
    }
    // Any other error is a real problem (permissions, API server unreachable, etc.)
    return ctrl.Result{}, err
}
```

Notes:

* `webappv1` is the package generated from your CRD group/version.
* `apierrors` is `k8s.io/apimachinery/pkg/api/errors`; use `IsNotFound` to distinguish deletions from transient API errors.
* Owned children created with proper OwnerReferences are garbage-collected automatically. See the Kubernetes docs on OwnerReferences for details.

This fetch-and-bail pattern (under ten lines of Go) is the canonical approach to "fetch the parent and exit safely if it's gone."

The reconcile loop must be idempotent. The same WebApp will be reconciled many times — on creation, on spec changes, on periodic resyncs, after controller restarts, and after transient errors. If running reconcile twice yields a different result than running it once, you have a bug.

<Callout icon="warning" color="#FF6B6B">
  Idempotency is critical: ensure reconcile is safe to run repeatedly. Avoid relying on side-effects that make subsequent runs behave differently.
</Callout>

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/euSqH_V95DCdREv4/images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/idempotency-in-loops-concept-diagram.jpg?fit=max&auto=format&n=euSqH_V95DCdREv4&q=85&s=541daa0adaed82ded4be60b58dd52d1e" alt="The image illustrates the concept of idempotency in loops, stating &#x22;Run twice == run once&#x22; and mentions the importance of handling creation, spec changes, re-sync, restarts, and errors. It emphasizes that if running twice doesn't equal running once, it's considered a bug." width="1920" height="1080" data-path="images/Kubernetes-Operators/Building-The-WebApp-Operator-Core-Reconcile/Inside-the-Reconcile-Loop/idempotency-in-loops-concept-diagram.jpg" />
</Frame>

Return values from Reconcile control requeue behavior. Use these return patterns to express what should happen next:

| Return value                        | Meaning                                                   | When to use                                     |
| ----------------------------------- | --------------------------------------------------------- | ----------------------------------------------- |
| `ctrl.Result{}, nil`                | Done for now; wait for the next event.                    | Normal completion — nothing pending.            |
| `ctrl.Result{RequeueAfter: d}, nil` | Requeue this request after duration `d`.                  | Polling/waiting for readiness or a timed retry. |
| `ctrl.Result{}, err`                | Error: triggers controller-runtime's exponential backoff. | Unexpected failures — let the framework retry.  |

Example returns in code:

```go theme={null}
return ctrl.Result{}, nil
return ctrl.Result{RequeueAfter: d}, nil
return ctrl.Result{}, err
```

When `r.Get` returns a not-found error, the resource was deleted. If you created children with `OwnerReference`, Kubernetes will garbage collect them, so you typically return without error:

```go theme={null}
return ctrl.Result{}, nil
```

If you need to perform work during deletion (for example, to clean up external resources), implement finalizers and handle that lifecycle explicitly — this will be covered in the finalizers and cleanup section.

In the next lesson you'll wire up the rest of the loop: computing desired child resources, performing create-or-update operations, and updating status to reflect observed state.

Links and references:

* [Kubernetes Concepts — Desired State and Controllers](https://kubernetes.io/docs/concepts/overview/working-with-objects/handlers-events/)
* [controller-runtime Reconcile docs](https://pkg.go.dev/sigs.k8s.io/controller-runtime)
* [`k8s.io/apimachinery/pkg/api/errors`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/errors)
* [OwnerReferences and garbage collection](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/ef5c1b44-311a-415f-8eeb-8a460e759cfe/lesson/5377c550-a928-4c7d-bf1b-61df639cf1c4" />
</CardGroup>
