ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ArgoCD 双层轮询深入拆解:从 redis-app.yaml 注册到缓存重建的完整链路

2026/8/5 5:37:57 拓冰建站 浏览量
ArgoCD 双层轮询深入拆解:从 redis-app.yaml 注册到缓存重建的完整链路 在给 Redis 部署做 GitOps 规范化的时候我把redis-deployment仓库推上去之后突然意识到一个问题ArgoCD 到底是怎么知道这个仓库有改动的我的第一反应是ArgoCD 轮询 Git 仓库比对 hash但仔细一想又不对——我明明有两个仓库在参与这件事my-argocd-manifests管 Application 定义和redis-deployment管实际 Manifest。那 ArgoCD 到底轮询哪一个这篇文章把我从怀疑到查源码、再到上集群实证的完整过程记录下来。结论可能会颠覆你对 ArgoCD 的直觉ArgoCD 不是轮询一个仓库而是两层各自独立的轮询循环它也不是靠保存 hash 对比来检测改动Git 实时生成才是真相。一、 背景我的 ArgoCD 到底长什么样先交代我的实际环境后面所有例子都是真实资源ArgoCD 控制面阿里云 K3s 集群App-of-Apps 模式root-bootstrapApplication 管着my-argocd-manifests.git的argocd-apps/目录目标集群tencent-dp1-cluster横跨腾讯云vm-0-2-debian控制面、OCI ARMfree-arm-vm、本地 NUC 三个节点子应用fastapi-svc、quarkus-svc、kong-gateway-infra、kong-ingress-controller、gateway-api-crds共 5 个待接入redis-deployment.gitk8s/redis.yaml打算把 Redis 部署到 free-arm-vm经 Kong Gateway 6379 Stream 对外我当时给 README 写ArgoCD 轮询的是每个 Application 自己的 source.repoURL不是统一仓库这句话时被自己问住了那root-bootstrap轮询my-argocd-manifests又算什么两个仓库都会被轮询吗还是有一个是假的二、 双层轮询发现层与同步层各干各的答案是两个仓库都会被轮询但轮询的主体不同职责完全不同。这是 ArgoCD App-of-Apps 模式最核心、也最容易搞混的机制。第一层root-bootstrap发现层—— 轮询 my-argocd-manifests# argocd-apps/root-bootstrap-app.yamlspec:source:repoURL:https://github.com/nvd11/my-argocd-manifests.gittargetRevision:HEADpath:argocd-apps它的职责只有一件事盯住argocd-apps/目录看里面有没有新的 Application 文件。我往里加一个redis-app.yamlroot-bootstrap 下一轮轮询发现新文件就在集群里创建一个redis的 Application 对象。注意这一层不解析、不连接、不验证 redis-deployment.git。repoURL 对它来说只是 Application 对象里的一个字段它照抄进对象里就完事。第二层子 Application同步层—— 轮询各自 source.repoURL# 我 README 里规划的 redis-app.yaml示意spec:source:repoURL:https://github.com/nvd11/redis-deployment.gitpath:k8s一旦redisApplication 对象被创建application-controller 就为它单独开一个轮询循环去轮询 redis-deployment.git 的k8s/目录把 Deployment/PVC/Service/TCPIngress 同步到目标集群。我现有的fastapi-svc-app.yaml就是活证据——它的source.repoURL指向my-shared-helm-charts.git而不是my-argocd-manifests。如果 ArgoCD 只轮询 my-argocd-manifestsfastapi 的镜像永远不可能被同步。目标集群 tencent-dp1-clusterArgoCD 控制面 阿里云 K3sGit: redis-deployment.gitGit: my-argocd-manifests.git第一层轮询 180s发现新 Application 文件创建/更新对象第二层轮询 180s检测 k8s/ 变更自动 syncargocd-apps/ 目录Application 定义文件k8s/ 目录实际 Manifestroot-bootstrapApplicationredisApplication 对象Redis Podfree-arm-vm两个关键结论两个循环完全解耦不是父级扫完通知子级的串行。父级只管 Application 对象的增删改子级只管资源同步。改redis-app.yaml的 syncPolicy → 父级发现改k8s/redis.yaml的镜像 → 父级完全无感是子级自己的事。首次部署 Redis 最坏要等两个 3 分钟父级 3 分钟捡到新文件创建对象子级对象从创建那一刻才开始自己的轮询又是最多 3 分钟。所以第一次部署从 push 到 Redis 起来最坏 ~6 分钟。三、 子级轮询怎么知道 repo 改了—— 先 ls-remote再决定要不要重新生成这是我最开始搞混的地方。我一直以为 ArgoCD 把 repo 的 hash 存下来每次轮询比对 hash 变了没。查了源码之后发现机制比这精细而且hash 对比不是你想的那回事。第一步git ls-remote轻量探测远程 HEAD每次轮询默认 180srepo-server 对远程仓库执行git ls-remote HEAD。注意这不是 clone是只读一次远程 ref开销 KB 级。源码util/git/git.go_,errclient.LsRemote(HEAD)第二步拿远程 SHA 对比缓存repo-server 的 manifest 缓存Rediskey 里带着TargetRevisionTTL 默认 3 分钟ARGOCD_RECONCILIATION_TIMEOUT。比对逻辑远程 HEAD SHA 缓存里的 SHA ? ├─ 相同 → 直接用缓存的 manifest不重新生成省 CPU/网络 └─ 不同 → fetch 重新生成 manifest → 更新缓存第三步Application 对象持久记录 revisionhash 不只活在缓存里每次 sync 之后还会写进 Application 对象状态kubectl get app redis-ojsonpath{.status.sync.revision}这是 UI 上 “Synced to xxxx” 的数据来源也相当于上次同步到哪个 commit的锚点。但真相是轮询本身是无条件的我差点被自己的提问带偏——ArgoCD 不是发现 hash 变了才去处理而是每 180s 无条件触发一次 reconcile 流程ls-remote 只是流程里的第一步。hash 对比的作用是决定要不要重新生成 manifest而不是决定要不要轮询。每 180s → ls-remote 拿远程 SHA → 对比缓存 SHA ├─ 相同 → 复用缓存 manifest → 和集群 live state 对比 └─ 不同 → fetch 重新生成 manifest → 和集群 live state 对比 最终: diff 非空 automated → 触发 sync → 更新 .status.sync.revision这顺便解释了为什么改 README 不触发部署SHA 变了有提交重新生成 manifest但 diff 为空README 不影响 k8s/ 产物所以不 sync。逻辑闭环。四、 缓存到底存在哪—— 三层物理位置各不相同继续往下挖问题变成缓存在哪。答案不是一个地方是三层1. Git 仓库本地 clone —— repo-server 容器 /tmp这是最关键的一层也是检测 diff的真正基准。每个被引用的 repo 在 repo-server 容器里有一份 clone路径由 URL 消毒而来// util/git/client.goroot:filepath.Join(os.TempDir(),r.ReplaceAllString(normalizedGitURL,_))// 例: /tmp/https_github.com_nvd11_redis-deployment.git我上集群实证过后面会详细讲 repo-server 在哪它的/tmp挂的是emptyDirvolumes:-name:tmpemptyDir:{}volumeMounts:-name:tmpmountPath:/tmp2. Manifest 生成缓存 —— Redisrevision → manifest 生成结果缓存在 ArgoCD 自带的 Redis 里TTL 3 分钟。3. revision 锚点 —— K8s etcd.status.sync.revision存在 Application 对象里底层是 etcd跨重启持久。K8s etcdargocd-redis Podrepo-server Podaws-moon-proxy 节点生成 manifest 的基准diff 对比/tmp emptyDirGit clone 缓存例: /tmp/https_github.com_nvd11_xxx.gitManifest 缓存key 含 TargetRevisionTTL 3minApplication.status.sync.revision上次成功 sync 的 commit SHA五、 缓存丢了怎么办—— 自动重建检测零损失这是我问自己的第二个问题缓存要是丢了是不是就 detect 不了 diff 了结论很干脆不会。缓存是性能优化不是正确性依赖。Git 是唯一事实源。源码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轮询触发 → gitClient.Init() ├─ clone 在 → 直接复用 └─ clone 丢 → PlainInit 重建空仓库 → IsRevisionPresent(revision)? 否 → gitClient.Fetch() ← 从远程重新拉 → gitClient.Checkout(revision) ← checkout 目标 revision → CommitSHA() 拿当前 commit hash缓存层丢了会怎样恢复方式Git 本地 clone/tmp不影响自动重建Init()→Fetch()→Checkout()Redis manifest 缓存不影响重新生成每次 reconcile 实时生成.status.sync.revision不影响检测只影响 UI 显示下次 sync 自动更新就算把 repo-server 的 /tmp 和 Redis 全清了它顶多慢几秒重新 clone检测照样精确。Git 永远可以被重新拉取这就是 GitOps “Git 为源 缓存可弃” 的可靠性根基。六、 repo-server 到底在哪—— 它是独立 Pod不在ArgoCD 主节点上这个问题我也踩了认知坑。我一直默认 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 是一组微服务不是单个进程。repo-server 是独立 Deployment负责所有 Git 操作clone/fetch/生成 manifest通过 gRPC 被 application-controller 调用。它可以调度到任何节点——我集群里它就跑在aws-moon-proxyAWS 节点上。而且它彻底无状态/tmp挂 emptyDirPod 重建 clone 全清 自动重建。官方部署不挂 PV就是因为缓存不值得持久化。七、 从推代码到 Redis 起来完整时序把前面所有机制串起来一次完整的 GitOps 部署是这样的以 redis-deployment 为例目标集群repo-serverredis Applicationroot-bootstrapredis-deployment.gitmy-argocd-manifests.git开发者目标集群repo-serverredis Applicationroot-bootstrapredis-deployment.gitmy-argocd-manifests.git开发者对象创建后才有自己的轮询循环push redis-app.yaml轮询 180s发现新文件创建 redis Application 对象触发 reconcilegit ls-remote HEAD远程 SHASHA 对比缓存fetch checkout (缓存缺失时)生成 manifestdiff 非空 → 自动 syncRedis Pod 运行在 free-arm-vm八、 总结这套机制给工程实践带来的三条启示App-of-Apps 的两层别搞混父级管应用是否存在App 定义层子级管资源是否一致Manifest 层。repoURL 是父子之间的桥梁字段但两者各自独立轮询、互不阻塞。加文件 加应用删文件 prune 删应用不需要任何 UI 注册。轮询与缓存是两回事轮询是每 180s 无条件触发的 reconcilels-remote 只是第一步缓存/tmp clone、Redis、etcd revision只决定要不要重新生成 manifest不影响要不要检测。所以缓存丢了检测能力零损失。Git 是唯一事实源其他都可弃repo-server 无状态、emptyDir、自动重建这套设计让 ArgoCD 在任何节点故障/缓存清空后都能自愈。这也是为什么 GitOps 敢把部署交给一个会忘事的进程——它忘了没关系Git 记得一切。题外话本文从ArgoCD 到底轮询哪个 repo这个看似简单的问题出发最后查到了util/git/client.go的Init()源码。这种问题 → 怀疑 → 查源码 → 上集群实证的路径比直接看文档收获大得多。文档只告诉你它会自动同步源码才告诉你它凭什么敢自动同步。