Git, CI/CD & GitOpsLXXIV · ReconciliationReliability
Failure modes in reconciliation — what stops the loop, and how to detect it
What you'll learn
- Identify the failure modes that stop a reconciliation tick and where each one occurs
- Read controller logs and status subresources to diagnose which phase is failing
- Apply targeted remediation per phase rather than generic restarts
- Distinguish a healthy loop from a stuck loop using observable signals
Prerequisites
Practice
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The reconciliation loop fails in distinct ways. Each failure maps to one of the four phases - read, observe, diff, apply - and each phase has a distinct signal and a distinct remediation. The production discipline is to diagnose by phase rather than restart the controller and hope.
flowchart LR
R["Read"] -->|"fails"| RF["Credential or source error"]
O["Observe"] -->|"fails"| OF["RBAC or API-server error"]
D["Diff"] -->|"fails"| DF["Render or normalisation error"]
A["Apply"] -->|"fails"| AF["Validation or admission error"]
RF --> RMD["Inspect creds and source"]
OF --> RDO["Inspect RBAC and API server"]
DF --> DFR["Re-render and compare"]
AF --> APF["Inspect resource and admission"]
The diagram shows the four phases and the failure mode each one produces. The right column shows the remediation category. A generic “restart the controller” is not on the list; it is sometimes necessary, but it is not a diagnosis.
Read failures
A read failure is the controller’s inability to pull the desired state from its source. The source could be Git, a Helm repository, an OCI registry, or an S3 bucket. Symptoms:
- The status subresource reports
Last Attempted Revision: <none>orLast Applied Revision: <none>. - The controller logs show errors mentioning
git fetch,authentication,repository not found,permission denied, orrate limit. - The Application is in
UnknownorProgressingstate for an extended period.
Common causes:
- Expired credentials. The deploy key or token used by the controller has rotated or expired. The source controller fetches successfully but receives a 401.
- Network reachability. The cluster cannot reach the Git server. This is common in air-gapped or restricted-egress clusters.
- Repository moved or deleted. The URL no longer resolves.
- Rate limits. The Git provider’s API rate limit has been exhausted.
Remediation: rotate the credentials, fix the network policy, update the URL, or wait for the rate limit to reset. The controller does not recover on its own in any of these cases - the operator must intervene.
Observe failures
An observe failure is the controller’s inability to query the cluster API for the live state. Symptoms:
- The controller logs show errors mentioning
forbidden,unauthorized,connection refused,timeout, orconnection reset. - The status subresource shows a recent revision but the inventory is stale or empty.
- The Application is in
ProgressingorUnknownwith no progress on the diff.
Common causes:
- RBAC denials. The controller’s ServiceAccount does not have
get/list/watchon the resource kinds the application owns. This is the most common cause. - API-server reachability. The controller cannot reach the API server; this is rare in-cluster but common if the controller is on a different cluster.
- CRDs missing. The controller is observing a custom resource type whose CRD has been deleted or not installed. The API server returns a “no matches for kind” error.
Remediation: fix the RoleBinding, restore the API-server connectivity, or re-install the CRD. A controller that has RBAC denials on one resource kind will still reconcile other resources; the failure is per-resource, not cluster-wide.
Diff failures
A diff failure is the controller’s inability to compute a structured comparison. This is rarer than read or observe failures because the diff is local computation. Symptoms:
- The controller logs show errors mentioning
unmarshaling,parsing,kustomize,helm template, orschema. - The status subresource shows the source revision loaded correctly but no inventory.
- The Application is in
Progressingwith no apply attempt.
Common causes:
- Render errors. A Helm chart or Kustomize overlay produces invalid YAML. The render step fails before the diff can run.
- Schema errors. A custom resource manifest does not match the CRD’s schema. The Kubernetes API rejects the apply during validation, but the diff engine can fail earlier.
- Unsupported fields. A field type the controller’s diff engine does not know how to compare (rare in modern controllers).
Remediation: re-render the manifests locally with the same toolchain version, fix the schema violation, or update the controller. A render failure points to a manifest problem, not a controller problem.
Apply failures
An apply failure is the controller’s ability to write to the cluster but its inability to land the changes. Symptoms:
- The status subresource shows
Last Attempted Revision: <new>andLast Applied Revision: <old>- the controller tried to apply the new revision but fell back. - The controller logs show errors mentioning
validation,admission webhook,invalid, orconflict. - The Application is in
OutOfSynceven after multiple ticks.
Common causes:
- Validation. A required field is missing or has an invalid value. The API server rejects the apply.
- Admission webhooks. A validating or mutating webhook rejects the apply. Common with policy controllers (OPA, Kyverno, Datadog).
- Resource conflicts. Another actor modified the resource between the controller’s observe and apply phases; the controller retries but keeps losing the conflict.
- Quota or limit-range exhaustion. The cluster has reached a quota on the resource type.
Remediation: fix the manifest, update the admission policy, resolve the resource conflict (the controller usually retries on its own), or request a quota increase.
Detecting a stuck loop
A stuck loop is one where the controller is alive but not making progress. The signals:
- No successful reconcile in N intervals. The metric “last successful reconcile timestamp” should be recent. If it drifts older than the interval, the loop is stuck.
- Status subresource frozen on one revision. The
Last Applied Revisionis unchanged across many ticks. - Health endpoint unresponsive. The controller’s
/healthzendpoint times out. - Controller pod restarts. The pod’s restart count is climbing. The controller is crashing during the tick.
A stuck loop is distinct from a broken loop. A broken loop reports errors; a stuck loop reports silence. The remediation differs: a broken loop wants a phase diagnosis; a stuck loop wants a controller restart or replacement.
Operational levers when the loop is broken
Two levers are worth knowing:
argocd app sync "$APP_NAME" --prune --self-heal
Argo CD CLI for forcing a sync with pruning and self-healing. This is the right tool when the controller’s view of the diff is stale but the controller is otherwise healthy.
flux reconcile kustomization "$KS_NAME"
Flux CLI for forcing a fresh tick on a Kustomization. This is the right tool when the kustomize-controller has not picked up the source controller’s latest artifact.
Neither command fixes the underlying problem if the loop is genuinely broken; they only force a tick. The remediation for a genuinely broken loop is to read the controller logs, identify the failing phase, and fix the root cause.
Production discipline
- Diagnose by phase, not by symptom. A controller that reports “OutOfSync” could be failing on read, observe, diff, or apply. The remediation is different per phase. Phase diagnosis is faster than generic restart loops.
- Watch the four metrics. Read success rate, observe success rate, diff success rate, apply success rate. Each one is a separate metric in modern controllers. A spike in any one of them is the early signal of a phase-specific problem.
- Treat a stuck loop as an incident. A controller that is silent for more than two intervals is not “fine”. It is either failing in a way that does not surface errors, or it is dead. Either case warrants investigation.
Cross-course references
- Kubernetes for Production Sysadmins - Parts on RBAC debugging cover the most common observe-phase failure.
- Terraform for Production Sysadmins - Parts on state lock contention cover the apply-phase analogue.
- Ansible for Production Sysadmins - Parts on playbook failure modes cover the equivalent in configuration management.
Quiz
Knowledge check · 4 questions
Q1. A controller's logs show 'forbidden: cannot get resource deployments.apps in namespace prod'. Which phase is failing?
Q2. A controller whose last successful reconcile timestamp is older than two reconciliation intervals is healthy as long as the controller pod is running.
Q3. Name the four phases of a reconciliation tick and the symptom that indicates each one is failing.
Q4. Diagnose the failing phase and propose a remediation.
Team Q runs Argo CD with 30 Applications. Three Applications have been 'Progressing' for 90 minutes - longer than several reconcile intervals. `argocd app get` on each one shows 'Last Attempted Revision: none' and 'Message: failed to fetch source: authentication required'. The other 27 Applications are 'Synced' or 'OutOfSync' with normal messages.
Passing score: 75%. Answers are checked in this browser.