Git, CI/CD & GitOpsLXXXVI · GitOps Failure ModesDetection
Controller unavailable — the GitOps control plane is down
What you'll learn
- Identify the symptoms of a missing or unhealthy GitOps controller
- Distinguish a controller crash from a controller that is running but not progressing
- Explain why controller downtime is silent at the application level and loud at the audit level
- Apply the minimum set of alerts that catch controller downtime within one reconciliation interval
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 first two failure modes are visible at the application level:
the application shows Degraded or OutOfSync, the controller
log carries an error message, and the operator has a clear
starting point. The third failure mode is invisible at the
application level: when the controller itself is not running,
every application reports the last status it observed before the
controller stopped - almost always Synced and Healthy. The
cluster drifts in silence, and the operator’s only signal is the
absence of activity.
The silent failure
A controller that is not running leaves no application-level
symptom. argocd app get returns Synced and Healthy because
the API server is serving the last cached state. The
applications are not actually healthy - the controller is not
checking them - but the API surface does not distinguish “last
checked at 14:00 and was healthy” from “checked continuously”.
flowchart LR
A[Application status] --> B{Controller running?}
B -- yes --> C[Live reconciliation]
B -- no --> D["Cached state from last check"]
D --> E["Looks healthy - lies"]
C --> F["Reports actual state"]
The diagnostic move is to read the controller, not the applications:
kubectl -n argocd get pods
A missing or CrashLoopBackOff controller is the obvious case.
A controller that is Running but not progressing is the
harder case, and it requires reading the controller log:
kubectl -n argocd logs statefulset/argocd-application-controller
A controller that is hung on a watch or stuck in a backoff will show no recent entries - the absence of log lines is the symptom.
The three shapes
There are three distinct shapes of controller failure:
- Pod missing or
CrashLoopBackOff. Visible inkubectl get pods. Read the pod’s previous logs (--previous) to find the crash reason. Common causes: OOMKill, a misconfigured RBAC role, a missing CRD. - Pod
Runningbut no progress. Visible only by the absence of log entries or by the absence of recentSyncevents onApplicationresources. Common causes: a deadlock against the API server, a hung Git fetch. - HA shard mismatch. In high-availability mode the
controller shards managed clusters across its StatefulSet
replicas, driven by the
ARGOCD_CONTROLLER_REPLICASenvironment variable. Whenspec.replicasand the variable disagree, some clusters are assigned to a shard that does not exist and are silently never reconciled. The diagnostic is to compare the StatefulSet’sspec.replicaswith theARGOCD_CONTROLLER_REPLICASvalue on the StatefulSet.
For Flux, the equivalent shapes are spread across the source
controller, kustomize controller, and Helm controller. The
diagnostic is kubectl get kustomizations -A and kubectl get helmreleases -A plus the relevant controller logs.
Why this failure is the audit failure
Every other failure mode produces a visible signal that an audit can reconstruct. A controller downtime produces no signal at all. The audit trail shows a gap between the last sync before the controller stopped and the first sync after it restarted, and the operator reading the audit six months later cannot tell whether the cluster was unchanged during the gap or whether it drifted silently.
The remediation is alerting that catches the gap while it is still recoverable.
Production discipline
The minimum alert set for controller downtime:
- Controller pod not running. Alert when
kube_statefulset_status_replicas_ready{statefulset="argocd-application-controller"} == 0for more than two reconciliation intervals. - Controller running but no progress. Alert when no new
Applicationsync event has been observed for more than three reconciliation intervals. The Prometheus query isincrease(argocd_app_sync_total[10m]) == 0. - HA shard mismatch. Alert when a shard shows no
reconciliation activity. The Prometheus query is
increase(argocd_app_reconcile_count[10m]) == 0per controller replica - a shard with no reconcile activity means clusters assigned to a replica that does not exist.
These three alerts cover the three shapes. They must fire within one reconciliation interval of the controller stopping.
Cross-course references
- Linux for Production Sysadmins - Parts XII (RepoSecurity) and XXXIV (ConfigMgmt) cover monitoring the configuration management daemon itself, with the same silent-failure shape.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers monitoring the Ansible controller process with the same “alert on absence, not just on error” rule.
Quiz
Knowledge check · 4 questions
Q1. The Argo CD application controller pod has been in CrashLoopBackOff for twenty minutes. What does `argocd app get <name>` report for every application during this period?
Q2. Controller downtime is detectable by reading `argocd app get` - if any application shows Degraded, the controller is down.
Q3. What are the three distinct shapes of GitOps controller failure, and which one is the hardest to detect?
Q4. Diagnose why an audit six months later cannot reconstruct what happened during a controller outage.
An Argo CD controller pod was OOMKilled during a deploy surge and was not restarted for forty minutes. The cluster ran normally during this period - no human noticed because every `argocd app get` returned Healthy. Six months later, a security audit asks why a particular ConfigMap change is in production but no commit references it.
Passing score: 75%. Answers are checked in this browser.