
在給 Redis 部署做 GitOps 規(guī)范化的時候我把redis-deployment倉庫推上去之后突然意識到一個問題ArgoCD 到底是怎么知道這個倉庫有改動的我的第一反應(yīng)是ArgoCD 輪詢 Git 倉庫比對 hash但仔細(xì)一想又不對——我明明有兩個倉庫在參與這件事my-argocd-manifests管 Application 定義和redis-deployment管實(shí)際 Manifest。那 ArgoCD 到底輪詢哪一個這篇文章把我從懷疑到查源碼、再到上集群實(shí)證的完整過程記錄下來。結(jié)論可能會顛覆你對 ArgoCD 的直覺ArgoCD 不是輪詢一個倉庫而是兩層各自獨(dú)立的輪詢循環(huán)它也不是靠保存 hash 對比來檢測改動Git 實(shí)時生成才是真相。一、 背景我的 ArgoCD 到底長什么樣先交代我的實(shí)際環(huán)境后面所有例子都是真實(shí)資源ArgoCD 控制面阿里云 K3s 集群App-of-Apps 模式root-bootstrapApplication 管著my-argocd-manifests.git的argocd-apps/目錄目標(biāo)集群tencent-dp1-cluster橫跨騰訊云vm-0-2-debian控制面、OCI ARMfree-arm-vm、本地 NUC 三個節(jié)點(diǎn)子應(yīng)用fastapi-svc、quarkus-svc、kong-gateway-infra、kong-ingress-controller、gateway-api-crds共 5 個待接入redis-deployment.gitk8s/redis.yaml打算把 Redis 部署到 free-arm-vm經(jīng) Kong Gateway 6379 Stream 對外我當(dāng)時給 README 寫ArgoCD 輪詢的是每個 Application 自己的 source.repoURL不是統(tǒng)一倉庫這句話時被自己問住了那root-bootstrap輪詢my-argocd-manifests又算什么兩個倉庫都會被輪詢嗎還是有一個是假的二、 雙層輪詢發(fā)現(xiàn)層與同步層各干各的答案是兩個倉庫都會被輪詢但輪詢的主體不同職責(zé)完全不同。這是 ArgoCD App-of-Apps 模式最核心、也最容易搞混的機(jī)制。第一層root-bootstrap發(fā)現(xiàn)層—— 輪詢 my-argocd-manifests# argocd-apps/root-bootstrap-app.yamlspec:source:repoURL:https://github.com/nvd11/my-argocd-manifests.gittargetRevision:HEADpath:argocd-apps它的職責(zé)只有一件事盯住argocd-apps/目錄看里面有沒有新的 Application 文件。我往里加一個redis-app.yamlroot-bootstrap 下一輪輪詢發(fā)現(xiàn)新文件就在集群里創(chuàng)建一個redis的 Application 對象。注意這一層不解析、不連接、不驗(yàn)證 redis-deployment.git。repoURL 對它來說只是 Application 對象里的一個字段它照抄進(jìn)對象里就完事。第二層子 Application同步層—— 輪詢各自 source.repoURL# 我 README 里規(guī)劃的 redis-app.yaml示意spec:source:repoURL:https://github.com/nvd11/redis-deployment.gitpath:k8s一旦redisApplication 對象被創(chuàng)建application-controller 就為它單獨(dú)開一個輪詢循環(huán)去輪詢 redis-deployment.git 的k8s/目錄把 Deployment/PVC/Service/TCPIngress 同步到目標(biāo)集群。我現(xiàn)有的fastapi-svc-app.yaml就是活證據(jù)——它的source.repoURL指向my-shared-helm-charts.git而不是my-argocd-manifests。如果 ArgoCD 只輪詢 my-argocd-manifestsfastapi 的鏡像永遠(yuǎn)不可能被同步。目標(biāo)集群 tencent-dp1-clusterArgoCD 控制面 阿里云 K3sGit: redis-deployment.gitGit: my-argocd-manifests.git第一層輪詢 180s發(fā)現(xiàn)新 Application 文件創(chuàng)建/更新對象第二層輪詢 180s檢測 k8s/ 變更自動 syncargocd-apps/ 目錄Application 定義文件k8s/ 目錄實(shí)際 Manifestroot-bootstrapApplicationredisApplication 對象Redis Podfree-arm-vm兩個關(guān)鍵結(jié)論兩個循環(huán)完全解耦不是父級掃完通知子級的串行。父級只管 Application 對象的增刪改子級只管資源同步。改redis-app.yaml的 syncPolicy → 父級發(fā)現(xiàn)改k8s/redis.yaml的鏡像 → 父級完全無感是子級自己的事。首次部署 Redis 最壞要等兩個 3 分鐘父級 3 分鐘撿到新文件創(chuàng)建對象子級對象從創(chuàng)建那一刻才開始自己的輪詢又是最多 3 分鐘。所以第一次部署從 push 到 Redis 起來最壞 ~6 分鐘。三、 子級輪詢怎么知道 repo 改了—— 先 ls-remote再決定要不要重新生成這是我最開始搞混的地方。我一直以為 ArgoCD 把 repo 的 hash 存下來每次輪詢比對 hash 變了沒。查了源碼之后發(fā)現(xiàn)機(jī)制比這精細(xì)而且hash 對比不是你想的那回事。第一步git ls-remote輕量探測遠(yuǎn)程 HEAD每次輪詢默認(rèn) 180srepo-server 對遠(yuǎn)程倉庫執(zhí)行g(shù)it ls-remote HEAD。注意這不是 clone是只讀一次遠(yuǎn)程 ref開銷 KB 級。源碼util/git/git.go_,errclient.LsRemote(HEAD)第二步拿遠(yuǎn)程 SHA 對比緩存repo-server 的 manifest 緩存Rediskey 里帶著TargetRevisionTTL 默認(rèn) 3 分鐘ARGOCD_RECONCILIATION_TIMEOUT。比對邏輯遠(yuǎn)程 HEAD SHA 緩存里的 SHA ? ├─ 相同 → 直接用緩存的 manifest不重新生成省 CPU/網(wǎng)絡(luò) └─ 不同 → fetch 重新生成 manifest → 更新緩存第三步Application 對象持久記錄 revisionhash 不只活在緩存里每次 sync 之后還會寫進(jìn) Application 對象狀態(tài)kubectl get app redis-ojsonpath{.status.sync.revision}這是 UI 上 “Synced to xxxx” 的數(shù)據(jù)來源也相當(dāng)于上次同步到哪個 commit的錨點(diǎn)。但真相是輪詢本身是無條件的我差點(diǎn)被自己的提問帶偏——ArgoCD 不是發(fā)現(xiàn) hash 變了才去處理而是每 180s 無條件觸發(fā)一次 reconcile 流程ls-remote 只是流程里的第一步。hash 對比的作用是決定要不要重新生成 manifest而不是決定要不要輪詢。每 180s → ls-remote 拿遠(yuǎn)程 SHA → 對比緩存 SHA ├─ 相同 → 復(fù)用緩存 manifest → 和集群 live state 對比 └─ 不同 → fetch 重新生成 manifest → 和集群 live state 對比 最終: diff 非空 automated → 觸發(fā) sync → 更新 .status.sync.revision這順便解釋了為什么改 README 不觸發(fā)部署SHA 變了有提交重新生成 manifest但 diff 為空README 不影響 k8s/ 產(chǎn)物所以不 sync。邏輯閉環(huán)。四、 緩存到底存在哪—— 三層物理位置各不相同繼續(xù)往下挖問題變成緩存在哪。答案不是一個地方是三層1. Git 倉庫本地 clone —— repo-server 容器 /tmp這是最關(guān)鍵的一層也是檢測 diff的真正基準(zhǔn)。每個被引用的 repo 在 repo-server 容器里有一份 clone路徑由 URL 消毒而來// util/git/client.goroot:filepath.Join(os.TempDir(),r.ReplaceAllString(normalizedGitURL,_))// 例: /tmp/https_github.com_nvd11_redis-deployment.git我上集群實(shí)證過后面會詳細(xì)講 repo-server 在哪它的/tmp掛的是emptyDirvolumes:-name:tmpemptyDir:{}volumeMounts:-name:tmpmountPath:/tmp2. Manifest 生成緩存 —— Redisrevision → manifest 生成結(jié)果緩存在 ArgoCD 自帶的 Redis 里TTL 3 分鐘。3. revision 錨點(diǎn) —— K8s etcd.status.sync.revision存在 Application 對象里底層是 etcd跨重啟持久。K8s etcdargocd-redis Podrepo-server Podaws-moon-proxy 節(jié)點(diǎn)生成 manifest 的基準(zhǔn)diff 對比/tmp emptyDirGit clone 緩存例: /tmp/https_github.com_nvd11_xxx.gitManifest 緩存key 含 TargetRevisionTTL 3minApplication.status.sync.revision上次成功 sync 的 commit SHA五、 緩存丟了怎么辦—— 自動重建檢測零損失這是我問自己的第二個問題緩存要是丟了是不是就 detect 不了 diff 了結(jié)論很干脆不會。緩存是性能優(yōu)化不是正確性依賴。Git 是唯一事實(shí)源。源碼util/git/client.go的Init()寫得很直白func(m*nativeGitClient)Init()error{_,err:git.PlainOpen(m.root)iferrnil{returnnil}// clone 在 → 直接用if!errors.Is(err,git.ErrRepositoryNotExists){returnerr}log.Infof(Initializing %s to %s,m.repoURL,m.root)erros.RemoveAll(m.root)// 殘留清掉erros.MkdirAll(m.root,0o755)// 重建目錄repo,err:git.PlainInit(m.root,false)// 重新 git init...}加上checkoutRevision的完整鏈路reposerver/repository/repository.go輪詢觸發(fā) → gitClient.Init() ├─ clone 在 → 直接復(fù)用 └─ clone 丟 → PlainInit 重建空倉庫 → IsRevisionPresent(revision)? 否 → gitClient.Fetch() ← 從遠(yuǎn)程重新拉 → gitClient.Checkout(revision) ← checkout 目標(biāo) revision → CommitSHA() 拿當(dāng)前 commit hash緩存層丟了會怎樣恢復(fù)方式Git 本地 clone/tmp不影響自動重建Init()→Fetch()→Checkout()Redis manifest 緩存不影響重新生成每次 reconcile 實(shí)時生成.status.sync.revision不影響檢測只影響 UI 顯示下次 sync 自動更新就算把 repo-server 的 /tmp 和 Redis 全清了它頂多慢幾秒重新 clone檢測照樣精確。Git 永遠(yuǎn)可以被重新拉取這就是 GitOps “Git 為源 緩存可棄” 的可靠性根基。六、 repo-server 到底在哪—— 它是獨(dú)立 Pod不在ArgoCD 主節(jié)點(diǎn)上這個問題我也踩了認(rèn)知坑。我一直默認(rèn) repo-server 跟 ArgoCD 控制器在一起直到 SSH 上阿里云 master 查了一下$ kubectl get pods-nargocd-owide NAME READY NODE argocd-application-controller-01/1 free-amd-vm argocd-repo-server-797fc85c8f-x6csk1/1 aws-moon-proxy ← 在這 argocd-redis-9dbc65c5c-lcb281/1 aws-moon-proxy argocd-server-58d6c7fbb9-sg9xt1/1 free-amd-vm2ArgoCD 是一組微服務(wù)不是單個進(jìn)程。repo-server 是獨(dú)立 Deployment負(fù)責(zé)所有 Git 操作clone/fetch/生成 manifest通過 gRPC 被 application-controller 調(diào)用。它可以調(diào)度到任何節(jié)點(diǎn)——我集群里它就跑在aws-moon-proxyAWS 節(jié)點(diǎn)上。而且它徹底無狀態(tài)/tmp掛 emptyDirPod 重建 clone 全清 自動重建。官方部署不掛 PV就是因?yàn)榫彺娌恢档贸志没?。七?從推代碼到 Redis 起來完整時序把前面所有機(jī)制串起來一次完整的 GitOps 部署是這樣的以 redis-deployment 為例目標(biāo)集群repo-serverredis Applicationroot-bootstrapredis-deployment.gitmy-argocd-manifests.git開發(fā)者目標(biāo)集群repo-serverredis Applicationroot-bootstrapredis-deployment.gitmy-argocd-manifests.git開發(fā)者對象創(chuàng)建后才有自己的輪詢循環(huán)push redis-app.yaml輪詢 180s發(fā)現(xiàn)新文件創(chuàng)建 redis Application 對象觸發(fā) reconcilegit ls-remote HEAD遠(yuǎn)程 SHASHA 對比緩存fetch checkout (緩存缺失時)生成 manifestdiff 非空 → 自動 syncRedis Pod 運(yùn)行在 free-arm-vm八、 總結(jié)這套機(jī)制給工程實(shí)踐帶來的三條啟示App-of-Apps 的兩層別搞混父級管應(yīng)用是否存在App 定義層子級管資源是否一致Manifest 層。repoURL 是父子之間的橋梁字段但兩者各自獨(dú)立輪詢、互不阻塞。加文件 加應(yīng)用刪文件 prune 刪應(yīng)用不需要任何 UI 注冊。輪詢與緩存是兩回事輪詢是每 180s 無條件觸發(fā)的 reconcilels-remote 只是第一步緩存/tmp clone、Redis、etcd revision只決定要不要重新生成 manifest不影響要不要檢測。所以緩存丟了檢測能力零損失。Git 是唯一事實(shí)源其他都可棄repo-server 無狀態(tài)、emptyDir、自動重建這套設(shè)計讓 ArgoCD 在任何節(jié)點(diǎn)故障/緩存清空后都能自愈。這也是為什么 GitOps 敢把部署交給一個會忘事的進(jìn)程——它忘了沒關(guān)系Git 記得一切。題外話本文從ArgoCD 到底輪詢哪個 repo這個看似簡單的問題出發(fā)最后查到了util/git/client.go的Init()源碼。這種問題 → 懷疑 → 查源碼 → 上集群實(shí)證的路徑比直接看文檔收獲大得多。文檔只告訴你它會自動同步源碼才告訴你它憑什么敢自動同步。