GitOps với ArgoCD: git là nguồn sự thật
Bài trước Helm đã gói 130 file YAML thành 5 Chart gọn ghẽ. Nhưng nhìn lại pitfall số 10 ở cuối bài — nó chính là vết nứt còn lại, và là lý do bài này tồn…
Bài trước Helm đã gói 130 file YAML thành 5 Chart gọn ghẽ. Nhưng nhìn lại pitfall số 10 ở cuối bài — nó chính là vết nứt còn lại, và là lý do bài này tồn tại:
"Sửa trực tiếp bằng
kubectl edittrên thứ Helm quản → lầnhelm upgradesau ghi đè mất. Nguồn sự thật phải là Chart/values, không phải cluster."
Vấn đề sâu hơn cả Helm: bạn vẫn deploy bằng tay. Ai có kubeconfig prod là gõ được helm upgrade từ laptop. Không ai chắc chắn prod đang chạy đúng cái gì. Và một lệnh kubectl edit vội vàng lúc 2 giờ sáng sẽ âm thầm khiến cluster lệch khỏi git — mãi mãi, cho tới khi nó nổ.
GitOps lật ngược mô hình: git là nguồn sự thật duy nhất; cluster tự kéo về và tự sửa mọi sai lệch. ArgoCD là công cụ hiện thực nó. Sau bài này, "deploy" không còn là một hành động thủ công — nó là một commit.
Vấn đề: cluster trôi khỏi git
❌ Push-based deploy (cách bạn đang làm):
Laptop dev ──helm upgrade──► Cluster prod
Laptop khác ──kubectl apply──► ▲
CI runner ──helm upgrade──────────┘
→ Ai cũng có kubeconfig prod = ai cũng deploy được (kể cả lúc say)
→ Không có "sổ cái" ai deploy gì, lúc nào
→ 2h sáng, sự cố: một người kubectl edit replicas=10 để cứu tình thế,
rồi QUÊN cập nhật git.
→ Git nói 3 replica. Cluster chạy 10 replica.
→ CONFIGURATION DRIFT (lệch cấu hình).
→ Tuần sau ai đó helm upgrade → replicas về 3 → sự cố lặp lại,
không ai hiểu vì sao.
→ Dựng lại cluster từ git? Không dám chắc git = thực tế.Ba câu hỏi mà mô hình thủ công không trả lời được:
- Prod đang chạy đúng cái gì? (git nói một đằng, cluster có thể một nẻo)
- Ai đổi cái gì, lúc nào, vì sao? (không có audit trail)
- Cluster cháy — dựng lại từ git được không? (nếu git ≠ thực tế thì không)
GitOps là gì
GitOps: mọi trạng thái mong muốn của hệ thống được khai báo trong git; một agent trong cluster liên tục so sánh git với thực tế và tự đưa thực tế khớp git.
Nghe quen chứ? Đây chính xác là reconciliation loop của K8s (Bài 01 chặng trước) — chỉ nâng lên một tầng: thay vì "controller đối chiếu Pod với spec", giờ là "ArgoCD đối chiếu cả cluster với git".
Push-based (cũ) Pull-based / GitOps (mới)
─────────────── ─────────────────────────
Người → đẩy vào cluster Người → commit vào GIT
│
▼
ArgoCD (SỐNG TRONG CLUSTER)
• kéo git về
• so sánh với thực tế
• tự apply cho khớp
• phát hiện & sửa drift
kubeconfig prod nằm Không ai cần kubeconfig prod
trên nhiều laptop → chỉ cần quyền push gitBốn nguyên tắc GitOps:
1. KHAI BÁO toàn bộ hệ thống mô tả bằng khai báo (YAML/Helm), không phải script
2. CÓ PHIÊN BẢN git là nguồn sự thật duy nhất — có lịch sử, có review, có revert
3. TỰ KÉO VỀ agent trong cluster tự pull & apply (không ai push vào cluster)
4. TỰ HÀN GẮN liên tục đối chiếu; lệch thì tự sửa (self-heal)→ Lợi ích cộng dồn: deploy = git push · rollback = git revert · audit = git log · dựng lại cluster = trỏ ArgoCD vào repo.
ArgoCD — kiến trúc & Application
ArgoCD là một controller chạy trong cluster, theo dõi các repo git và đồng bộ chúng vào cluster.
┌──────────┐ commit & ┌──────────┐
│ Dev │ ──push────► │ GIT │ ← nguồn sự thật
└──────────┘ └────┬─────┘
│ ArgoCD PULL (mỗi 3 phút, hoặc webhook)
▼
┌────────────────────────┐
│ ArgoCD (trong cluster) │
│ • so sánh git ↔ live │
│ • Synced / OutOfSync? │
│ • auto-sync, self-heal │
└───────────┬─────────────┘
│ apply
▼
┌────────────────────────┐
│ Cluster (Deployment, │
│ Service, Ingress...) │
└────────────────────────┘Application — đơn vị cốt lõi
ArgoCD định nghĩa một CRD tên Application: "repo này, đường dẫn này → đồng bộ vào cluster/namespace kia".
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
namespace: argocd
spec:
project: default
source: # NGUỒN SỰ THẬT
repoURL: https://git.cty.vn/team/myapp-manifests.git
targetRevision: main # nhánh/tag/commit
path: charts/myapp # Helm Chart từ Bài 01!
helm:
valueFiles:
- values-prod.yaml # đúng file values của môi trường prod
destination: # ĐÍCH
server: https://kubernetes.default.svc # chính cluster này
namespace: prod
syncPolicy:
automated:
prune: true # xóa resource đã bị gỡ khỏi git
selfHeal: true # AI SỬA TAY TRÊN CLUSTER → ArgoCD KÉO VỀ ĐÚNG GIT
syncOptions:
- CreateNamespace=true→ Để ý: ArgoCD dùng thẳng Helm Chart bạn viết ở Bài 01. Hai công cụ không cạnh tranh — Helm đóng gói, ArgoCD đồng bộ. Cặp đôi kinh điển.
Ba núm quyết định hành vi
automated ArgoCD TỰ sync khi git đổi (không cần bấm nút)
→ tắt = phải bấm "Sync" thủ công (hợp môi trường nhạy cảm)
prune: true Xóa resource khỏi git → ArgoCD XÓA khỏi cluster
→ tắt = resource bị gỡ khỏi git vẫn "mồ côi" trong cluster
selfHeal: true Ai sửa tay trên cluster (kubectl edit) → ArgoCD ĐÈ LẠI theo git
→ đây là thứ GIẾT CHẾT configuration drift ⭐selfHeal: true là linh hồn của GitOps. Bật nó lên, kubectl edit replicas=10 sẽ bị ArgoCD kéo về 3 trong vài giây. Muốn 10 replica thật? Sửa git. Không còn đường tắt — và đó chính là điều tốt.
Trạng thái Application
SYNC STATUS HEALTH STATUS
─────────── ─────────────
Synced git = live Healthy mọi thứ chạy tốt
OutOfSync git ≠ live Progressing đang triển khai
Unknown chưa so được Degraded có resource lỗi (Pod CrashLoop...)
Missing resource chưa tồn tại⚠️ Synced ≠ Healthy. Synced nghĩa là "cluster khớp git"; nhưng thứ trong git có thể... hỏng (image sai → Pod CrashLoopBackOff → Degraded). Nhìn cả hai cột.
Deploy = commit; Rollback = revert
Đây là thay đổi lớn nhất trong đời sống hằng ngày:
TRƯỚC (thủ công) SAU (GitOps)
──────────────── ────────────
Deploy: helm upgrade từ laptop Sửa image tag trong git → PR → merge
→ ArgoCD tự sync (~vài giây)
Rollback: helm rollback (nhớ số git revert <commit> → push
revision, chạy đúng máy) → ArgoCD tự kéo về bản cũ
Audit: "ai deploy nhỉ?" 🤷 git log — ai, lúc nào, PR nào, review ai
Quyền: kubeconfig prod trên KHÔNG AI cần kubeconfig prod;
nhiều laptop chỉ cần quyền merge PR→ Deploy trở thành thứ có review, có lịch sử, có thể revert — giống hệt cách bạn quản code. Đó là toàn bộ ý tưởng.
App-of-Apps & ApplicationSet — quản nhiều app
Một Application cho một app. Có 20 app thì sao? Hai mẫu:
App-of-Apps — một Application "cha" trỏ vào thư mục chứa các Application "con":
root-app (Application)
└── apps/ ← thư mục trong git
├── frontend.yaml (Application)
├── api.yaml (Application)
├── redis.yaml (Application)
└── monitoring.yaml (Application)
→ Thêm app mới = thêm 1 file vào apps/ → ArgoCD tự nhận. Bootstrap cả cluster
chỉ bằng cách tạo MỘT root-app.ApplicationSet — sinh nhiều Application từ một khuôn (generator), rất hợp mô hình "cùng app, nhiều môi trường/cluster":
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: myapp-all-envs, namespace: argocd }
spec:
generators:
- list:
elements:
- env: dev
- env: staging
- env: prod
template:
metadata: { name: 'myapp-{{env}}' }
spec:
project: default
source:
repoURL: https://git.cty.vn/team/myapp-manifests.git
targetRevision: main
path: charts/myapp
helm:
valueFiles: ['values-{{env}}.yaml'] # ← đúng cấu trúc Bài 01!
destination:
server: https://kubernetes.default.svc
namespace: '{{env}}'
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]→ Ba môi trường sinh ra từ một khuôn, ghép thẳng với values-dev/staging/prod.yaml của Helm. Đây là lúc Bài 01 và Bài 02 khớp vào nhau hoàn hảo.
Bài toán khó: Secret trong GitOps
Đây là câu hỏi ai làm GitOps cũng vấp, và nó nối thẳng về Bài 05 chặng K8s:
GitOps nói: "MỌI THỨ phải ở trong git."
Bảo mật nói: "Secret KHÔNG BAO GIỜ được ở trong git."
→ Mâu thuẫn. Giải sao?Ba lời giải chuẩn:
Sealed Secrets Mã hóa Secret bằng khóa công khai của cluster
(Bitnami) → file MÃ HÓA an toàn để commit git
→ controller trong cluster giải mã thành Secret thật
→ đơn giản, hợp on-prem. ⭐ dễ bắt đầu nhất
External Secrets Secret NẰM NGOÀI (Vault, AWS Secrets Manager...)
Operator → git chỉ chứa "con trỏ" tới secret, không chứa giá trị
→ chuẩn doanh nghiệp, xoay vòng khóa tốt
SOPS + age/KMS Mã hóa file YAML tại chỗ, commit bản mã hóa
→ linh hoạt, hợp nhiều pipeline⚠️ Tuyệt đối không vì tiện GitOps mà commit Secret plain — đúng cái sự cố "rò secret lên git" tôi đã kể ở Bài 05 chặng K8s.
🚀 Lab — GitOps trong 20 phút
Dùng cluster kind.
1. Cài ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd rollout status deploy/argocd-server
# Lấy mật khẩu admin ban đầu
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d ; echo
# Mở UI (Bài 02 chặng K8s — port-forward!)
kubectl port-forward svc/argocd-server -n argocd 8080:443
# → https://localhost:8080 (user: admin)2. Tạo Application trỏ vào repo mẫu
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
destination:
server: https://kubernetes.default.svc
namespace: guestbook
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]
EOF
kubectl -n argocd get applications
kubectl get pods -n guestbook # ArgoCD tự kéo git về và tạo Pod! ✨3. Chứng kiến self-heal giết drift
# Xem trạng thái hiện tại
kubectl -n guestbook get deploy
# GIẢ VỜ "sửa tay lúc 2h sáng":
kubectl -n guestbook scale deploy guestbook-ui --replicas=5
kubectl -n guestbook get deploy --watch
# Đợi vài giây... ArgoCD phát hiện LỆCH GIT và TỰ KÉO VỀ đúng số trong git.
# → replicas quay lại giá trị trong repo. Drift bị giết ngay lập tức. ⭐→ Đây là khoảnh khắc quan trọng nhất của cả bài. Bạn vừa thấy tận mắt: cluster từ chối rời xa git. Không có kubectl edit lén lút nào sống sót.
4. Xóa resource — prune bảo vệ
kubectl -n guestbook delete deploy guestbook-ui
kubectl -n guestbook get deploy --watch
# ArgoCD tạo lại — vì git nói nó phải tồn tại. Cluster tự hàn gắn.5. Dọn
kubectl delete application guestbook -n argocd
kubectl delete namespace guestbook argocdCâu chuyện thực tế
Sự cố khiến chúng tôi chuyển sang GitOps xảy ra vào một đêm thứ Ba.
Traffic tăng đột biến, API chậm. Một bạn trong team (rất giỏi, rất có trách nhiệm) SSH vào, chạy kubectl scale deploy api --replicas=10. Hệ thống ổn định lại trong 2 phút. Anh hùng. Rồi bạn ấy đi ngủ — và quên cập nhật git.
Mười ngày sau, một người khác helm upgrade để đổi image tag. Helm áp lại values từ repo: replicaCount: 3. Trong một giây, số Pod API từ 10 tụt xuống 3 — giữa giờ cao điểm. API nghẽn, khách lỗi. Chúng tôi mất 35 phút để hiểu chuyện gì xảy ra, vì trong đầu ai cũng nghĩ "prod đang chạy 10 replica" — trong khi git chưa bao giờ biết điều đó.
Cái sai không phải ở người scale lúc nửa đêm. Cái sai là hệ thống cho phép cluster lệch khỏi git mà không ai hay biết.
Chúng tôi triển khai ArgoCD trong 2 ngày. Kết quả đo được sau một quý:
- Configuration drift: về 0.
selfHealkéo mọi thay đổi tay về đúng git trong vài giây. Muốn đổi thật? Sửa git — và thế là nó tự động được ghi lại, review, và tồn tại vĩnh viễn. - Rollback: từ ~15 phút run tay (nhớ số revision, chạy đúng máy, cầu trời) →
git revert+ đợi ArgoCD sync ≈ 1 phút, và ai cũng làm được. - Thu hồi
kubeconfigprod khỏi 6 laptop. Giờ deploy prod = merge PR. Bảo mật thắng lớn, và không ai còn deploy được lúc say. - Onboarding: dev mới nhìn UI ArgoCD là biết chính xác prod đang chạy gì — không phải đi hỏi.
Cú tát của ArgoCD đến ở tuần đầu: chúng tôi bật prune: true mà quên rằng có vài resource được tạo tay từ trước (không có trong git). ArgoCD thấy chúng "không thuộc git" và... xóa sạch. Trong đó có một ConfigMap ai đó tạo tay và không ai nhớ. Mất 30 phút để dựng lại. Bài học đắng: GitOps là hợp đồng — mọi thứ phải ở trong git, hoặc nó không tồn tại. Bật prune là bạn ký vào hợp đồng đó.
Bài học:
Configuration drift là sát thủ thầm lặng. Nó không gây sự cố ngay — nó cài bom hẹn giờ cho lần deploy sau.
selfHeallà thuốc giải."Sửa tay để cứu tình thế" luôn kết thúc bằng một sự cố lớn hơn. Không phải vì người sửa sai, mà vì hệ thống không ép sự thay đổi đó quay về git.
Deploy = commit, rollback = revert, audit = git log. Ba câu này gói trọn GitOps. Deploy trở thành thứ có review và lịch sử — như code.
Bật
prune= ký hợp đồng "mọi thứ phải ở trong git". Trước khi bật, rà soát mọi resource tạo tay. Nếu không, ArgoCD sẽ dọn sạch giúp bạn (theo nghĩa đen).Pull > push cho bảo mật. Không ai cần
kubeconfigprod nữa. Cluster tự kéo về; kẻ tấn công chiếm laptop dev cũng không deploy được vào prod.Helm + ArgoCD là cặp đôi, không phải đối thủ. Helm đóng gói và tham số hóa; ArgoCD đồng bộ và canh giữ.
values-prod.yamlcủa Bài 01 chính là thứ ArgoCD trỏ vào.
Pitfalls
Bật
prune: truekhi cluster còn resource tạo tay → ArgoCD xóa sạch chúng. Rà soát trước khi ký hợp đồng GitOps.Commit Secret plain lên git "cho đúng tinh thần GitOps". Sai nghiêm trọng. Dùng Sealed Secrets / External Secrets / SOPS.
Vẫn
kubectl edittrên app do ArgoCD quản → bị đè lại (nếuselfHeal) hoặc gâyOutOfSynckhó hiểu. Muốn đổi thì sửa git.Nhầm
Syncedlà "ổn". Synced chỉ nghĩa "cluster khớp git" — git có thể chứa thứ hỏng. Xem thêm cột Health (Degraded?).Dùng
targetRevision: HEAD/maincho prod → mọi commit vào main lập tức lên prod. Prod nên trỏ tag/commit cụ thể hoặc nhánh release, có cổng kiểm soát.Không tách repo app và repo manifest → CI build image xong sửa manifest, gây vòng lặp trigger rối. Mẫu phổ biến: repo code (build image) tách khỏi repo manifest (ArgoCD theo dõi).
Auto-sync cho môi trường siêu nhạy cảm mà không có gate. Cân nhắc
automatedtắt (sync thủ công) hoặc dùng sync window cho prod.ArgoCD tự quản chính nó nhưng không có backup → cluster cháy, mất luôn cấu hình Application. Đưa cả manifest ArgoCD (App-of-Apps) vào git.
Quên rằng ArgoCD cần quyền RBAC (Bài 04 chặng K8s) để tạo resource. Thiếu quyền → sync fail với lỗi
Forbidden.Drift do resource được controller khác sửa (vd HPA đổi
replicas— Bài 03 chặng K8s!) → ArgoCD tưởng là drift và kéo về. Khắc phục: bỏreplicaskhỏi git khi dùng HPA, hoặc dùngignoreDifferences.
Tóm tắt
- GitOps: git là nguồn sự thật duy nhất; agent trong cluster tự kéo về và tự sửa sai lệch. Chính là reconciliation loop của K8s, nâng lên tầng cả cluster.
- 4 nguyên tắc: khai báo · có phiên bản (git) · tự kéo về (pull, không push) · tự hàn gắn.
- Pull > push: không ai cần
kubeconfigprod; deploy = merge PR. - ArgoCD Application = (repoURL + path + targetRevision) → (cluster + namespace), kèm
syncPolicy. - Ba núm:
automated(tự sync),prune(xóa thứ đã gỡ khỏi git — ký hợp đồng),selfHeal(đè lại thay đổi tay — giết configuration drift). Synced≠Healthy— xem cả hai cột.- Deploy = commit · Rollback =
git revert· Audit =git log. - Nhiều app: App-of-Apps (bootstrap cả cluster) và ApplicationSet (sinh app cho nhiều môi trường/cluster — ghép thẳng với
values-<env>.yamlcủa Helm). - Secret: không bao giờ plain trong git → Sealed Secrets / External Secrets / SOPS.
- Helm + ArgoCD = cặp đôi: Helm đóng gói, ArgoCD đồng bộ & canh giữ.
Từ chặng Docker (một máy) → Kubernetes (một cụm tự phục hồi) → Helm (đóng gói có phiên bản) → GitOps (git điều khiển tất cả), bạn đã đi trọn con đường từ "chạy được" tới "vận hành được ở quy mô đội nhóm".
Hai mảnh còn thiếu để khép vòng tròn hiện đại: CI/CD (pipeline tự build image → cập nhật manifest → ArgoCD lo phần còn lại) và Observability (Prometheus/Grafana/Loki — để khi có sự cố, bạn thấy được thay vì đoán). Đó là những gì đang chờ ở phía trước.
Nếu hôm nay bạn kubectl scale một Deployment và thấy ArgoCD lặng lẽ kéo nó về đúng git trong vài giây — bạn vừa chứng kiến configuration drift chết ngay trước mắt. Và bạn sẽ không muốn quay lại cách cũ nữa.
— Minh Hưng