Your Function Will Be Called Many Times
Idempotency is a mathematical property: applying an operation multiple times produces the same result as applying it once. f(f(x)) = f(x).
In the context of a Kubernetes controller, your Reconcile function might be called 10 times in a row for the same object. This happens because:
- The in-memory cache gets updated after you write to the API server, triggering a new event.
- Multiple fields change at once, generating multiple events.
- The controller restarts and reprocesses all objects from scratch.
- You explicitly requeue with
RequeueAfter.
Your code must be idempotent. Every operation must be safe to run repeatedly.
| ❌ Not Idempotent | ✅ Idempotent |
|---|---|
createPod() — creates a new Pod every time |
createPodIfNotExists() — checks first, creates only if missing |
appendToConfigMap() — adds a line every call |
setConfigMapValue() — sets the key to the desired value |
incrementCounter() — increments on every call |
setCounter(desiredValue) — sets to the target value |
The practical pattern:
// Always: Get → Check → Create or Update
existing := &appsv1.Deployment{}
err := r.Get(ctx, namespacedName, existing)
if errors.IsNotFound(err) {
// Resource doesn't exist — create it
desired := r.buildDesiredDeployment(myResource)
return ctrl.Result{}, r.Create(ctx, desired)
}
if err != nil {
return ctrl.Result{}, err
}
// Resource exists — check if it matches desired state
if needsUpdate(existing, myResource) {
existing.Spec = buildDesiredSpec(myResource)
return ctrl.Result{}, r.Update(ctx, existing)
}
// Resource exists and is correct — do nothing
return ctrl.Result{}, nil
The key insight is that your controller is not a script that runs once. It is a continuous control loop. Every action it takes must be designed with the assumption that it will be called again immediately after.