傳統的 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-password

Application 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: 3m

Helm 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: true

Kustomize 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: staging

App 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

面向ArgoCDFlux
Web UI有(很好用)沒有(靠 CLI 或第三方 UI)
安裝複雜度
資源消耗較多(UI + API server)較少
Multi-cluster支援支援
Helm 支援完整完整
Image Automation需要 ArgoCD Image Updater內建
學習曲線低(UI 直覺)中(全 CLI)
社群CNCF graduatedCNCF 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 main

ArgoCD 偵測到 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: IgnoreExtraneous

Webhook 沒設,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,合理得不能再合理。


延伸閱讀


本系列文章