GitOps với ArgoCD

Nguyên tắc, kiến trúc ArgoCD, các chiến lược tổ chức repo, và triển khai đa môi trường với App of Apps, ApplicationSet và Helm.
4 giờ · Lý thuyết + thực hành trực tiếp · Lab gitops-training
Nội dung

Cấu trúc buổi học

PHẦN A · LÝ THUYẾT · 110'
  • GitOps: nguyên tắc, push-based vs pull-based, reconciliation
  • ArgoCD deep dive: kiến trúc, Application CRD, sync wave
  • So sánh App of Apps vs ApplicationSet
  • So sánh directory-based vs branch-based
PHẦN B · THỰC HÀNH · 105'
  • Giới thiệu lab: 2 tier, chuỗi sinh 3 tầng
  • Dựng Kubernetes, cài ArgoCD, deploy root application
  • Deploy app đa môi trường: single & multi component
  • Promotion, drift & self-heal

🏠 Bài tập về nhà: Sealed Secrets + mở rộng hệ thống.

Hết phần lý thuyết nghỉ giải lao 15', rồi chuyển sang thực hành.
01

GitOps — nguyên tắc cốt lõi

Định nghĩa

GitOps là gì?

Git là single source of truth cho trạng thái mong muốn, và một controller liên tục đối chiếu trạng thái thực tế của cluster với Git rồi tự hội tụ về đúng trạng thái đó.

Không phải "CI/CD gắn với Git". Khác biệt bản chất: pull model + continuous reconciliation.
OpenGitOps

Bốn nguyên tắc

1 · Declarative

Toàn hệ thống mô tả khai báo (YAML), không phải chuỗi lệnh thủ tục.

2 · Versioned & Immutable

Desired state trong Git, có version, revert được; mỗi commit là bản chụp bất biến.

3 · Pulled automatically

Agent trong cluster kéo trạng thái từ Git, thay vì CI đẩy vào.

4 · Continuously reconciled

Agent liên tục quan sát & sửa lệch (drift) — không chỉ apply một lần.

Mô hình triển khai

Push-based vs Pull-based

Tiêu chíPush (CI)Pull (GitOps)
Ai applyPipeline CI ngoài clusterAgent trong cluster tự kéo
Credential clusterTập trung ở CI — bề mặt tấn công lớnKhông rời cluster
DriftKhông phát hiệnPhát hiện & tự sửa
AuditRải rác trong log CIToàn bộ là commit Git
Scale nhiều clusterCI giữ creds mọi clusterMỗi cluster 1 agent
Push vs Pull · đánh đổi

Mạnh, yếu & cách kết hợp

Push — mạnh

  • Đơn giản khi mới bắt đầu
  • Dễ chèn bước imperative (script, gọi API)

Push — yếu

  • Creds cluster tập trung ở CI
  • Không thấy drift, khó audit, khó scale

Pull — mạnh

  • Creds ở trong cluster, drift tự sửa
  • Mọi thay đổi revert được, scale tốt

Pull — yếu

  • Học khó hơn
  • Thao tác imperative phải gói thành hook/Job
Thực tế hay kết hợp: CI ghi vào Git, GitOps đọc từ Git. Đừng để CI vừa build vừa kubectl apply.
Vòng lặp đối chiếu

Reconciliation & Drift

desired state (Git) ───────────────┐ ▼ observe ───► diff ───► act (sync) ───► live state ▲ │ └──────────────── loop ───────────────┘
Hay gặp: hard-code replicas trong Git mà vẫn bật HPA → selfHeal liên tục revert. Cách xử lý: bỏ replicas khỏi manifest hoặc ignoreDifferences field /spec/replicas.
02

ArgoCD deep dive

Kiến trúc

Các thành phần ArgoCD

