從「推動式」轉向「聲明式」,順便看清它跟 CI 的分工。
傳統做法是在 Pipeline 最後一步執行 kubectl apply,把配置「推 (Push)」進叢集。這會衍生兩個嚴重問題:
kubectl edit 改了 replica 數,Git 裡的 YAML 跟實際環境就對不上了。三個月後沒人記得叢集到底長什麼樣,下次部署就是災難現場。GitOps 的核心理念一句話:「Git 是唯一的真理 (Single Source of Truth)」。
把一個叫 ArgoCD 的 Controller 裝在 K8S 裡面,它會定期去「拉 (Pull)」Git 倉庫的設定,跟叢集實際狀態比對。只要不一致,就把叢集「同步」成跟 Git 一模一樣。方向反過來之後,兩個痛點同時解掉:
先把身家背景交代清楚,因為「要不要把整套部署流程押在一個工具上」這種決定,值得先知道它是誰做的。
ArgoCD 是一個給 Kubernetes 用的宣告式 GitOps 持續交付工具,用 Go 寫的,以 controller 的形式住在叢集裡面。它不是外掛在 K8S 旁邊的服務,而是用 CRD 擴充 K8S 自己的 API——你之前看到的 kubectl get application -n argocd,那個 Application 就是 ArgoCD 新增的資源類型,跟 Deployment、Service 一樣是叢集裡的一等公民。
| 項目 | 說明 |
|---|---|
| 授權 | Apache License 2.0,完全開源,核心功能沒有付費牆 |
| 出身 | 2018 年由 Intuit 團隊開源 |
| 治理 | 捐給 CNCF,並於 2022 年 12 月畢業 (Graduated)——跟 Kubernetes、Prometheus 同一個等級 |
| 家族 | Argo 專案包含 Workflows、CD、Rollouts、Events 四個成員。Day 20 要用的 Argo Rollouts 就是同一家人,所以 UI 風格和操作邏輯是通的 |
CNCF 畢業這件事對選型來說是有意義的訊號:它代表專案有多家廠商共同維護、有明確的治理與安全流程,不是某家公司哪天不爽就能收回去的東西。
這是我自己一開始最混淆的地方——它們不是競爭對手,是分工。
| 比較項目 | GitHub Actions / Jenkins | ArgoCD |
|---|---|---|
| 定位 | 通用 CI(也能兼做 CD) | 只做 K8S 的 CD |
| 跑在哪 | 叢集外面(雲端 runner / 自架 server) | 叢集裡面 |
| 模式 | 推 (Push):流程跑到最後 kubectl apply |
拉 (Pull):controller 主動比對 Git |
| 憑證 | 需要 kubeconfig,等於握有叢集最高權限 | 只需要 Git 的讀取權限;叢集憑證不出叢集 |
| 什麼時候知道狀態 | 只有部署當下那一瞬間 | 持續比對,隨時知道現在跟 Git 差在哪 |
| 有人手動改叢集 | 完全不知情 | 標成 OutOfSync,開了 selfHeal 就改回去 |
| 能不能 build image | ✅ 這是它的主場 | ❌ 完全不做 |
| 回滾方式 | 重跑一次舊的 pipeline | 讓 Git 退回去(Day 20 詳談) |
Actions/Jenkins 擅長「把程式碼變成 image」,ArgoCD 擅長「讓叢集永遠等於 Git」。前者是一次性的動作,後者是持續的狀態維護。我們的架構就是兩個都用——這也是目前業界最常見的組合。
如果你想比較同類型的工具,GitOps 這個領域主要的另一個選擇是 Flux CD(同樣是 CNCF 畢業專案)。粗略的差別是 Flux 更輕、更「純 CLI / CRD」,ArgoCD 則有一個功能完整的 UI——如果你的團隊有非 K8S 專家需要看部署狀態,ArgoCD 的 UI 會方便很多。
概念很抽象?直接看實測。我在本機叢集註冊了一個 Application,指向這個 repo 的 k8s/base/observability(就是 Day 24 會用到的 Jaeger),並且故意不開自動同步——因為要拍的正是「Git 有、叢集還沒有」的那一刻。

▲ 剛註冊完:Status 是 Missing + OutOfSync,左側過濾器統計 OutOfSync 1、Missing 1
卡片上每一行都是 ArgoCD 的世界觀:Repository(哪個 repo)、Target Revision(哪個分支)、Path(哪個目錄)、Destination(哪座叢集)、Namespace(哪個命名空間)。這五個欄位就是「期望狀態」的完整定義,不多不少,GitOps 要設定的東西就這些。
此刻 ArgoCD 已經知道「應該長什麼樣」,也知道「現在長什麼樣」,而且兩者不一樣。但它什麼都沒做——因為我沒開自動同步,它現在只是個盡責的觀察者。
按下 Sync 之後:

