advanced25 min studyLesson 351 in Engineering

Reconciliation: restore an invariant from the state you can observe

Design repeatable controller actions that converge toward desired state despite missed events, stale reads, and uncertain external outcomes.

On this page

What you’ll understand

  • Write a desired-versus-observed invariant and a no-op fixed point.
  • Distinguish repeated reconciliation from replaying event commands.
  • Explain idempotent external effects and truthful status reporting.

A controller repeatedly compares declared intent with observed reality and takes bounded steps to reduce the difference. The event that wakes it is a hint to inspect a resource, not an instruction to repeat a historical side effect. This module uses Kubernetes v1.34 API documentation, client-go v0.34.0 where library behavior matters, and explicitly labeled toy models. The central engineering task is preserving the intended invariant through retries, partial failure, and changes that arrive while reconciliation is already running.

Describe the invariant before the event handler

For a fictional worker-group resource, the desired specification may request five workers while observation shows three current owned workers. The controller should inspect identity, ownership, deletion state, and readiness before deciding which action is missing. A watch notification that a worker disappeared does not mean blindly create one more worker: another reconciliation may already have replaced it, or the user may have reduced the desired count. Read current state and compute the required difference instead of accumulating event-derived commands.

A converged state is a fixed point for the controller’s intended actions while the desired state and relevant dependencies remain unchanged. Reconciliation may still refresh observations, but should avoid unnecessary writes and duplicate external resources. Creating a worker is also different from that worker becoming ready; observed existence, scheduling, startup, and application readiness are separate milestones. A desired count is not a promise that capacity is available or that every attempted action succeeds within one pass.

e=d−n,R(x∗,d)=x∗e=d-n,\qquad R(x^*,d)=x^*
d and n count desired and observed current workers in a deliberately simple count model. R is a reconciliation transition, and x* is a converged state for unchanged intent and dependencies. Real controllers need richer identity and health predicates than this scalar error.↔ Scroll wide equations horizontally.

Retries must discover an earlier uncertain outcome

Suppose an external create request times out after the remote service may already have accepted it. Retrying with a fresh random identity can leak a second resource. Use a stable operation identity or deterministic external name, then query or ensure the intended resource on a retry. The remote API must actually support the chosen idempotency contract. Recording a local success flag before the external effect can instead lose the effect after a crash, while recording only afterward leaves an uncertainty window that the protocol must handle.

Status should report observed facts about the relevant desired generation, rather than claim success because an API write returned. Many resource types use generation-related conventions, but the exact generation and observedGeneration semantics belong to their API definition. A Ready condition for an older desired generation is not evidence that the new configuration is ready. Avoid unconditional status updates that trigger another reconcile without changing information. Distinguish retryable dependency failures from invalid specifications that need a user-visible condition and a later change.

nk+1=nk+min⁡(b,max⁡(0,d−nk))n_{k+1}=n_k+\min(b,\max(0,d-n_k))
This toy scale-up recurrence adds at most b successful workers per pass for fixed desired count d, starting with n_0≤d. Counts are dimensionless. It assumes immediate successful observation and no concurrent changes; it is not Kubernetes’s implementation or a readiness model.↔ Scroll wide equations horizontally.

Bound each pass and preserve recovery evidence

A reconciliation loop should limit work per pass, respect cancellation, and leave enough durable evidence to resume after a crash. External resources need an identity tied to the object’s immutable UID rather than only a reusable namespace/name. Deletion follows a different branch from ordinary provisioning, so inspect the deletion timestamp before creating more work. Protect updates against competing writers, and make every retry re-evaluate whether the desired operation still applies. Progress requires eventual useful reads and successful effects, not merely an infinite loop.

The optimization lab supplies a small fixed-point analogy. Reset, set step size η=0.5 and initial position x₀=2, predict x₁=1, then Step once. A second Step gives 0.5. Its exact gradient-descent recurrence moves toward zero under this step size; it does not reconcile API objects, count workers, or handle external failures. Changing controls preserves elapsed time, so Reset before a fresh experiment. Use the analogy to ask whether repeated application approaches the intended state, then test the actual controller’s invariants separately.

Bounded scale-up after a duplicate notification

A teaching controller desires five workers and observes two valid owned workers. It creates at most two per successful pass. Assume no other changes and that created workers become visible before the next pass.

  1. The first pass computes deficit 5−2=3 and creates min(2,3)=2 workers. Observation then reaches four existing workers, without yet claiming those workers are application-ready.

  2. The second pass recomputes the deficit from current observation: 5−4=1. It creates one, bringing existence count to five. It does not replay the first pass’s two-create action.

  3. A duplicate notification triggers a third pass with deficit zero, so no create occurs. If the observation were stale, identity checks and safe create semantics would still be needed; the simple count recurrence assumes that uncertainty away.

The result

Two productive passes create three workers; subsequent unchanged passes are no-ops for provisioning. Readiness still requires its own observed condition.

Explore the model

Interactive lab

Interactive lab · optimization

A repeatable transition approaches a fixed point

Reset, set η=0.5 and x₀=2, predict x₁=1, then Step; another Step gives 0.5. This exact descent on x²/2 is only a convergence analogy, not a Kubernetes controller, provisioning loop, or failure simulator.

A repeatable transition approaches a fixed pointGradient descent on f(x) = x²/2: xₙ = (1 − η)ⁿx₀. Playback advances two discrete iterations per second. Values outside |x| ≤ 4 are explicitly reported. Current readings: Position x0: 2; Objective x²/2: 2; Iteration behavior: Stable step. All parameters are adjustable with the labeled controls below.−4−202402.174.356.528.7Parameter xf(x) = x²/2n = 0
ObjectiveDiscrete iterate
Position x0
2
Objective x²/2
2
Iteration behavior
Stable step
n = 0 · t = 0 s

Gradient descent on f(x) = x²/2: xₙ = (1 − η)ⁿx₀. Playback advances two discrete iterations per second. Values outside |x| ≤ 4 are explicitly reported. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forA fixed-point argument must name the transition and its assumptions before being applied to a real controller.

Check your understanding

A create call times out and might already have succeeded remotely. What is the safest next design step?

Try it yourself

A fixed desired count is eight, observation starts at three, and a toy controller adds at most two successful workers per pass. How many productive passes are needed and what are the counts?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Recompute the remaining deficit after each pass; the final pass may add fewer than two.

Compare with the solution

The counts progress 3→5→7→8, requiring three productive passes and five creations. A later unchanged pass creates none. This assumes successful immediate observation and no deletions or new intent; real readiness and stale-read handling require additional checks.

Take it with you

  • Reconcile present state and intent instead of replaying event commands.
  • Stable identities and supported idempotency make uncertain effects recoverable.
  • Converged provisioning and truthful readiness are distinct invariants.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.