技术文章2026 年 8 月 23 日约 5.8 分钟

ArgoCD 实战:用 GitOps 把 CI/CD 部署搬进 Kubernetes

从安装、Application 声明到 App of Apps 与 CI 配合,把 GitOps 部署体系在 Kubernetes 上落地,并复盘真实事故。

上一篇文章里我们用 GitHub Actions 直接 kubectl set image 发布,那是典型的 push 模式:CI 拿着集群权限,把变更推给集群。这套流程的问题在于——发布完就没人再管集群了。昨天谁手动 kubectl apply 改了个副本数?Git 里写的还是不是线上真实状态?没人说得清。GitOps 换个思路:Git 是唯一事实来源,集群里有个控制器持续盯着「Git 里声明的」和「集群里实际的」,不一致就拉平。ArgoCD 就是这件事在 Kubernetes 上的标准实现。

#GitOps:pull 和 push 到底差在哪

  • push 模式:CI 构建镜像 → 用 kubeconfig 直接改集群。缺点:kubeconfig 要分发给 CI,权限面大;发布之后状态漂移没人发现,全靠人肉巡检。
  • pull 模式:ArgoCD 的 controller 在集群内主动拉取 Git 仓库,对比期望状态和实际状态,默认每 3 分钟一次,有差异就自动同步。CI 完全不需要集群权限。

一句话:CI 负责把镜像做好,ArgoCD 负责让集群长成 Git 里写的样子。我把这套架构跑了一年多,最直观的感受是:排查「线上为什么和预期不一样」的时间,从小时级降到了分钟级——答案几乎都是「Git 里就是这么写的,你只是没看 Git」。

#安装与登录

bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl get pods -n argocd -w

装完会起四个核心组件:

  • argocd-server:API、Web UI、CLI 后端,也是登录入口。
  • argocd-repo-server:负责拉取和缓存 Git 仓库内容,Application 的渲染结果都从它这里来。
  • argocd-application-controller:核心引擎,持续比对 Git 与集群,执行同步。
  • argocd-redis:缓存 Application 状态和锁,controller 和 server 都要用,别随手删。

登录第一步是拿初始密码,它存在 argocd-initial-admin-secret 里;第一次登录改密之后,这个 secret 就消失了:

bash
kubectl -n argocd get secret argocd-initial-admin-secret   -o jsonpath='{.data.password}' | base64 -d; echo
kubectl -n argocd port-forward svc/argocd-server 8080:443
argocd login 127.0.0.1:8080 --insecure
argocd account update-password

生产环境别用 port-forward 裸奔:接 Ingress + HTTPS,再用 SSO(GitHub/OIDC)做登录,admin 账号平时不启用。

#注册仓库:私有仓库的凭据要收敛

ArgoCD 需要读你的 Git 仓库。公开仓库直接 argocd repo add;私有仓库推荐 HTTPS + 只读 PAT,或者 SSH key:

bash
argocd repo add https://github.com/acme/manifests.git   --username git --password ghp_xxx    # PAT 只开 Contents: Read
argocd repo list

PAT 权限务必收窄——repo-server 拿到的是能读整个仓库的凭据,谁拿到它谁就能看到仓库里所有文件。我见过有人把带 repo 写权限的 PAT 配进去,等于给 ArgoCD 开了一个能改代码的后门,完全没必要。

#第一个 Application:声明「从哪来、到哪去」

Application 是 ArgoCD 的核心对象,一条声明写清楚「仓库 + 路径 → 集群 + namespace」:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo-api
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/acme/manifests.git
    targetRevision: main
    path: apps/demo-api
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

三个字段拆开理解:

  • source:从哪个仓库、哪个分支、哪个路径取期望状态。
  • destination:同步到哪个集群、哪个 namespace。https://kubernetes.default.svc 就是当前集群(in-cluster)。
  • syncPolicy.automated:自动同步三件套——sync 自动拉平差异、selfHeal 把手工改动拉回 Git 状态、prune 删除 Git 里已不存在的资源。

用 kubectl 提交,或者用 CLI 操作:

bash
kubectl apply -f application-demo-api.yaml
argocd app list
argocd app get demo-api            # 看 Sync Status 和 Health
argocd app sync demo-api           # 手动触发同步
argocd app history demo-api        # 每次同步对应一个 Git revision
argocd app rollback demo-api 2     # 回滚到历史 revision

