傳統的 CI/CD 是「push model」:CI pipeline 跑完後用 kubectl apply 把 YAML 推到 K8s。問題是——你怎麼知道叢集裡跑的跟 Git 裡的是一樣的?有人手動 kubectl edit 改了一下、有人直接 kubectl scale 調了副本數——叢集的實際狀態跟 Git 慢慢就不一致了。GitOps 解決的就是這個問題。
先講結論
GitOps = Git 是唯一的 truth source。ArgoCD 監聽 Git repo,發現 Git 跟叢集不一致就自動(或手動)sync。好處:可審計(所有變更都有 commit)、可回滾(git revert)、多環境管理(不同 branch/path 對應不同環境)。ArgoCD 有 Web UI,Flux 更輕量但沒 UI。大部分團隊選 ArgoCD。
為什麼 GitOps
傳統 CI/CD(Push Model)
Developer → git push → CI build → CI run tests → CI kubectl apply → K8s
問題:
- CI pipeline 有 cluster-admin 權限(安全風險)
- 有人手動
kubectl改了東西,Git 不知道 - 叢集狀態跟 Git 慢慢 drift
- 回滾要重跑 pipeline
GitOps(Pull Model)
Developer → git push → ArgoCD 偵測到變更 → ArgoCD sync 到 K8s
好處:
- 可審計:所有變更都是 Git commit,誰改了什麼一清二楚
- 可回滾:
git revert一行搞定,不用找 pipeline 重跑 - Drift detection:有人手動改了叢集,ArgoCD 會偵測到並修正
- 安全:CI 不需要 cluster 權限,只需要 push 到 Git 的權限
- 多環境:同一個 repo,不同 path 或 branch 對應不同環境
安裝 ArgoCD
基本安裝
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml用 Helm 安裝(推薦,更好管理)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
-f values-argocd.yaml# values-argocd.yaml
server:
ingress:
enabled: true
ingressClassName: nginx
hosts:
- argocd.example.com
tls:
- secretName: argocd-tls
hosts:
- argocd.example.com
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
configs:
params:
server.insecure: false
repositories:
my-repo:
url: https://github.com/myorg/k8s-manifests.git
type: git
rbac:
policy.default: role:readonly
policy.csv: |
p, role:dev-team, applications, get, */*, allow
p, role:dev-team, applications, sync, */*, allow
g, dev-team, role:dev-team取得初始密碼
# ArgoCD 會自動建立一個 admin 帳號,密碼存在 Secret 裡
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# 安裝 argocd CLI
brew install argocd
# 登入
argocd login argocd.example.com
# 改密碼
argocd account update-passwordApplication YAML
ArgoCD 的核心物件是 Application——定義「哪個 Git repo 的哪個 path,部署到哪個 K8s cluster 的哪個 namespace」。
基本 Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-server
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: apps/api-server/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Git 裡刪掉的資源,K8s 裡也刪
selfHeal: true # 有人手動改了,自動修正回 Git 的狀態
allowEmpty: false # 不允許同步到空的 manifest
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3mHelm Chart Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prometheus
namespace: argocd
spec:
project: default
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 58.x.x
helm:
releaseName: kube-prometheus-stack
valueFiles:
- values-prod.yaml # 從 Git repo 讀 values file
values: |
grafana:
adminPassword: "${GRAFANA_PASSWORD}"
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: trueKustomize Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-server-staging
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: apps/api-server/overlays/staging
kustomize:
namePrefix: staging-
images:
- myapp/api:staging-latest
destination:
server: https://kubernetes.default.svc
namespace: stagingApp of Apps 模式
你有 10 個服務,不會想手動建 10 個 Application YAML。App of Apps 模式:一個 “root” Application 管理所有其他 Application。
Repo 結構
k8s-manifests/
├── root-app.yaml # Root Application
├── apps/
│ ├── api-server.yaml # Application for api-server
│ ├── web-frontend.yaml # Application for web
│ ├── worker.yaml # Application for worker
│ ├── prometheus.yaml # Application for monitoring
│ └── ingress-nginx.yaml # Application for ingress
└── manifests/
├── api-server/
│ ├── base/
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── production/
├── web-frontend/
│ ├── base/
│ └── overlays/
└── ...
Root Application
# root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: apps # 這個目錄下所有 YAML 都會被 apply
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true# apps/api-server.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-server
namespace: argocd
labels:
team: backend
env: production
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: manifests/api-server/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true新增一個服務只需要在 apps/ 目錄加一個 YAML 檔。ArgoCD 的 root app 會自動偵測到並建立對應的 Application。
Sync Policy:自動 vs 手動
自動 Sync
syncPolicy:
automated:
prune: true # Git 刪了的資源,K8s 也刪
selfHeal: true # 偵測到 drift 自動修正適合:dev / staging 環境。Git push 就自動部署,快速迭代。
手動 Sync
syncPolicy: {} # 不設 automated適合:production 環境。Git push 後 ArgoCD 會標示 “OutOfSync”,但不會自動部署。需要人手動在 UI 或 CLI 點 Sync。
# CLI 手動 sync
argocd app sync api-server
# sync 到特定 revision
argocd app sync api-server --revision v1.3.0我的建議:
- dev → 全自動(automated + prune + selfHeal)
- staging → 自動 sync,但不 prune(避免誤刪)
- production → 手動 sync(多一層人工確認)
多環境管理
方式 1:Kustomize overlays(推薦)
manifests/api-server/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
├── staging/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
└── production/
├── kustomization.yaml
├── patch-replicas.yaml
└── patch-resources.yaml
每個環境一個 Application,指向不同的 overlay path。
方式 2:Helm values per env
# apps/api-server-dev.yaml
spec:
source:
path: charts/api-server
helm:
valueFiles:
- values.yaml
- values-dev.yaml
# apps/api-server-prod.yaml
spec:
source:
path: charts/api-server
helm:
valueFiles:
- values.yaml
- values-prod.yaml方式 3:Branch per env(不推薦)
main → prod、develop → staging。問題是 cherry-pick 很煩,而且環境之間的差異不容易比較。用 path/overlay 比 branch 好管理。
ArgoCD vs Flux
| 面向 | ArgoCD | Flux |
|---|---|---|
| Web UI | 有(很好用) | 沒有(靠 CLI 或第三方 UI) |
| 安裝複雜度 | 中 | 低 |
| 資源消耗 | 較多(UI + API server) | 較少 |
| Multi-cluster | 支援 | 支援 |
| Helm 支援 | 完整 | 完整 |
| Image Automation | 需要 ArgoCD Image Updater | 內建 |
| 學習曲線 | 低(UI 直覺) | 中(全 CLI) |
| 社群 | CNCF graduated | CNCF graduated |
我的建議:大部分團隊選 ArgoCD。UI 是殺手級功能——你可以在 UI 上看到每個 app 的 sync 狀態、resource tree、diff、health,不用敲一堆 kubectl。Flux 適合純 CLI 工作流或者對資源消耗很敏感的環境。
完整的 GitOps Workflow
1. Developer 改 code → PR → code review → merge to main
↓
2. CI pipeline build image → push to registry → 更新 manifest repo 的 image tag
↓
3. ArgoCD 偵測到 manifest repo 變更
↓
4a. dev/staging: 自動 sync
4b. production: ArgoCD 顯示 OutOfSync → 人工確認 → Sync
↓
5. ArgoCD 執行 sync → kubectl apply
↓
6. 有問題 → git revert → ArgoCD 自動 sync 回上一版
CI 更新 Image Tag 的 Script
#!/bin/bash
# update-manifest.sh — CI pipeline 呼叫
NEW_TAG=$1
REPO="myorg/k8s-manifests"
APP_PATH="manifests/api-server/base"
# Clone manifest repo
git clone https://github.com/${REPO}.git
cd k8s-manifests
# 更新 image tag(用 kustomize)
cd ${APP_PATH}
kustomize edit set image myapp/api=myapp/api:${NEW_TAG}
# Commit & push
git add .
git commit -m "chore: update api-server image to ${NEW_TAG}"
git push origin mainArgoCD 偵測到 commit 後就會自動(或等手動)sync。
常見踩坑
Sync 失敗但 UI 沒有顯示詳細錯誤:點進去看 “Sync Status” 的 detail,或用 CLI argocd app get <name>。
prune 刪掉不該刪的東西:某些資源不應該被 ArgoCD 管理(像是 cert-manager 自動建的 Secret)。加 annotation:
metadata:
annotations:
argocd.argoproj.io/compare-options: IgnoreExtraneousWebhook 沒設,ArgoCD 要等 3 分鐘才偵測到變更:預設 ArgoCD 每 3 分鐘 poll 一次 Git。設 GitHub webhook 可以做到秒級偵測。
Sealed Secret 跟 ArgoCD 衝突:ArgoCD 偵測到 Secret 內容跟 Git 不同(因為 controller 解密了),一直 OutOfSync。在 Application 裡設 ignoreDifferences 排除 Secret 的 data 欄位。
GitOps 不是什麼新發明——它只是把 “Infrastructure as Code” 的理念徹底執行。Git 已經是你的 code 的 truth source,讓它也成為你的基礎設施的 truth source,合理得不能再合理。
延伸閱讀
本系列文章
- ← 上一篇:Troubleshooting:從 Pending 到 CrashLoopBackOff
- 本篇:GitOps:ArgoCD 宣告式部署
- 回到 K8s 實戰系列目錄