Overview
GitOps is an operating model, not a specific tool: the desired state of a system is declared in a Git repository, and an automated agent continuously reconciles the live system toward whatever that repository currently describes. The practical difference from "we deploy via CI" is the direction of control, a CI pipeline pushes changes out on trigger, while a GitOps agent pulls from Git on a loop and keeps enforcing it, catching and correcting drift even if nobody touched the pipeline. That single property, a continuously enforced, Git-sourced desired state, is what gives GitOps its rollback, audit, and drift-detection properties essentially for free.
This is the same desired-state control loop introduced in Kubernetes Fundamentals, with Git promoted to the durable source of truth. Kubernetes controllers reconcile API objects to running resources; the GitOps controller first reconciles the API objects to the reviewed manifests in Git.
Quick Reference
| Concept | What it means |
|---|---|
| Source of truth | Git repository, not the live cluster |
| Reconciliation | Pull-based, continuous, an in-cluster agent compares and corrects drift |
| Rollback | A Git revert, using the same mechanism as any other change |
| Audit trail | Git commit history, author, timestamp, review, diff |
| Drift | Any live-state divergence from Git is automatically detected and (optionally) corrected |
Syntax
# Argo CD Application - points a cluster at a Git path and keeps it in sync
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api
spec:
source:
repoURL: 'https://github.com/org/infra-config'
path: apps/api
targetRevision: main
destination:
server: 'https://kubernetes.default.svc'
namespace: api
syncPolicy:
automated:
selfHeal: true
prune: trueExamples
# A rollback is just a Git revert - no separate rollback tooling
git revert HEAD
git push origin main
# The GitOps agent detects the new commit and reconciles the cluster
# back to the previous declared state automatically.selfHeal vs manual sync
selfHeal: true makes the agent correct drift automatically, even without a new commit, someone
manually editing a live resource gets silently reverted. Some teams prefer manual sync approval
for production, trading that safety net for an explicit human confirmation step.
Troubleshooting Reconciliation
I do not start by forcing another sync. First identify which comparison failed: Git revision to rendered manifests, rendered manifests to live objects, or live objects to healthy workloads.
Step 1: Confirm the source revision
I verify the repository URL, path, target revision, and last successfully reconciled commit. A healthy controller cannot apply a commit it cannot fetch, a path that renders no manifests, or credentials that expired.
Step 2: Separate sync status from workload health
Synced means the live object definitions match the rendered desired state; it does not mean the
application is serving traffic. OutOfSync means a manifest difference exists; it does not prove
the workload is broken. If objects match but Pods are unhealthy, move to the Pod, Service, probe,
and resource workflow in Kubernetes Fundamentals.
Step 3: Read controller conditions and Kubernetes Events
I inspect the GitOps Application object, controller logs, generated manifests, and Events. Look for repository authentication, rendering, admission, immutable-field, ownership, or ordering errors before changing the cluster.
kubectl -n argocd get applications.argoproj.io <app> -o yaml
kubectl -n argocd get pods
kubectl -n argocd logs deployment/argocd-application-controller
kubectl get events -n <workload-namespace> \
--sort-by=.metadata.creationTimestampStep 4: Fix the source, not the symptom
If a manual live edit caused drift, either revert it or commit the intended change to Git. If a
bad commit caused the failure, revert that commit. Repeated kubectl edit or forced sync actions
hide the evidence and create a contest between the operator and controller.
Practice the failure boundaries
The production AKS GitOps project follows the full path from Terraform and Argo CD into Kubernetes rollout and application troubleshooting, showing where infrastructure, reconciliation, and runtime failures differ.
Visual Diagram
Common Mistakes
- Confusing "we deploy from a CI pipeline triggered by Git" with GitOps, without a continuous, pull-based reconciliation loop, there's no drift detection or self-healing, just a scripted push.
- Granting engineers standing
kubectl applyaccess to production alongside a GitOps agent, any manual change either gets silently reverted (confusing) or causes permanent drift (defeats the point). - Storing secrets in plaintext in the Git repository that GitOps reconciles from, the repository needs the same access control as production credentials, or secrets need to be kept out of it entirely (sealed secrets, external secret managers).
- Treating the GitOps repository structure as an afterthought, a disorganized repo with unclear environment boundaries makes "what's actually running in production" as hard to answer as it was before GitOps.
- Treating
Syncedas equivalent to healthy, when the applied manifest can still create failing Pods or an unreachable Service. - Forcing repeated syncs without reading controller conditions and cluster Events, reproducing the same deterministic failure while discarding time and context.
Performance
- Reconciliation interval (how often the agent polls or reacts to Git changes) trades responsiveness for load on the Git provider and cluster API, very tight intervals on a large cluster with many Applications can add real API server pressure.
- Large repositories with many resources per sync can make individual reconciliation passes slow; splitting by application or environment (multiple smaller Application/Kustomization objects) keeps each sync fast and isolates blast radius.
- Self-healing on every drift detection adds reconciliation overhead but is usually worth it; the alternative is silent, undetected drift accumulating over time.
Best Practices
- I keep application config and infrastructure config in separate repositories or clearly separated paths, since they change at different rates and often need different review policies.
- Require pull-request review on the GitOps repository for anything touching production; this is the actual audit/approval mechanism, so it needs to be enforced, not optional.
- I keep secrets out of the plain Git repository (sealed secrets, external secret operators) even though everything else lives there.
- I start with manual sync approval for production and move to automated self-heal only once the team trusts the pipeline and monitoring around it.
- Alert separately on reconciliation failures, drift, and workload health so each page starts at the layer that actually failed.
- Include the Git revision in deployment annotations and telemetry so an incident can be tied back to the exact reviewed change quickly.