GitOpsGitOps
Thuộc bộ tài liệu kiến thức theo DevOps Roadmap.
Tổng quan
GitOps là mô hình vận hành cho việc triển khai hạ tầng và ứng dụng, trong đó Git là nguồn chân lý duy nhất (single source of truth) cho trạng thái mong muốn của hệ thống. Mọi thứ định nghĩa môi trường runtime — manifest Kubernetes, Helm values, Kustomize overlays, định nghĩa hạ tầng — đều nằm trong Git. Một agent tự động chạy trong cluster liên tục so sánh những gì đang thực sự chạy với những gì Git khai báo phải chạy, và reconcile phần chênh lệch. Deploy trở thành git merge; rollback trở thành git revert.
Mô hình này mang các bảo đảm của công nghệ phần mềm vào vận hành: mọi thay đổi là một pull request được review, toàn bộ lịch sử có thể audit, và môi trường có thể dựng lại từ repository bất cứ lúc nào. Vì controller pull thay đổi về thay vì pipeline push lên, credential của cluster không bao giờ phải rời khỏi cluster — một cải thiện bảo mật lớn so với deployment do CI điều khiển kiểu cũ.
GitOps xây trực tiếp trên các chủ đề trước trong roadmap: container cho bạn artifact bất biến, Kubernetes cung cấp API khai báo và reconciliation, còn GitOps khép kín vòng lặp bằng cách đưa trạng thái mong muốn vào quản lý phiên bản. Argo CD và Flux là hai triển khai thống trị, đều đã graduated trong CNCF.
Một mô hình tư duy
GitOps là vòng lặp reconciliation của Kubernetes (spec vs status), mở rộng ra để bản thân spec nằm trong Git thay vì chỉ trong etcd. Agent biến repository Git thành một kho trạng thái mong muốn thứ hai và liên tục đưa cluster về khớp với nó. Hai hệ quả trực tiếp:
- Cluster không còn là nơi tạo ra state — chỉ là nơi hội tụ về. Bất kỳ thay đổi nào tạo bên ngoài Git, theo định nghĩa, là drift mà agent sẽ báo cáo hoặc hoàn tác.
- Khôi phục trở nên tầm thường theo thiết kế. Để dựng lại một môi trường, hãy trỏ một agent mới vào repo; trạng thái mong muốn tự tái hiện. Cluster của bạn trở thành “gia súc”, còn Git trở thành sổ giống của cả đàn.
GitOps không chỉ là “Kubernetes + Git”. Cùng mẫu này áp dụng cho bất cứ thứ gì có API khai báo và một reconciler — state của Terraform, tài nguyên cloud do Crossplane quản lý, các add-on cluster — nhưng Kubernetes là nơi nó trưởng thành nhất.
Kiến thức nền tảng
Bốn nguyên tắc GitOps (OpenGitOps)
- Khai báo (Declarative) — toàn bộ trạng thái mong muốn được biểu diễn kiểu khai báo (manifest YAML, không phải script gồm các bước mệnh lệnh).
- Có phiên bản và bất biến — trạng thái mong muốn được lưu ở nơi bảo đảm tính bất biến và đầy đủ lịch sử phiên bản (Git, với commit được ký, chỉ nối thêm).
- Được pull tự động — các software agent tự động pull trạng thái mong muốn từ nguồn, thay vì con người hay pipeline push vào.
- Reconcile liên tục — agent liên tục quan sát trạng thái thực tế và cố gắng đưa nó về trạng thái mong muốn, tự sửa drift mà không cần con người can thiệp.
Mô hình deploy push vs pull
| Khía cạnh | Push (CI/CD kiểu cũ) | Pull (GitOps) |
|---|---|---|
| Ai thực hiện deploy | Pipeline CI chạy kubectl apply / helm upgrade | Agent trong cluster pull từ Git |
| Credential của cluster | Lưu trong hệ thống CI (rủi ro lộ lọt) | Không bao giờ rời cluster |
| Xử lý drift | Không có — thay đổi thủ công không ai biết | Được phát hiện và (tùy chọn) tự sửa |
| Dấu vết audit | Rải rác trong log pipeline | Đầy đủ trong lịch sử Git |
| Khôi phục / DR | Chạy lại pipeline theo thứ tự | Trỏ agent vào repo; trạng thái tự hội tụ |
| Onboard cluster mới | Cấu hình credential pipeline cho từng cluster | Cài agent, trỏ vào repo |
| Phạm vi ảnh hưởng nếu CI bị chiếm | Quyền ghi trực tiếp vào cluster prod | CI chỉ mở được PR; merge vẫn được gác |
| Chiều tác động | CI → cluster (đẩy ra nhiều cluster) | Cluster → Git (mỗi cluster tự pull phần của mình) |
Thực tế CI vẫn tồn tại trong GitOps — nó build, test và push image, rồi cập nhật image tag trong config repo (trực tiếp hoặc qua image automation controller). Phần CD hoàn toàn do GitOps agent đảm nhiệm. Sự phân chia gọn gàng là: CI tạo ra artifact và đề xuất trạng thái mong muốn; CD (agent) hiện thực hóa nó.
Cấu trúc repository
Mẫu phổ biến là tách source code ứng dụng khỏi cấu hình deployment:
app-repo/ # source code + Dockerfile; CI build và push image
config-repo/
├── apps/
│ └── web/
│ ├── base/ # manifest dùng chung (Kustomize base)
│ └── overlays/
│ ├── dev/kustomization.yaml
│ ├── staging/
│ └── prod/
└── clusters/
├── dev/
│ └── apps.yaml # Argo CD Applications / Flux Kustomizations
└── prod/
└── apps.yaml
Tách repo giúp tránh vòng lặp CI (commit bump image lại kích hoạt build), cho phép phân quyền khác nhau (developer trên app-repo, platform/SRE trên config-repo), và giữ lịch sử audit sạch sẽ. Cây clusters/ giữ các định nghĩa gốc mà agent của từng cluster trỏ vào; apps/ giữ manifest workload thực sự. Một số team thay vào đó dùng monorepo với phân quyền chặt theo path — cách nào cũng được, nhưng hãy giữ source app và config đã render tách bạch về mặt logic.
Những gì GitOps không tự giải quyết
- Bản thân nó chưa phải toàn bộ câu chuyện CD. Bạn vẫn cần CI để build/test/ký image và một thứ gì đó (image automation hoặc công cụ promotion) để chuyển version giữa các môi trường.
- Thứ tự và phụ thuộc giữa nhiều app cần sync wave / sắp thứ tự phụ thuộc, không chỉ “apply hết”.
- Migration database và tác vụ một-lần không khớp gọn với mô hình reconcile thuần túy; dùng Job với sync hook và trình tự cẩn thận.
- Secret không thể để plaintext trong Git — bắt buộc phải có một chiến lược riêng (bàn ở dưới).
Khái niệm chính
Argo CD vs Flux
| Khía cạnh | Argo CD | Flux |
|---|---|---|
| Trạng thái CNCF | Graduated | Graduated |
| UI | Web UI tích hợp mạnh (trạng thái sync, cây resource, diff trực tiếp) | Ưu tiên CLI; UI qua Weave GitOps / Headlamp / Capacitor |
| Abstraction cốt lõi | CRD Application và ApplicationSet | CRD GitRepository, Kustomization, HelmRelease, OCIRepository (GitOps Toolkit) |
| Kiến trúc | api-server, repo-server, application-controller, redis | Bộ controller chuyên biệt (source, kustomize, helm, notification, image) |
| Multi-tenancy | Projects, RBAC, SSO tích hợp sẵn | RBAC thuần Kubernetes, phạm vi namespace, cô lập tenant qua impersonation |
| Tự động cập nhật image | Dự án riêng (argocd-image-updater) | Tích hợp sẵn (image-reflector + image-automation controller) |
| Progressive delivery | Argo Rollouts (dự án anh em) | Flagger (dự án anh em) |
| Render | Helm, Kustomize, YAML thuần, jsonnet, config plugin | Helm, Kustomize, YAML thuần |
| Phù hợp với | Team cần trực quan/UI và workflow lấy app làm trung tâm | Platform team cần khối xây dựng nhẹ, kết hợp linh hoạt, thuần Kubernetes |
Cả hai đều theo dõi Git, render manifest (Helm/Kustomize/YAML thuần), diff với cluster và reconcile theo chu kỳ hoặc webhook. Chọn giữa hai chủ yếu là muốn một UI lấy app làm trung tâm có định hướng (Argo CD) hay một bộ công cụ controller kết hợp linh hoạt (Flux); nhiều tổ chức thành công với cả hai.
Chi tiết vòng lặp reconciliation
- Fetch — agent clone/pull revision đích (một branch, tag hoặc commit SHA) từ Git (Argo CD mặc định polling ~3 phút kèm webhook; Flux dùng interval theo từng source).
- Render — chạy
kustomize build/helm templateđể sinh ra manifest mong muốn cụ thể. - Diff — so sánh trạng thái mong muốn đã render với trạng thái sống của cluster (diff phía server, bỏ qua các field không nên quản lý).
- Sync — áp phần chênh lệch: tạo, cập nhật và (nếu bật prune) xóa các resource đã bị gỡ khỏi Git.
- Report — hiển thị trạng thái:
Synced/OutOfSync,Healthy/Degraded, và event cho con người và cảnh báo.
Ví dụ manifest Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-prod
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # xóa cascade các resource được quản lý khi xóa App
spec:
project: default
source:
repoURL: https://github.com/example/config-repo.git
targetRevision: main
path: apps/web/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: web
syncPolicy:
automated:
prune: true # xóa resource đã bị gỡ khỏi Git
selfHeal: true # hoàn tác thay đổi thủ công trong cluster
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
retry:
limit: 3
backoff: { duration: 5s, factor: 2, maxDuration: 3m }
Với selfHeal: true, một lệnh kubectl edit thủ công sẽ bị hoàn tác trong vài giây — cluster luôn khớp với Git. Với prune: true, xóa một manifest khỏi Git sẽ xóa resource khỏi cluster. Cả hai đều mạnh và nguy hiểm; chỉ bật cho production khi kỷ luật review và rollback của bạn đã vững.
App-of-apps và ApplicationSet
Để quản lý nhiều ứng dụng, hãy định nghĩa một Application gốc mà source path của nó chứa các manifest Application khác (mẫu app-of-apps) — bootstrap cả cluster chỉ còn là một lệnh kubectl apply của App gốc. ApplicationSet tổng quát hóa mẫu này với các generator sinh hàng loạt Application từ một template duy nhất:
- Git generator — mỗi thư mục hoặc file trong repo thành một Application (thêm một folder là có một app).
- Cluster generator — mỗi cluster đã đăng ký thành một Application (rải cùng một app ra cả fleet).
- List / matrix / pull-request generator — danh sách tường minh, tích chéo, và môi trường preview tạm thời cho mỗi PR đang mở.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: web-all-envs, namespace: argocd }
spec:
generators:
- list:
elements:
- { env: dev, cluster: https://kubernetes.default.svc }
- { env: staging, cluster: https://kubernetes.default.svc }
- { env: prod, cluster: https://kubernetes.default.svc }
template:
metadata: { name: 'web-{{env}}' }
spec:
project: default
source:
repoURL: https://github.com/example/config-repo.git
targetRevision: main
path: 'apps/web/overlays/{{env}}'
destination: { server: '{{cluster}}', namespace: web }
syncPolicy: { automated: { prune: true, selfHeal: true } }
Promotion giữa các môi trường
Promotion là một thao tác Git, không phải một stage của pipeline:
- CI build
web:1.5.0, push nó, và cập nhật tag trongoverlays/dev. - Sau khi kiểm chứng ở dev, một PR chép version bump sang
overlays/staging, rồi sau khi “ngâm” ở staging, một PR nữa sangoverlays/prod. - Mỗi lần promotion là một commit được review và revert được, nên “cái gì đang chạy ở đâu” trả lời được bằng cách đọc Git.
Ưu tiên mỗi-môi-trường-một-thư-mục thay vì mỗi-môi-trường-một-branch: các branch dễ lệch nhau, và việc merge qua lại tạo nhiễu và vô tình promote những thay đổi không liên quan. Overlay theo thư mục làm toàn bộ state của mỗi môi trường tường minh và diff được. Các công cụ như Kargo tự động hóa promotion nhiều tầng (freight, xác minh, tiến bước có gác) trên nền GitOps, và PR generator của ApplicationSet cho môi trường preview theo từng PR.
Secrets trong GitOps
Tuyệt đối không được commit secret dạng plaintext — lịch sử Git là mãi mãi và repo bị clone khắp nơi. Ba cách tiếp cận tiêu chuẩn, mỗi cách giữ Git an toàn theo một kiểu khác nhau:
| Cách tiếp cận | Cách hoạt động | Đánh đổi |
|---|---|---|
| Sealed Secrets (Bitnami) | kubeseal mã hóa Secret bằng public key của controller trong cluster; SealedSecret đã mã hóa an toàn để lưu trong Git và chỉ controller đó giải mã được | Đơn giản, không cần store bên ngoài; nhưng secret bị gắn với key của một cluster, và rotation/DR cần backup private key |
| SOPS (+ age/KMS) | Mã hóa từng giá trị ngay trong file YAML/JSON; Flux giải mã native, Argo CD qua plugin (ví dụ ksops) | File vẫn diff được về cấu trúc; bạn tự lo quản lý key (age key, cloud KMS) |
| External Secrets Operator | Git chỉ lưu một tham chiếu ExternalSecret; operator đồng bộ giá trị thật từ Vault / AWS Secrets Manager / GCP SM / Azure Key Vault vào một Secret native | Dữ liệu secret hoàn toàn không chạm Git, và rotation diễn ra trong store; cần chạy và tin cậy một secret store bên ngoài |
# ExternalSecret: Git giữ con trỏ, không giữ secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db-credentials }
spec:
refreshInterval: 1h
secretStoreRef: { name: aws-sm, kind: ClusterSecretStore }
target: { name: db-credentials } # Secret K8s native được tạo ra
data:
- secretKey: password
remoteRef: { key: prod/db, property: password }
Nguyên tắc: Sealed Secrets cho hệ nhỏ không có secret store; SOPS khi muốn mọi thứ (kể cả secret) nằm trong Git nhưng đã mã hóa; External Secrets Operator khi bạn đã chạy Vault hoặc một cloud secret manager và muốn Git không giữ chút vật liệu secret nào.
Phát hiện drift và reconciliation
Vòng lặp reconciliation của agent tính diff giữa trạng thái mong muốn đã render và trạng thái đang chạy. Drift có thể xử lý theo hai hướng:
- Phát hiện và cảnh báo — hiển thị trạng thái
OutOfSyncvà thông báo cho người xử lý, người này review rồi hoặc reconcile hoặc đưa thay đổi vào Git. Tốt ở giai đoạn đầu, khi bạn chưa tin tự-sửa. - Tự sửa (self-heal) — agent lập tức hoàn tác thay đổi ngoài luồng để cluster bật về khớp Git. Tốt khi đã trưởng thành, nhưng nghĩa là một hotfix sửa tay sẽ biến mất — có chủ đích, nhưng lần đầu gặp sẽ bất ngờ.
Dù cách nào, thay đổi ngoài luồng đều thua: cluster không còn là nơi tạo ra trạng thái, chỉ là nơi trạng thái hội tụ về. Một số field phải được loại khỏi phát hiện drift (ví dụ số replica do HPA quản lý, field do webhook inject) qua ignoreDifferences để agent và các controller khác không “đánh nhau”.
Progressive delivery trên nền GitOps
GitOps lo cái gì được deploy; progressive delivery lo rollout an toàn thế nào. Argo Rollouts (với Argo CD) và Flagger (với Flux) thêm chiến lược canary và blue-green được điều khiển bởi metric thật: dịch 10% traffic, theo dõi tỉ lệ lỗi và độ trễ (Prometheus), rồi tự động promote hoặc rollback. Định nghĩa rollout vẫn nằm trong Git, nên chính sách an toàn cũng được version cùng với app.
Best Practices
- Tách repo source code ứng dụng và repo cấu hình deployment — nhịp release độc lập, phân quyền gọn gàng, không tạo vòng lặp CI từ các commit bump image.
- Biến Git thành con đường ghi duy nhất vào production — khóa quyền ghi
kubectltrực tiếp; thứ gì không có trong Git thì coi như không tồn tại và không nên sống sót qua lần reconcile kế tiếp. - Chỉ chuyển sang automated sync (prune + self-heal) khi đã đủ trưởng thành — bắt đầu với sync thủ công + review diff, rồi mới bật auto-sync theo từng môi trường khi kỷ luật và cảnh báo đã vững; giữ prod chặt hơn dev.
- Dùng mỗi-môi-trường-một-thư-mục, không phải mỗi-môi-trường-một-branch — tránh branch sống lâu bị lệch và các cuộc merge liên môi trường đau đớn, dễ sai.
- Ghim phiên bản ở mọi nơi — digest/tag của image, version của Helm chart, và commit SHA (không phải branch trôi nổi) cho base; khả năng tái lập chính là toàn bộ mục tiêu.
- Không bao giờ commit secret plaintext — chọn một chiến lược secret và chuẩn hóa — Sealed Secrets, SOPS, hoặc External Secrets Operator dùng thống nhất cho mọi team để có một con đường được audit.
- Bảo vệ config repo như bảo vệ production — branch protection, bắt buộc review, CODEOWNERS, signed commit; một lần merge ở đây chính là một lần deploy, nên hãy coi review là cổng phê duyệt thay đổi.
- Kiểm tra manifest trong CI trước khi merge —
kustomize build,helm template,kubeconformvà policy check (OPA/Conftest/Kyverno) bắt lỗi schema và policy trước khi agent cố áp. - Hiển thị diff đã render trong PR — chạy
argocd app diff/flux diff kustomizationnhư một PR check để reviewer thấy thay đổi thực sự áp lên cluster, không chỉ thấy sửa template. - Cảnh báo khi sync thất bại hoặc có drift — nối Argo CD notifications / Flux alerts vào Slack hoặc công cụ incident; một
OutOfSynchayDegradedâm thầm là rủi ro sự cố âm thầm. - Bootstrap cluster bằng app-of-apps hoặc ApplicationSet — khôi phục cluster chỉ còn: tạo cluster, cài agent, trỏ vào repo, để nó tái hiện mọi thứ.
- Rollback bằng
git revert, không sửa tay — giữ nguyên dấu vết audit và bảo đảm Git luôn phản ánh sự thật; sửa tay dù sao cũng bị self-heal hoàn tác. - Dùng sync wave / sắp thứ tự phụ thuộc — deploy CRD và namespace trước các workload cần chúng, và database trước các app kết nối tới.
- Loại các field do controller quản lý khỏi drift — dùng
ignoreDifferencescho số replica của HPA và field do admission inject để GitOps và các controller khác không giằng co. - Thêm progressive delivery cho các service rủi ro — Argo Rollouts / Flagger cho canary gác bằng metric, để một version tệ bị bắt và rollback tự động, với chính sách nằm trong Git.
- Quản lý cả cấu hình của agent bằng Git — quản lý Argo CD/Flux qua GitOps (tự quản lý) để platform cũng tái lập và audit được như các app nó deploy.
Tài liệu tham khảo
Part of the DevOps Roadmap knowledge base.
Overview
GitOps is an operating model for delivering infrastructure and applications in which Git is the single source of truth for the desired state of the system. Everything that defines your runtime environment — Kubernetes manifests, Helm values, Kustomize overlays, infrastructure definitions — lives in Git. An automated agent running in the cluster continuously compares what is actually running against what Git says should be running, and reconciles the difference. Deploying becomes git merge; rolling back becomes git revert.
This model brings the guarantees of software engineering to operations: every change is a reviewed pull request, the full history is auditable, and the environment can be rebuilt from the repository at any time. Because a controller pulls changes rather than a pipeline pushing them, credentials for the cluster never need to leave the cluster — a major security improvement over classic CI-driven deployment.
GitOps builds directly on the previous roadmap topics: containers give you immutable artifacts, Kubernetes provides declarative APIs and reconciliation, and GitOps closes the loop by putting the desired state under version control. Argo CD and Flux are the two dominant CNCF-graduated implementations.
A mental model
GitOps is the reconciliation loop of Kubernetes (spec vs status), extended so that the spec itself lives in Git instead of only in etcd. The agent turns your Git repository into a second desired-state store and continuously drives the cluster toward it. Two consequences follow directly:
- The cluster is no longer a place where state is authored — only where it converges. Any change made outside Git is, by definition, drift the agent will report or undo.
- Recovery is trivial by construction. To rebuild an environment, point a fresh agent at the repo; the desired state re-materializes. Your cluster becomes cattle, and Git becomes the herd’s pedigree.
GitOps is not just “Kubernetes + Git.” The same pattern applies to anything with a declarative API and a reconciler — Terraform state, Crossplane-managed cloud resources, cluster add-ons — but Kubernetes is where it is most mature.
Fundamentals
The four GitOps principles (OpenGitOps)
- Declarative — the entire desired state is expressed declaratively (YAML manifests, not scripts of imperative steps).
- Versioned and immutable — the desired state is stored in a way that enforces immutability and full version history (Git, with signed, append-only commits).
- Pulled automatically — software agents automatically pull the desired state from the source, rather than a human or pipeline pushing it in.
- Continuously reconciled — agents continuously observe actual state and attempt to converge it toward the desired state, correcting drift without human intervention.
Push vs pull deployment models
| Aspect | Push (classic CI/CD) | Pull (GitOps) |
|---|---|---|
| Who deploys | CI pipeline runs kubectl apply / helm upgrade | In-cluster agent pulls from Git |
| Cluster credentials | Stored in the CI system (exfiltration risk) | Never leave the cluster |
| Drift handling | None — manual changes go unnoticed | Detected and (optionally) auto-corrected |
| Audit trail | Scattered across pipeline logs | Complete in Git history |
| Recovery / DR | Re-run pipelines in order | Point agent at repo; state converges |
| New cluster onboarding | Configure pipeline credentials per cluster | Install agent, point at repo |
| Blast radius of CI compromise | Direct write access to prod cluster | CI can only open a PR; merge still gated |
| Direction | CI → cluster (outbound to many clusters) | Cluster → Git (each cluster pulls its own) |
In practice CI still exists in GitOps — it builds, tests, and pushes images, then updates an image tag in the config repo (directly or via an image automation controller). CD is fully owned by the GitOps agent. The clean split is: CI produces artifacts and proposes desired state; CD (the agent) realizes it.
Repository structure
A common pattern is separating application source from deployment configuration:
app-repo/ # source code + Dockerfile; CI builds and pushes the image
config-repo/
├── apps/
│ └── web/
│ ├── base/ # shared manifests (Kustomize base)
│ └── overlays/
│ ├── dev/kustomization.yaml
│ ├── staging/
│ └── prod/
└── clusters/
├── dev/
│ └── apps.yaml # Argo CD Applications / Flux Kustomizations
└── prod/
└── apps.yaml
Separate repos prevent CI loops (image-bump commits re-triggering builds), allow different access controls (developers on app-repo, platform/SRE on config-repo), and keep audit history clean. The clusters/ tree holds the root definitions each cluster’s agent points at; apps/ holds the actual workload manifests. Some teams instead use a monorepo with strict path-based ownership — either works, but keep app source and rendered config logically separate.
What GitOps does not solve on its own
- It is not a full CD story by itself. You still need CI to build/test/sign images and something (image automation or a promotion tool) to move versions between environments.
- Ordering and dependencies across many apps need sync waves / dependency ordering, not just “apply everything.”
- Database migrations and one-shot tasks don’t fit the pure reconcile model cleanly; use Jobs with sync hooks and careful sequencing.
- Secrets cannot go in Git in plaintext — a dedicated strategy is mandatory (covered below).
Key Concepts
Argo CD vs Flux
| Aspect | Argo CD | Flux |
|---|---|---|
| CNCF status | Graduated | Graduated |
| UI | Rich built-in web UI (sync status, resource tree, live diffs) | CLI-first; Weave GitOps / Headlamp / Capacitor plugins for UI |
| Core abstraction | Application and ApplicationSet CRDs | GitRepository, Kustomization, HelmRelease, OCIRepository CRDs (GitOps Toolkit) |
| Architecture | api-server, repo-server, application-controller, redis | Set of specialized controllers (source, kustomize, helm, notification, image) |
| Multi-tenancy | Projects, RBAC, SSO built in | Kubernetes-native RBAC, namespace-scoped, tenant isolation via impersonation |
| Image update automation | Separate project (argocd-image-updater) | Built-in (image-reflector + image-automation controllers) |
| Progressive delivery | Argo Rollouts (sibling project) | Flagger (sibling project) |
| Rendering | Helm, Kustomize, plain YAML, jsonnet, config plugins | Helm, Kustomize, plain YAML |
| Typical fit | Teams wanting visibility/UI and an app-centric workflow | Platform teams wanting lightweight, composable, Kubernetes-native building blocks |
Both watch Git, render manifests (Helm/Kustomize/plain YAML), diff against the cluster, and reconcile on an interval or webhook. Choosing between them is mostly about whether you want an opinionated app-centric UI (Argo CD) or a composable controller toolkit (Flux); many organizations succeed with either.
The reconciliation loop in detail
- Fetch — the agent clones/pulls the target revision (a branch, tag, or commit SHA) from Git (Argo CD polls ~3 min by default, plus webhooks; Flux uses a per-source interval).
- Render — it runs
kustomize build/helm templateto produce the concrete desired manifests. - Diff — it compares rendered desired state against live cluster state (server-side diff, ignoring fields it shouldn’t manage).
- Sync — it applies the difference: create, update, and (if pruning is on) delete resources removed from Git.
- Report — it surfaces status:
Synced/OutOfSync,Healthy/Degraded, and events for humans and alerting.
Example Argo CD Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-prod
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # cascade-delete managed resources with the App
spec:
project: default
source:
repoURL: https://github.com/example/config-repo.git
targetRevision: main
path: apps/web/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: web
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: true # revert manual changes in the cluster
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
retry:
limit: 3
backoff: { duration: 5s, factor: 2, maxDuration: 3m }
With selfHeal: true, a manual kubectl edit is reverted within seconds — the cluster always matches Git. With prune: true, deleting a manifest from Git deletes the resource from the cluster. Both are powerful and dangerous; enable them for production only once your review and rollback discipline is solid.
App-of-apps and ApplicationSet
To manage many applications, define a root Application whose source path contains more Application manifests (the app-of-apps pattern) — bootstrapping an entire cluster becomes one kubectl apply of the root App. ApplicationSet generalizes this with generators that stamp out Applications from a single template:
- Git generator — one Application per directory or file in a repo (add a folder, get an app).
- Cluster generator — one Application per registered cluster (fan the same app across a fleet).
- List / matrix / pull-request generators — explicit lists, cross-products, and ephemeral preview environments per open PR.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: web-all-envs, namespace: argocd }
spec:
generators:
- list:
elements:
- { env: dev, cluster: https://kubernetes.default.svc }
- { env: staging, cluster: https://kubernetes.default.svc }
- { env: prod, cluster: https://kubernetes.default.svc }
template:
metadata: { name: 'web-{{env}}' }
spec:
project: default
source:
repoURL: https://github.com/example/config-repo.git
targetRevision: main
path: 'apps/web/overlays/{{env}}'
destination: { server: '{{cluster}}', namespace: web }
syncPolicy: { automated: { prune: true, selfHeal: true } }
Environment promotion
Promotion is a Git operation, not a pipeline stage:
- CI builds
web:1.5.0, pushes it, and updates the tag inoverlays/dev. - After validation in dev, a PR copies the version bump to
overlays/staging, then after staging soak, another PR tooverlays/prod. - Each promotion is a reviewed, revertible commit, so “what is running where” is answerable by reading Git.
Prefer directory-per-environment over branch-per-environment: branches drift, and merges between them create noise and accidental promotion of unrelated changes. Directory overlays make each environment’s full state explicit and diffable. Tools like Kargo automate multi-stage promotion (freight, verification, and gated progression) on top of GitOps, and Argo CD’s ApplicationSet PR generator gives per-PR preview environments.
Secrets in GitOps
Plaintext secrets can never be committed — Git history is forever and repos get cloned widely. Three standard approaches, each keeping Git safe in a different way:
| Approach | How it works | Trade-off |
|---|---|---|
| Sealed Secrets (Bitnami) | kubeseal encrypts a Secret with the cluster controller’s public key; the encrypted SealedSecret is safe in Git and only that controller can decrypt it | Simple, no external store; but secrets are tied to one cluster’s key, and rotation/DR needs the private key backed up |
| SOPS (+ age/KMS) | Encrypt individual values inside YAML/JSON files; Flux decrypts natively, Argo CD via a plugin (e.g., ksops) | Files remain structurally diffable; key management (age keys, cloud KMS) is on you |
| External Secrets Operator | Git stores only an ExternalSecret reference; the operator syncs the real value from Vault / AWS Secrets Manager / GCP SM / Azure Key Vault into a native Secret | Secret data never touches Git at all, and rotation happens in the store; requires running and trusting an external secret store |
# ExternalSecret: Git holds a pointer, not the secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db-credentials }
spec:
refreshInterval: 1h
secretStoreRef: { name: aws-sm, kind: ClusterSecretStore }
target: { name: db-credentials } # the native K8s Secret that gets created
data:
- secretKey: password
remoteRef: { key: prod/db, property: password }
Rule of thumb: Sealed Secrets for small setups with no secret store; SOPS when you want everything (including secrets) in Git but encrypted; External Secrets Operator when you already run Vault or a cloud secret manager and want Git to hold zero secret material.
Drift detection and reconciliation
The agent’s reconciliation loop computes a diff between rendered desired state and live state. Drift can be handled two ways:
- Detect and alert — surface
OutOfSyncstatus and notify a human, who reviews and either reconciles or codifies the change into Git. Good early on, when you don’t yet trust auto-correction. - Auto-correct (self-heal) — the agent immediately reverts the out-of-band change so the cluster snaps back to Git. Good once mature, but it means a hotfix applied by hand will vanish — deliberate, but surprising the first time it happens.
Either way, out-of-band changes lose: the cluster is no longer a place where state is authored, only where it converges. Some fields must be excluded from drift detection (e.g., HPA-managed replica counts, webhook-injected fields) via ignoreDifferences so the agent and other controllers don’t fight over them.
Progressive delivery on top of GitOps
GitOps handles what is deployed; progressive delivery handles how safely it rolls out. Argo Rollouts (with Argo CD) and Flagger (with Flux) add canary and blue-green strategies driven by real metrics: shift 10% of traffic, watch error rate and latency (Prometheus), and automatically promote or roll back. The rollout definition still lives in Git, so the safety policy is versioned alongside the app.
Best Practices
- Separate application code and deployment config repos — independent release cadence, cleaner permissions, and no CI trigger loops from image-bump commits.
- Make Git the only write path to production — lock down direct
kubectlwrite access; if it’s not in Git, it doesn’t exist and shouldn’t survive the next reconcile. - Graduate to automated sync (prune + self-heal) only after maturity — start with manual sync and diff review, then enable auto-sync per environment once your discipline and alerting are solid; keep prod stricter than dev.
- Use directory-per-environment, not branch-per-environment — avoids long-lived branch drift and painful, error-prone cross-environment merges.
- Pin versions everywhere — image digests/tags, Helm chart versions, and Git commit SHAs (not floating branches) for bases; reproducibility is the entire point.
- Never commit plaintext secrets — pick one secrets strategy and standardize — Sealed Secrets, SOPS, or External Secrets Operator, applied consistently across all teams so there’s one audited path.
- Protect the config repo like production — branch protection, required reviews, CODEOWNERS, and signed commits; a merge here is a deployment, so treat the review as a change-approval gate.
- Validate manifests in CI before merge —
kustomize build,helm template,kubeconform, and policy checks (OPA/Conftest/Kyverno) catch schema and policy errors before the agent ever tries to apply them. - Show the rendered diff in PRs — run
argocd app diff/flux diff kustomizationas a PR check so reviewers see the actual cluster-facing change, not just the template edit. - Alert on sync failures and drift — wire Argo CD notifications / Flux alerts into Slack or your incident tooling; a silent
OutOfSyncorDegradedis a silent outage risk. - Bootstrap clusters with app-of-apps or ApplicationSets — cluster recovery becomes: create cluster, install the agent, point it at the repo, and let it re-materialize everything.
- Roll back with
git revert, not manual edits — this preserves the audit trail and keeps Git truthful; a manual fix will be undone by self-heal anyway. - Use sync waves / dependency ordering — deploy CRDs and namespaces before the workloads that need them, and databases before the apps that connect to them.
- Exclude controller-managed fields from drift — use
ignoreDifferencesfor HPA replica counts and admission-injected fields so GitOps and other controllers don’t thrash. - Layer progressive delivery for risky services — Argo Rollouts / Flagger for metric-gated canaries so a bad version is caught and rolled back automatically, with the policy itself in Git.
- Keep the agent’s own config in Git too — manage Argo CD/Flux via GitOps (self-management) so the platform is as reproducible and auditable as the apps it deploys.