▲ 同一張卡片:Missing + OutOfSync 變成 Healthy + Synced,並多出一行 Last Sync 時間
同一張卡片、同樣的五個欄位,只有狀態變了。「Git 裡寫什麼、叢集就長什麼樣」這句話,在畫面上是看得見的。
這裡有一組概念值得先建立,因為之後排查會一直用到:Sync 和 Health 是兩個獨立的維度。
兩者可以任意組合,每種組合代表的狀況不同:Synced 但 Degraded,代表你部署的正是 Git 裡那個壞掉的版本(Day 20 會實測這種情況);Healthy 但 OutOfSync,代表現在跑得好好的,但有人手動改過、或 Git 已經前進了。把這兩個維度混為一談,排查時很容易找錯方向。
至於資源樹長什麼樣、按下 Sync 的瞬間叢集發生什麼事——那是明天實作篇的主場。
第一次用大概只會用到 Sync 按鈕,但真正讓它從「自動 apply 工具」變成「部署平台」的是下面這些。
同步策略其實是三個獨立開關疊出來的:
syncPolicy:
automated:
prune: true # Git 裡刪掉的資源,叢集也跟著刪
selfHeal: true # 有人手動改叢集,自動改回來
新環境建議從手動開始,等你信得過這條鏈路再往上加。特別是 prune,它真的會刪東西——如果有人把某個資源從 Git 拿掉只是想「先註解起來試試看」,prune 會忠實地把 production 的那個資源刪掉。
UI 上那顆 DIFF 按鈕(或 CLI 的 argocd app diff)會逐個資源顯示 Git 與叢集的差異,像 code review 一樣。這是「聲明式」最舒服的地方:部署前你能確切知道這次會改到什麼,而不是按下去才知道。
預設情況下 ArgoCD 會一次把所有資源丟進去,但有些東西有順序依賴。Day 10 提過我們踩到的例子:Chart 裡有一張 cert-manager 的 ClusterIssuer,叢集還沒裝 cert-manager 就會直接報 no matches for kind。
解法是 sync wave,用 annotation 標一個數字,小的先跑:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1" # 比預設的 0 早一步
同一個 wave 內平行處理,前一個 wave 全部健康了才進下一個。再進階還有 hooks(PreSync / Sync / PostSync / SyncFail),可以在同步前跑資料庫遷移、同步後跑煙霧測試、失敗時自動通知。
有些欄位天生就會一直變,比對起來永遠不一致。我們自己就有一個現成的例子:Helm 模板裡的 deployed-at: {{ now }} Pod annotation,每次渲染出來的值都不同,ArgoCD 會覺得永遠有差異,開了 selfHeal 還會不停重新部署。
ignoreDifferences 就是拿來畫免戰牌的:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/template/metadata/annotations/deployed-at
常見用途還有:被 HPA 動態調整的 replicas(Day 25 會裝 HPA,如果 Git 寫死 replicas 又開 selfHeal,兩個控制器會打架)、被 webhook 注入的欄位、憑證輪替的 Secret。不是所有欄位都該由 Git 決定,這點在設計時很容易忽略。
App 詳情頁上那顆 HISTORY AND ROLLBACK 會列出每一次同步的紀錄(時間、commit、作者),可以直接選一筆退回去。
但這裡有個關鍵限制:開了 automated sync 的 App,ArgoCD 會擋住你回滾。原因很單純——就算讓你退回去,下一輪比對它又會把 Git 的版本推回來。想用這顆按鈕,得先把自動同步關掉。
這件事我在 Day 20 會用實測數字說得更完整:在 GitOps 底下,真正有效的回滾只有一種,就是讓 Git 本身退回去。
當你有十個微服務、三個環境的時候,手動維護三十個 Application 是災難。兩種解法:
我們的規模用不到,但值得知道它存在——順帶一提,applicationset-controller 就算你沒用它,官方安裝檔也會把它裝起來一直跑著。我自己的叢集裡它就默默壞了 96 天、重啟了四千多次都沒人發現,因為沒人用它,所以沒人在意。明天講安裝驗證時會把這個案例完整攤開。
引入 ArgoCD 後,我們的架構變得很乾淨:
GitHub Actions:只負責 build image + 改寫 Git 上的版本號(不碰叢集)
ArgoCD(住在叢集裡):盯著 Git,有變化就同步(不碰 CI)
職責徹底分離,憑證不出叢集。這裡分享一個我自己踩過的反面教材:Helm 化之前,CI 曾經改的是一個 ArgoCD 根本沒在看的目錄——CI 每次都「成功」,但 production 永遠是舊版,兩邊各自運作良好,就是沒接在一起,哭啊。GitOps 的鏈路是「CI 改的檔案」=「ArgoCD 看的檔案」,這條等式斷了整套就是空轉,強烈建議把它寫進 checklist 用工具檢查(我們後來就寫了 scripts/check-config-sync.py)。
今天把 ArgoCD 的 Why 講完了,順便交代它的身家背景,以及那些第一次用不會碰到、但遲早會需要的功能。三個重點:
prune 會真的刪東西、selfHeal 會讓你的手動操作失效(Day 20 會用實測告訴你失效得多快)明天來 How:實際安裝 ArgoCD、登入那個著名的章魚 UI、建立 Application。