Thành phầnVai trò
application-controllerReconciliation, so desired vs live, sync, health
repo-serverClone repo, render manifest. Cần egress OCI/Git
applicationset-ctrlSinh Application từ generator
argocd-serverAPI + Web UI + auth (SSO/Dex)
redisCache trạng thái/manifest
Git/OCI │ ▼ repo-server │ rendered ▼ app-controller ─► K8s API │ ▲ ▼ │ live argocd-server(UI) ▲ │ user
Lab: mỗi cluster có một ArgoCD riêng, mọi destination là in-cluster. repo-server là nút thắt cổ chai khi repo lớn → scale replica + cache.
CRD trung tâm

Application — mổ xẻ

spec:
  project: mention-mate            # thuộc AppProject nào (RBAC, repo/dest cho phép)
  sources:                         # multi-source — CẢ HAI cùng repo, cùng main
    - repoURL: .../gitops-training
      path: helm-charts/app        # 1 base chart duy nhất, không version
      targetRevision: main
      helm: {valueFiles: [$values/apps/.../values.yaml]}
    - repoURL: .../gitops-training
      targetRevision: main           # bắt buộc cùng main: $values không trỏ revision khác
      ref: values
  destination: {namespace: mention-mate-dev}
  syncPolicy: {automated: {prune: true, selfHeal: true}}
Hàng rào multi-tenant

AppProject

Lab: project platform được tạo cluster-scoped resource (CRD, Gateway); project app chỉ được tạo namespaced resource trong <project>-*. Quên whitelist namespace → lỗi destination not permitted.
Thứ tự & lifecycle

Sync Waves & Hooks

Sync waves

argocd.argoproj.io/sync-wave: "<n>" — wave nhỏ sync trước. Xếp thứ tự CRD/controller trước workload.

-2 AppProjects -1 platform (SSA) 0 kgateway + projects 1 shared-gateway 2 httproutes

Sync hooks

  • PreSync — migrate DB trước deploy
  • PostSync — smoke test sau deploy
  • SyncFail — cleanup/thông báo

Wave là rào cản cứng trong một Application, không xuyên nhiều Application.

Hai trục độc lập

Health vs Sync status

Sync status

Synced / OutOfSync
Live có khớp Git không?

Health status

Healthy / Progressing / Degraded / Missing
Tài nguyên có khoẻ không?

Tách biệt: có thể Synced nhưng Degraded (đúng Git nhưng pod crash do sai image/env). Sync không cứu — phải xem log pod.
03

App of Apps & ApplicationSet

Pattern

App of Apps

Một Application "cha" trỏ tới thư mục chứa các manifest con. Sync cha → tạo/cập nhật loạt con. Là pattern, không có CRD riêng.

root-<tier> (Application, recurse:false) ──► bootstrap/<tier>/ ├─ appprojects / platform / kgateway └─ all-projects / shared-gateway / httproutes
Lab: root-nonproduction.yaml / root-production.yaml trỏ bootstrap/<tier> — chính là App-of-Apps.
Controller

ApplicationSet

CRD + controller sinh Application từ template theo generator (động, theo dữ liệu).

generators: [ ... ]      # nguồn dữ liệu sinh app
template:                  # khuôn Application, nội suy {{...}}
  metadata: {name: '{{.project}}-{{.app}}-{{.env}}'}
Giới hạn: chỉ sinh được Application. Muốn sinh AppProject/appset con phải đi vòng: appset → App → manifest.
Generators

Các generator chính

GeneratorSinh app theoDùng khi
listdanh sách khai báo taytập cố định (vd platform component)
git (directories)mỗi thư mục → 1 appenv/app/project là thư mục
git (files)mỗi file config → 1 apptham số hoá theo file YAML
clustermỗi cluster đã đăng kýrải ra nhiều cluster
scm providerrepo trong 1 org/groupauto-discover nhiều repo
pull requestmỗi PR mởpreview-env ephemeral
matrix / mergekết hợp nhiều generatorghép trục (vd cluster × app)
Lab dùng git-directories ở cả 3 tầng + list cho platform component.
So sánh

App of Apps vs ApplicationSet

