02 · GitOps Patterns (ArgoCD/Flux Concepts)¶
Not run against a live cluster
Manifests and CLI output below follow documented ArgoCD/Flux behavior; not executed against a live cluster in this session.
The core idea: Git as the single source of truth¶
GitOps flips the normal deployment flow. Instead of a CI pipeline running
kubectl apply against a cluster (a push model), a controller running
inside the cluster continuously watches a Git repository and reconciles
the live cluster state to match what's declared there (a pull model).
Push model: CI pipeline --kubectl apply--> Cluster
Pull model: Git repo <--watches-- GitOps controller (in-cluster) --reconciles--> Cluster
The distinction matters operationally: in the pull model, nothing outside
the cluster needs write credentials to it — the controller already runs
with a service account inside the cluster, and CI only needs push access to
Git. Drift (someone running kubectl edit by hand) gets corrected
automatically on the next reconcile loop instead of silently persisting.
ArgoCD: the Application CRD¶
ArgoCD models each deployed thing as an Application custom resource that
points at a Git path and a destination cluster/namespace:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/payments-api-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: payments
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: true # revert manual cluster edits back to Git state
syncOptions:
- CreateNamespace=true
kubectl apply -f payments-api-application.yaml -n argocd
argocd app get payments-api
# Health Status: Healthy
# Sync Status: Synced
argocd app sync payments-api # manual sync if automated is off
prune: true and selfHeal: true together are what make ArgoCD
enforce Git as the only legitimate source of change — without them,
ArgoCD only reports drift ("OutOfSync") rather than correcting it.
Flux: reconciliation via Kustomization/HelmRelease¶
Flux (v2, "Flux CD") is built as a set of composable CRD-driven
controllers rather than one Application object. A GitRepository source
defines what to watch; a Kustomization defines what to apply from it:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: payments-api
namespace: flux-system
spec:
interval: 1m
url: https://github.com/example/payments-api-manifests.git
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: payments-api
namespace: flux-system
spec:
interval: 5m
path: "./overlays/production"
prune: true
sourceRef:
kind: GitRepository
name: payments-api
flux get sources git
flux get kustomizations
# NAME READY MESSAGE
# payments-api True Applied revision: main@sha1:8f2a1c
The GitRepository/Kustomization split lets one Git source feed
multiple Kustomizations (e.g. staging and production overlays pointed at
different paths of the same repo) — a composition ArgoCD achieves with
separate Application objects instead.
Worked example: promoting a change through environments¶
A typical GitOps repo layout separates environment overlays (Kustomize) so promotion is a Git operation, not a pipeline deploy step:
manifests/
base/
deployment.yaml
service.yaml
overlays/
staging/kustomization.yaml (replicas: 2, image tag: staging-latest)
production/kustomization.yaml (replicas: 6, image tag: v1.4.2)
# CI, on merge to main, updates only the image tag via a tool like `kustomize edit`:
cd manifests/overlays/production
kustomize edit set image payments-api=registry.example.com/payments-api:v1.4.3
git commit -am "bump payments-api to v1.4.3" && git push
# ArgoCD/Flux notices the new commit within its poll interval (or via webhook)
# and reconciles the live Deployment's image field to match — no kubectl needed.
argocd app diff payments-api # confirms what changed before/if manual sync
Promoting to production is then just merging the same commit (or a PR)
into the production overlay's branch/path — the full deployment history is
the Git log, and rollback is git revert plus a reconcile.
How It Actually Works¶
- The reconciliation loop is a standard Kubernetes controller pattern,
not something GitOps-specific. ArgoCD's
application-controllerand Flux'skustomize-controllerare both just controllers watching their own CRDs (Application,Kustomization) the same way the built-inkube-controller-managerwatches Deployments — a control loop that computes desired state (rendered manifests from Git) vs. observed state (live cluster objects via the API server) and callsApply/Patchto close the gap, repeating on a timer (interval) and on webhook-triggered wakeups. - Drift detection relies on comparing live object state to a
re-rendered manifest, field by field, not on a checksum of the whole
object. Both tools compute a structured diff so that fields Kubernetes
itself sets (
status,resourceVersion, defaulted fields added by admission webhooks) are excluded — this is why "OutOfSync" specifically means a field you declared in Git differs from the live cluster, not that any byte of the object changed. selfHeal/pruning re-triggers on watch events, not only on the poll timer. ArgoCD additionally watches the destination cluster's resources (via its own informer against the target API server) so a manualkubectl editis detected and reverted almost immediately, independent of the Git-repo poll interval — this is the mechanism that actually enforces "no manual changes survive," not just periodic re-sync.- Multi-tenancy in ArgoCD is enforced via the
AppProjectCRD, which is a policy object the application-controller consults before every sync, not a Kubernetes RBAC construct.AppProjectrestricts which Git repos, destination clusters/namespaces, and resource kinds anApplicationin that project may reference; this lets one shared ArgoCD instance safely serve many teams without giving each team cluster-wide RBAC — the enforcement point is inside ArgoCD's own reconcile logic, in addition to (not instead of) the service account's real Kubernetes RBAC.
Exercise¶
Install ArgoCD (or Flux) into a local kind/minikube cluster, point it at a
public Git repo containing a simple Deployment manifest, and confirm it
syncs. Then run kubectl scale or kubectl edit directly against the
resulting Deployment to introduce drift, and observe (via argocd app get
or flux get kustomizations) how quickly and by what mechanism the
controller reverts your manual change back to the Git-declared state.