🏠 Bài tập về nhà: Sealed Secrets + mở rộng hệ thống.
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 đó.
Toàn hệ thống mô tả khai báo (YAML), không phải chuỗi lệnh thủ tục.
Desired state trong Git, có version, revert được; mỗi commit là bản chụp bất biến.
Agent trong cluster kéo trạng thái từ Git, thay vì CI đẩy vào.
Agent liên tục quan sát & sửa lệch (drift) — không chỉ apply một lần.
| Tiêu chí | Push (CI) | Pull (GitOps) |
|---|---|---|
| Ai apply | Pipeline CI ngoài cluster | Agent trong cluster tự kéo |
| Credential cluster | Tập trung ở CI — bề mặt tấn công lớn | Không rời cluster |
| Drift | Không phát hiện | Phát hiện & tự sửa |
| Audit | Rải rác trong log CI | Toàn bộ là commit Git |
| Scale nhiều cluster | CI giữ creds mọi cluster | Mỗi cluster 1 agent |
kubectl apply.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.| Thành phần | Vai trò |
|---|---|
| application-controller | Reconciliation, so desired vs live, sync, health |
| repo-server | Clone repo, render manifest. Cần egress OCI/Git |
| applicationset-ctrl | Sinh Application từ generator |
| argocd-server | API + Web UI + auth (SSO/Dex) |
| redis | Cache trạng thái/manifest |
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}}
sourceRepos — repo nào được phép làm nguồndestinations — cặp cluster + namespace được deployclusterResourceWhitelist / namespaceResourceWhitelistplatform đượ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.argocd.argoproj.io/sync-wave: "<n>" — wave nhỏ sync trước. Xếp thứ tự CRD/controller trước workload.
Wave là rào cản cứng trong một Application, không xuyên nhiều Application.
Synced / OutOfSync
Live có khớp Git không?
Healthy / Progressing / Degraded / Missing
Tài nguyên có khoẻ không?
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-nonproduction.yaml / root-production.yaml trỏ bootstrap/<tier> — chính là App-of-Apps.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}}'}
Application. Muốn sinh AppProject/appset con phải đi vòng: appset → App → manifest.| Generator | Sinh app theo | Dùng khi |
|---|---|---|
| list | danh sách khai báo tay | tập cố định (vd platform component) |
| git (directories) | mỗi thư mục → 1 app | env/app/project là thư mục |
| git (files) | mỗi file config → 1 app | tham số hoá theo file YAML |
| cluster | mỗi cluster đã đăng ký | rải ra nhiều cluster |
| scm provider | repo trong 1 org/group | auto-discover nhiều repo |
| pull request | mỗi PR mở | preview-env ephemeral |
| matrix / merge | kết hợp nhiều generator | ghép trục (vd cluster × app) |
| Tiêu chí | App of Apps | ApplicationSet |
|---|---|---|
| Bản chất | Pattern (App cha → thư mục con) | Controller + CRD |
| Nguồn app | File YAML tĩnh viết sẵn | Template + generator (sinh động) |
| Thêm app mới | Viết thêm 1 file tay | Tự sinh (thêm thư mục/cluster) |
| Sinh được gì | Mọi resource (kể cả AppProject) | Chỉ Application |
App-of-Apps / ApplicationSet
directory-based / branch-based
Hai trường phái — và cái nào hợp cho môi trường.
| Tiêu chí | Directory-based | Branch-based |
|---|---|---|
| Env biểu diễn bằng | thư mục trên 1 branch | branch riêng |
| Promotion | PR sửa file overlay | merge giữa branch |
| Diff chéo | dễ, thấy hết cùng lúc | khó, phải so branch |
| Nguy cơ phân kỳ | thấp | cao (merge drift) |
mainproject, app, env đều là thư mục: apps/<project>/<app>/overlays/<env>/.
2 tier giống hệt, chỉ khác env filter:root-nonproduction → dev + stagingroot-production → prod
15 phút — quay lại với phần thực hành.
| Thư mục | Vai trò | Phát hành |
|---|---|---|
| helm-charts/app | 1 base chart đa năng (multi-deploy) | sửa chart + commit main (ảnh hưởng 2 tier) |
| apps/.../overlays/<env> | overlay env, deploy thật | sửa values + commit main |
| bootstrap/<tier> | App-of-Apps + appset per tier | sửa khi thêm project/app/env |
| platform/ | shared gateway + httproutes | sửa khi thêm route/gateway |
values.yaml.| Project | Apps | nonprod (dev+staging) | prod |
|---|---|---|---|
| birdnet-market | frontend, backend | 4 | 2 |
| mention-mate | app (backend+worker) | 2 | 1 |
{project}-{app}-{env} · namespace = {project}-{env} · AppProject = {project}. → 6 app nonprod, 3 app prod.componentsportconfighttpRoute.enabled (trỏ shared-gw)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.# 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
# 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>
| Triệu chứng | Nguyên nhân | Xử lý |
|---|---|---|
| kgateway ComparisonError OCI | chưa add OCI repo | argocd repo add ... --enable-oci |
| CRDs OutOfSync / SSA conflict | thiếu ServerSideApply | bật ServerSideApply=true |
| workload không sinh | path/env filter sai | kiểm tra directories của appset |
| destination not permitted | namespace chưa whitelist | thêm namespace vào AppProject |
| HTTPRoute không attach | hostname ngoài *.duongot.work | sửa hostname / đợi gateway Ready |
birdnet-market/frontend — 1 component appport → Service; httpRoute → route qua shared-gwvalues.yaml: replicas, hostname, image.tagconfig.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]
| birdnet-market/frontend | mention-mate/app | |
|---|---|---|
| Số component | 1 (app) | 2 (backend + worker) |
| Số Deployment | 1 | 2 |
| Service | có (port) | chỉ backend (có port) |
| Base chart | cùng 1 chart helm-charts/app — khác biệt ở values.yaml | |
components, không đụng base chart. Secret chung qua envFrom SealedSecret của release.# overlays/prod/values.yaml components.app.replicaCount: 3 → 5 git commit -am "promote" && git push
kubectl -n birdnet-market-dev scale \
deploy ...-frontend-dev-app --replicas=5
# OutOfSync → ArgoCD tự về 1 replicamain (không merge branch). selfHeal + prune bật sẵn → cluster luôn hội tụ về Git.Sealed Secrets + mở rộng hệ thống.
# 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
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.repoURL khớp; bootstrap cả 2 tier (6 app nonprod, 3 app prod).uat cho frontend: tạo overlay + thêm path vào directories của appset → app birdnet-market-frontend-uat tự sinh.scheduler vào mention-mate/app: thêm khoá vào components (không sửa base chart).pipeline cho birdnet-market → appset tự quét.notification: app + appproject + per-project appset → tự sinh ở cả 2 tier.Lab: gitops-training — single branch, directory-based, 2 tier theo cluster · Tài liệu chi tiết kèm theo.
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?