Tiêu chíApp of AppsApplicationSet
Bản chấtPattern (App cha → thư mục con)Controller + CRD
Nguồn appFile YAML tĩnh viết sẵnTemplate + generator (sinh động)
Thêm app mớiViết thêm 1 file tayTự sinh (thêm thư mục/cluster)
Sinh được gìMọi resource (kể cả AppProject)Chỉ Application
Giống: cùng quản lý nhiều Application khai báo · bootstrap 1 lần · lồng nhau được.
Mạnh / yếu / use case

Chọn cái nào?

App of Apps

  • ✅ Đơn giản, sinh được mọi resource
  • ❌ Không tự sinh, nhiều boilerplate
  • Use case: bootstrap nền tảng, tập app ít/không đồng dạng

ApplicationSet

  • ✅ Tự sinh theo dữ liệu, ít lặp
  • ❌ Chỉ sinh Application, khó debug hơn
  • Use case: nhiều app/env/cluster đồng dạng, preview-env theo PR
Không loại trừ nhau — lab dùng chung: App-of-Apps (root) → ApplicationSet (nền tảng) → ApplicationSet (per-project) → workload.
Điểm hay nhầm nhất

Hai trục VUÔNG GÓC

TRỤC A

Quản lý nhiều Application

App-of-Apps / ApplicationSet

TRỤC B

Cấu trúc repo cho môi trường

directory-based / branch-based

Độc lập nhau: ApplicationSet + git-directory generator chính là cách hiện thực directory-based. Đừng gộp 2 khái niệm.
04

Directory-based
vs branch-based

Biểu diễn nhiều môi trường (dev / staging / prod) trong Git ra sao?

Hai trường phái — và cái nào hợp cho môi trường.

Hai trường phái

TRƯỜNG PHÁI 1

Directory-based

  • Mỗi env là một thư mục trên cùng branch
  • Promotion = PR đổi file overlay env đích
  • Generator: git-directory
main: overlays/ ├ dev/ ├ staging/ └ prod/ ◄ bump tag
TRƯỜNG PHÁI 2

Branch-based

  • Mỗi env là một branch dài hạn
  • Promotion = merge branch này sang branch kia
  • Generator: list / scm / pull-request
env/dev ──┐ env/staging ──┼ merge env/prod ──┘
So sánh

Directory vs Branch

Tiêu chíDirectory-basedBranch-based
Env biểu diễn bằngthư mục trên 1 branchbranch riêng
PromotionPR sửa file overlaymerge giữa branch
Diff chéodễ, thấy hết cùng lúckhó, phải so branch
Nguy cơ phân kỳthấpcao (merge drift)
Giống: cùng tổ chức desired state nhiều biến thể · đều có version, revert được · đều chạy với ArgoCD.
Mạnh / yếu / use case

Mỗi cái hợp với gì

Directory-based

  • ✅ Nhìn toàn cảnh, ít merge drift
  • ❌ Hạn quyền theo env cần CODEOWNERS/path rule
  • Use case: môi trường (dev/staging/prod)

Branch-based

  • ✅ Branch protection theo env tự nhiên
  • ❌ Cấu hình dễ phân kỳ, hay cherry-pick
  • Use case: feature/code, preview-env theo PR
Branch hợp cho FEATURE; directory hợp cho ENV — cộng đồng coi branch-per-env là anti-pattern.
Lab áp dụng

Directory-based + 2 tier theo cluster

Một branch main

project, app, env đều là thư mục: apps/<project>/<app>/overlays/<env>/.

Tách prod bằng cluster

2 tier giống hệt, chỉ khác env filter:
root-nonproduction → dev + staging
root-production → prod

Cô lập prod bằng cluster + ArgoCD riêng, không bằng branch. Giải pháp khác đáng cân nhắc: repo-per-team + SCM generator, tách config repo khỏi source repo.

☕ Giải lao

15 phút — quay lại với phần thực hành.

05

Giới thiệu bài thực hành

