Argo CD GitOps: From Git to the Cluster
Reconcile Ziba’s Kubernetes application from an auditable Git revision while keeping Terraform responsible for the underlying infrastructure it owns.
The enterprise problem and today’s slice
Enterprise problem: Ziba’s GKE infrastructure can be reproducible while the live Deployment still drifts after emergency kubectl edits, and a push-based pipeline with broad cluster credentials can deploy an unreviewed image or silently leave production different from Git. Whole-course context: Terraform mapped reviewed configuration and state to GCP resources; today consumes an existing reachable cluster and moves Kubernetes application delivery into a continuous GitOps reconciliation loop. Today’s slice: Argo CD compares Git target state with Kubernetes live state, reports drift, and optionally syncs Ziba under bounded policy; Terraform continues to own the VPC, GKE cluster, node pools, and Argo CD installation boundary. End-of-day evidence: A reviewer sees commit-to-sync provenance, health, served image digest, a detected drift, a denied unauthorized change, and recovery. Still unsolved: Multi-cluster promotion, secret delivery, disaster recovery, progressive traffic shifting, and production auto-sync approval remain environment decisions.
Customer use cases
GitOps is not merely deploying from Git; it must continuously expose whether the approved revision and live application differ. These use cases cover intended release and out-of-band drift recovery.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D65-UC-01 | Ziba release engineer | Promote a reviewed Ziba manifest revision to GKE | Argo CD reports Synced and Healthy, and the public blog serves the pinned image digest | Invalid manifest or unauthorised repository revision is rejected while the previous healthy revision remains served |
| D65-UC-02 | Ziba operations lead | Detect and recover a manual live-cluster change | Argo CD reports OutOfSync, attributes the diff, and restores or adopts it through reviewed Git | Unauthorised actor cannot mutate protected resources; drift and unaffected positive-control request remain visible |
Actor-centred user stories
Sync status alone can be green while an application is unhealthy, so acceptance must include both configuration convergence and customer behavior. These stories retain positive and negative evidence across Git, Argo CD, Kubernetes, and the request path.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D65-US-01 | D65-UC-01 | As a Ziba release engineer, I want Argo CD to reconcile a pinned Git revision, so that production manifests are traceable without giving CI direct cluster mutation credentials | Commit, repository/path, Application revision, sync result, health, Kubernetes rollout, image digest, HTTP result, environment, timestamp, and operation ID are captured |
| D65-US-02 | D65-UC-02 | As a Ziba operations lead, I want drift detected before it is corrected, so that emergency changes are visible and the chosen recovery is reviewable | Live diff is recorded, unauthorized mutation is denied, self-heal or Git adoption is approved, and the customer positive control remains healthy |
End-to-end product flows
Continuous reconciliation can also continuously apply a bad decision, so the flow separates review, comparison, policy, sync, and health. The recovery path preserves drift evidence before correcting it.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D65-FLOW-01 | D65-UC-01 | Happy | Release engineer merges a reviewed manifest change | 1. CI validates and signs/pins the artifact reference. 2. Git stores the reviewed target revision. 3. Argo CD repository server renders manifests. 4. Application controller compares target and live state. 5. Policy permits sync. 6. Kubernetes controllers roll out Ziba. 7. Argo CD observes health and the release probe reads the served digest. | Actor, repo/path, commit, target/live diff, cluster/namespace, sync operation ID, resource UIDs, image digest, health, HTTP result, environment, and time |
| D65-FLOW-02 | D65-UC-02 | Recovery | A manual replica or image edit changes live state | 1. Kubernetes authorisation denies an unapproved actor or records an approved emergency actor. 2. Argo CD refresh detects OutOfSync. 3. Preserve the diff and audit event. 4. Decide self-heal to Git or adopt through a reviewed commit. 5. Sync the chosen target. 6. Observe Synced, Healthy, and customer response. | Denial/audit identity, drift diff, recovery decision, commit, sync operation ID, final resource values, and unaffected request trace |
System design derived from the flows
Argo CD owns application reconciliation, not the servers beneath the cluster. Its repository server renders manifests, while the application controller continuously compares live Kubernetes resources with the target in Git and can take corrective action (Argo CD architecture).
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D65-UC-01 | Reviewed manifest merge and Argo CD Application | Git host, repository server, application controller, Argo CD policy/RBAC, Kubernetes API/controllers, Ziba health probe | Git commit for target manifests; Kubernetes API for live state; Argo CD Application status/history for comparison and operations | Render error, invalid revision, policy denial, sync failure, degraded health, rollout timeout, wrong image digest, or failed HTTP probe |
| D65-UC-02 | Drift alert and recovery decision | Kubernetes admission/RBAC/audit, Argo CD refresh/diff, application controller, Git review, evidence store | Kubernetes API/audit for changed live state; Git for chosen target; Argo CD status/history and immutable incident evidence | Unauthorized mutation, ignored material diff, self-heal disabled, repeated drift, prune hazard, or customer regression |
Argo CD defines target state as the application state represented in Git, live state as the deployed resources, refresh as comparison, sync as moving live resources toward target, and health as whether the application is operating (Argo CD core concepts). These are related but distinct signals: Synced does not necessarily mean Healthy.
Data model and ownership
Git, Argo CD status, and Kubernetes live objects cannot be one authority because each records a different part of the control loop. Ziba’s content database, if any, remains outside declarative manifest reconciliation and needs its own backup and migration lifecycle.
Generated-application database: Not created in this slice — Git revisions, Argo CD Application state/history, Kubernetes resources, audit events, and HTTP probes provide the durable deployment evidence required here.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| GitTargetRevision | Git host, owned by Ziba release engineering | Commit SHA plus repository path | Opaque artifact digest and environment configuration references | Environment/repository ID | Immutable reviewed commit; manifest references pinned; no plaintext secret material | Proposed, merged/promoted, reverted by new commit, retained by repository policy | D65-UC-01, D65-UC-02 |
| ArgoApplication | Kubernetes API in Argo CD control namespace, owned by platform GitOps team | Application UID | Repository URL, target revision/path, destination cluster and namespace | Argo CD project | Source and destination must satisfy project allowlists and sync policy | Declaratively created, updated, disabled, or deleted; finalizers/prune policy reviewed before resource deletion | D65-UC-01, D65-UC-02 |
| LiveWorkloadResource | Destination Kubernetes API, owned by Kubernetes controllers and Ziba team | Resource UID | Argo CD tracking reference and image digest | Namespace | Live spec should match rendered target except documented ignored differences; readiness controls traffic | Created/updated by sync, rolled back via Git, pruned only under explicit policy, deleted with app retirement | D65-UC-01, D65-UC-02 |
| SyncAndDriftEvidence | Argo CD history, Kubernetes audit, and immutable CI/incident store | Operation or incident ID | Commit, Application UID, resource UID, audit event, request trace | Environment | Comparison, action, actor, observed result, and customer proof refer to one environment and revision | Appended per operation/drift, retained immutably, expired under audit policy | D65-UC-01, D65-UC-02 |
Expand reconciliation one block at a time
The phrase “Git is the source of truth” is incomplete unless a controller repeatedly compares it with live state and reports what happened. Expand the three boxes into render, compare, policy, apply, observe, and repeat.
Automatic sync can act when target and live state differ, but pruning and self-healing are separate choices; automatic pruning is off by default, and self-heal must be explicitly enabled (Argo CD automated sync policy). Treat each as a deletion or mutation authority decision, not a convenience checkbox.
Declare the Ziba Application and workload
A GitOps controller cannot infer ownership from a directory name, so the Application must bind source, destination, and policy explicitly. Keep the workload manifest in the referenced path and pin production images by digest.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ziba-prod
namespace: argocd
spec:
project: ziba-production
source:
repoURL: https://github.com/example/ziba-deployments.git
targetRevision: main
path: environments/production
destination:
server: https://kubernetes.default.svc
namespace: blog
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
syncOptions:
- CreateNamespace=false
apiVersion: apps/v1
kind: Deployment
metadata:
name: ziba
namespace: blog
spec:
replicas: 2
selector:
matchLabels:
app: ziba
template:
metadata:
labels:
app: ziba
spec:
containers:
- name: blog
image: europe-west2-docker.pkg.dev/ziba-prod/blog/site@sha256:REPLACE_ME
readinessProbe:
httpGet:
path: /healthz
port: 3000
Terraform and Argo CD complement each other
Treating Terraform and Argo CD as substitutes creates overlapping ownership and control loops. Give each resource exactly one primary reconciler, and connect them through explicit outputs and bootstrap dependencies.
| Layer | Primary owner | Typical resources | Why not the other tool by default? |
|---|---|---|---|
| Cloud foundation | Terraform | Project services, VPC/subnets, GKE cluster, node pools, IAM, DNS foundation | Argo CD needs a running Kubernetes API and should not implicitly own all provider lifecycle/state |
| GitOps control plane bootstrap | Terraform or a dedicated platform bootstrap | Argo CD installation, initial project/repository/cluster access | Argo CD cannot reconcile itself before a minimal controller exists; ownership must avoid a bootstrap loop |
| Kubernetes application desired state | Argo CD | Namespace-scoped Deployments, Services, ConfigMaps, policy-approved CRDs | Terraform apply is episodic and does not provide Argo CD’s continuous application comparison/health model |
| Application/domain data | Application and data platform | Posts, users, sessions, migrations, backups | Neither an HCL state file nor Git manifests should become the application database |
Terraform changes infrastructure through plans and stateful provider mappings; Argo CD continuously reconciles rendered Kubernetes application state. If both manage the same Deployment, each can undo the other, destroy provenance, and create perpetual drift.
Code versus actual effects
A Git commit changes no running bytes until reconciliation is authorised and Kubernetes controllers act. This table separates source, control-plane, software, hardware, and customer consequences.
| Code or action | Git/Argo control state | Kubernetes software effect | Hardware effect | Ziba deployment outcome |
|---|---|---|---|---|
| Merge new image digest | Target revision changes | No live pod changes before sync | No immediate CPU/RAM allocation | Release is reviewable but not yet served |
| Argo refresh | Re-renders and compares target/live | Reads Kubernetes API; reports Synced/OutOfSync | No intended hardware mutation | Detects drift; does not prove health |
| Argo sync Deployment | Records operation and applies desired spec | Deployment creates a ReplicaSet; scheduler/kubelet start replacement pods | Containers consume existing node CPU/RAM; Pending pods may trigger separate node scaling | New digest serves only after readiness and Service endpoints update |
selfHeal: true | Authorises corrective sync for live drift | Manual spec edits can be overwritten by target state | May restart/reschedule pods depending on changed field | Restores reviewed target but can erase an emergency workaround unless adopted in Git |
prune: true | Authorises deletion of resources absent from target | Kubernetes objects can be deleted during sync | Associated cloud load balancers or volumes may be released by their controllers | Can retire stale objects or cause outage/data loss if ownership is wrong |
| Scale replicas from 2 to 4 | Target/live replica count changes | Scheduler creates two additional pods | Adds per-pod CPU/RAM demand but does not add nodes itself | Increases parallel capacity only if nodes can fit the pods |
Prove sync, drift, and recovery
A screenshot of a green Argo CD application cannot prove the served revision or the negative path. Capture machine-readable status, Kubernetes rollout, customer response, then introduce a safe staging drift and preserve the OutOfSync diff before recovery.
argocd app get ziba-prod -o json
argocd app diff ziba-prod
argocd app sync ziba-prod
argocd app wait ziba-prod --sync --health --timeout 300
kubectl -n blog rollout status deployment/ziba
kubectl -n blog get deployment ziba -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
curl --fail --show-error "${ZIBA_URL}/healthz"
For the staging-only drift exercise, change a harmless annotation with an authorised emergency identity, capture the audit event and OutOfSync diff, then choose self-heal or a reviewed Git adoption. Also prove that an unapproved identity receives a Kubernetes RBAC denial. Never induce production drift merely to satisfy a course exercise.
Failure modes and decision rules
An OutOfSync status is a comparison result, not a diagnosis, and Healthy is not proof that the intended revision is live. Use both signals plus rollout and request evidence.
| Evidence | Interpretation | Decision |
|---|---|---|
Unknown or render error | Target manifests could not be obtained or generated | Fix repository access/revision/rendering before any sync |
| OutOfSync after manual edit | Live state differs from rendered target | Preserve diff; self-heal if Git is correct, or adopt through review if emergency state is correct |
| Synced but Degraded | Desired spec applied, workload health failed | Inspect rollout, readiness, events, logs, resources, and dependencies; do not call release complete |
| Repeated immediate drift | Another controller or defaulting webhook owns/mutates the field | Establish one owner or configure a narrowly justified diff rule |
| Prune proposes persistent resource deletion | Target removed an object with durable consequences | Stop, export/backup, confirm ownership and retention, then approve explicit retirement |
Decision rule: use Argo CD for continuous Kubernetes application reconciliation when Git review and drift visibility are desired; keep infrastructure in Terraform where stateful provider lifecycle is required. Never assign the same field to two reconcilers.
Key takeaways
GitOps closes a control loop between reviewed target state and live Kubernetes objects. Argo CD supplies that application loop; it does not replace the infrastructure, identity, data, or customer-health layers around it.
- Git records target state; Argo CD renders, compares, syncs, and observes; Kubernetes controllers run the workload.
- Synced and Healthy answer different questions, and neither alone proves the customer response.
- Auto-sync, self-heal, and prune grant different mutation/deletion authorities.
- Terraform and Argo CD complement each other when their resource ownership is disjoint.
- Drift must produce evidence and a decision, not silent correction without attribution.
Checklist
A GitOps release is complete only when provenance, convergence, health, and denial are all observable. Retain the evidence under the same release identifier.
- [ ] Application binds an allowlisted repository/path/revision to an allowlisted cluster/namespace
- [ ] Production image is digest-pinned and manifests pass pre-merge validation
- [ ] Auto-sync, self-heal, prune, and allow-empty are separately reviewed
- [ ] Argo CD and Terraform ownership tables contain no overlapping resource fields
- [ ] Captured commit, diff, operation ID, resource UIDs, health, image digest, HTTP result, time, and environment
- [ ] Proved safe staging drift recovery and unauthorised Kubernetes mutation denial