app get 输出里我最常盯两个字段:Sync Status(OutOfSync / Synced)和 Health(Healthy / Degraded)。OutOfSync 说明 Git 和集群不一致,Degraded 说明应用本身有问题——这是两种完全不同的事故,别混着查。

#App of Apps:仓库结构决定可维护性

在一个 Application 里塞 50 个路径,UI 会乱到没法看。标准做法是「App of Apps」:一个根 Application 只管 apps/ 目录下的 Application 清单,每个子 Application 再管自己的资源目录:

text
manifests/
├── apps/                  # 根 Application 管这里
│   ├── demo-api.yaml
│   └── monitoring.yaml
└── demo-api/              # 子 Application 管这里
    ├── deployment.yaml
    └── service.yaml

新增一个应用 = 在 apps/ 里加一个文件,根 Application 同步后,子 Application 自动出现。这套结构跑熟之后,整个集群的「部署拓扑」都写在 Git 里,任何人 git log 就能看懂发布历史。

#CI 与 ArgoCD 的分工:CI 只改 Git,不碰集群

和 GitHub Actions 直接 kubectl set image 不同,GitOps 下 CI 的最后一个动作是更新 manifest 仓库里的镜像 tag 并提交:

yaml
- name: Update image tag in gitops repo
  run: |
    cd manifests
    sed -i "s|image: ghcr.io/acme/demo-api:.*|image: ghcr.io/acme/demo-api:${{ github.sha }}|" demo-api/deployment.yaml
    git config user.name "ci-bot"
    git config user.email "ci-bot@example.com"
    git commit -am "release demo-api ${{ github.sha }}"
    git push

ArgoCD 默认 3 分钟轮询一次;想秒级触发,在 GitHub 仓库配 webhook(/api/webhook),有 push 事件立刻同步。这样做的收益:CI 不再持有集群权限,镜像 tag 和线上版本一一对应(argocd app history 里每条都有 commit),出问题回滚就是改 Git,或者 argocd app rollback 一条命令。

#我踩过的坑

  1. 手工改动被 selfHeal 无情拉回。有人上线时手滑 kubectl scale 改了副本数,ArgoCD 三分钟内拉回 Git 状态,他以为是灵异事件。其实这是 selfHeal 在正常工作——集群里的手工改动在 GitOps 里不算数,改配置请走 Git。
  2. 没开 prune,删除变幽灵。Git 里删了某个 Service,但 Application 没开 prune,资源留在集群里继续被访问,域名解析到一个已废弃的服务上。排查了半天才想起来是孤儿资源。
  3. 健康检查卡在 Progressing。新版本 Deployment 探针配错,Pod 永远不就绪,ArgoCD 显示 Degraded 但不报错,因为健康状态取决于 Deployment 的进展。这种问题去查 kubectl describe pod 的 Events,跟普通排障流程没区别。
  4. 镜像 tag 用 latest。ArgoCD 认为 Git 没变就不同步,但镜像内容变了,集群里还是旧镜像。GitOps 下镜像 tag 必须是不可变的(用 commit SHA),否则 Git 状态和实际运行对不上,整个 GitOps 的信任就塌了。

#经验

GitOps 不是装个 ArgoCD 就完事,它是一套纪律:Git 是唯一事实来源,集群状态靠自动同步维持,环境差异用 kustomize/Helm 的 overlay 表达,而不是复制一堆 YAML。我的建议是先在非生产跑熟 selfHeal + prune,同时把 webhook、SSO、RBAC 配齐,再让生产环境切过去。等哪天线上出问题,你 git log 就能看到是谁、在什么时候、改了哪个文件导致——这个可追溯性,是 ArgoCD 给我最大的回报。

写于 2026 年 8 月 23 日

栏目
技术文章
约
5.8 分钟
字数
4.3K
阅读
75

本文为原创记录,转载请注明出处。如果这篇替你省了时间,欢迎留言说说你踩到的坑。

同题 · related

留言 · remarks

00 条

还没有留言,来说点什么吧。

ArgoCD 实战:用 GitOps 把 CI/CD 部署搬进 Kubernetes · LXH·BLOG