Một repo, hai trách nhiệm

gitops-training · single branch

Thư mụcVai tròPhát hành
helm-charts/app1 base chart đa năng (multi-deploy)sửa chart + commit main (ảnh hưởng 2 tier)
apps/.../overlays/<env>overlay env, deploy thậtsửa values + commit main
bootstrap/<tier>App-of-Apps + appset per tiersửa khi thêm project/app/env
platform/shared gateway + httproutessửa khi thêm route/gateway
Platform team làm base chart tái sử dụng; app team chỉ khai báo cấu hình theo môi trường — cùng repo, mỗi env 1 values.yaml.
Hai tier theo cluster

Một lệnh apply / cluster

ProjectAppsnonprod (dev+staging)prod
birdnet-marketfrontend, backend42
mention-mateapp (backend+worker)21
Application = {project}-{app}-{env} · namespace = {project}-{env} · AppProject = {project}. → 6 app nonprod, 3 app prod.
Chuỗi sinh 3 tầng

Bootstrap chain (mỗi tier)

kubectl apply root-<tier>.yaml (1 lần / cluster, chạy tay) │ recurse:false → đọc 6 file cấp 1 bootstrap/<tier>/ ▼ root-<tier> (App-of-Apps) ├─ appprojects (appset) ─► App ─► AppProject wave -2 ├─ platform (appset, SSA) ─► sealed-secrets + kgateway-crds -1 ├─ kgateway (App, KHÔNG SSA) ─► controller 0 ├─ all-projects (appset) ─► App ─► appset của project 0 ├─ shared-gateway (App) ─► Gateway shared-gw 1 └─ httproutes (App) ─► HTTPRoute đứng riêng 2 │ (tầng 3) per-project appset quét apps/<proj>/*/overlays/<env> ▼ workload {project}-{app}-{env} (multi-source, cả hai = main)
Base chart

1 chart cho mọi app

  • Render theo map components
  • Mỗi component → 1 Deployment
  • + Service nếu có port
  • + ConfigMap nếu có config
  • + HTTPRoute nếu httpRoute.enabled (trỏ shared-gw)
  • 1 SealedSecret dùng chung cả release
components:
  backend:
    image: {repository: ..., tag: ...}
    port: 80
    httpRoute: {enabled: true}
  worker:                # không port → chỉ Deployment
    image: {repository: ...}
components mặc định {} (cố ý) — Helm deep-merge, default khác rỗng sẽ rò vào mọi release.
06

Dựng K8s · ArgoCD · root

Demo · hạ tầng

Dựng cluster & cài ArgoCD

# 1) Cluster (lab: kind; production: EKS/GKE/AKS/kubeadm)
kind create cluster --name gitops-nonprod
kubectl get nodes

# 2) Cài ArgoCD (≥ 3.1 cho native OCI Helm; lab pin 3.3.x)
kubectl create namespace argocd
kubectl apply -n argocd -f .../argo-cd/v3.3.0/manifests/install.yaml
kubectl -n argocd rollout status deploy/argocd-server

# mật khẩu admin ban đầu
argocd admin initial-password -n argocd
Lab tối thiểu cần 1 cluster (demo được cả 2 tier); nhấn mạnh production tách nonprod/prod.
Demo · bootstrap

Đăng ký repo & deploy root

# repo gitops-training PUBLIC → clone không cần creds. Chỉ thêm OCI kgateway:
argocd repo add cr.kgateway.dev/kgateway-dev/charts --type helm --enable-oci

# 1 lệnh tay / cluster → cả chain tự sinh
kubectl apply -f root-nonproduction.yaml      # cluster nonprod
kubectl apply -f root-production.yaml          # cluster prod
argocd app list      # 6 app nonprod: <project>-<app>-<env>
Quan sát UI: tầng 1 (appprojects, platform, kgateway, all-projects, shared-gateway, httproutes) → tầng 2 (appproject/projectset) → tầng 3 (workload).
Demo · gỡ rối

Trục trặc bootstrap hay gặp

Triệu chứngNguyên nhânXử lý
kgateway ComparisonError OCIchưa add OCI repoargocd repo add ... --enable-oci
CRDs OutOfSync / SSA conflictthiếu ServerSideApplybật ServerSideApply=true
workload không sinhpath/env filter saikiểm tra directories của appset
destination not permittednamespace chưa whitelistthêm namespace vào AppProject
HTTPRoute không attachhostname ngoài *.duongot.worksửa hostname / đợi gateway Ready
07

Deploy & vận hành

Demo · single component

App đa môi trường

  • birdnet-market/frontend — 1 component app
  • port → Service; httpRoute → route qua shared-gw
  • Khác biệt env nằm trong values.yaml: replicas, hostname, image.tag
  • Config = config.data → ConfigMap mount tại mountPath; checksum/config đổi → pod rollout
# overlays/dev/values.yaml
components:
  app:
    image: {repository: traefik/whoami}
    replicaCount: 1
    port: 80
    httpRoute:
      enabled: true
      hostnames: [...-dev.duongot.work]
Demo · multi component

Nhiều Deployment trong 1 app

birdnet-market/frontendmention-mate/app
Số component1 (app)2 (backend + worker)
Số Deployment12
Servicecó (port)chỉ backend (có port)
Base chartcùng 1 chart helm-charts/app — khác biệt ở values.yaml
Thêm/bớt deployment = thêm/bớt khoá trong components, không đụng base chart. Secret chung qua envFrom SealedSecret của release.
Demo · vận hành

Promotion & Drift / Self-heal

Promotion (sửa overlay)

# overlays/prod/values.yaml
components.app.replicaCount:
  3 → 5
git commit -am "promote" && git push

Drift → self-heal

kubectl -n birdnet-market-dev scale \
  deploy ...-frontend-dev-app --replicas=5
# OutOfSync → ArgoCD tự về 1 replica
Promotion = commit sửa file overlay trên main (không merge branch). selfHeal + prune bật sẵn → cluster luôn hội tụ về Git.

🏠 Bài tập về nhà

Sealed Secrets + mở rộng hệ thống.

Bài tập · secrets

Sealed Secrets

secret thô ──kubeseal + public key──► SealedSecret (ciphertext) ──commit main │ ArgoCD sync ▼ controller (private key) giải mã ──► Secret thật ──► pod
# secret-name = <release>-secret = {project}-{app}-{env}-secret
./scripts/seal.sh mention-mate-dev mention-mate-app-dev-secret DB_PASSWORD=...
# dán encryptedData vào values.yaml; đặt sealedSecret.enabled: true
Lưu ý: scope strict gắn name+namespace · ciphertext gắn public key của chính cluster · mất private key controller = mất mọi secret → phải backup. Giải pháp khác: External Secrets, SOPS+age, Vault.
Bài tập · mở rộng

Tự làm sau buổi học

  1. Fork repo, sửa repoURL khớp; bootstrap cả 2 tier (6 app nonprod, 3 app prod).
  2. Thêm env uat cho frontend: tạo overlay + thêm path vào directories của appset → app birdnet-market-frontend-uat tự sinh.
  3. Thêm deployment scheduler vào mention-mate/app: thêm khoá vào components (không sửa base chart).
  4. Thêm app mới pipeline cho birdnet-market → appset tự quét.
  5. (Nâng cao) Thêm project notification: app + appproject + per-project appset → tự sinh ở cả 2 tier.

Cảm ơn & hỏi đáp

Lab: gitops-training — single branch, directory-based, 2 tier theo cluster · Tài liệu chi tiết kèm theo.

Gợi mở: vì sao chỉ root apply tay? · vì sao recurse:false? · vì sao CRDs dùng SSA còn controller thì không? · vì sao 2 source phải cùng main?
01 / 36
viettel · GitOps × ArgoCD
← → di chuyển · F toàn màn hình