ArgoCD 实战:用 GitOps 把 CI/CD 部署搬进 Kubernetes
从安装、Application 声明到 App of Apps 与 CI 配合,把 GitOps 部署体系在 Kubernetes 上落地,并复盘真实事故。
#部署为什么长这样:先从三个基本事实推导
先从问题推方案,而不是先学工具。部署环节有三个绕不开的基本事实:
- 集群的可用状态由一份「期望清单」决定:镜像版本、副本数、环境变量、网络规则。
- 任何人直接
kubectl apply改过之后,都回答不了「线上现在和谁一致、是谁改的、什么时候改的」——状态不可审计、不可回放。 - 真实集群一定会漂移:手滑、扩缩容、临时排查都会让实际状态偏离预期。
结论不是「多写几个脚本」,而是把期望清单放进唯一事实来源(Git),再让一个常驻进程反复做三件事:读期望 → 对比现状 → 收敛差异。这个循环叫 reconciliation loop,这套做法就是 GitOps,ArgoCD 是它在 Kubernetes 上的标准实现。
- push 模式:CI 构建完镜像,拿着 kubeconfig 直接改集群。缺点:集群权限必须下发给 CI,权限面大;发布完成后漂移无人发现。
- pull 模式:controller 在集群内主动拉 Git,周期比对,有差异自动同步。CI 完全不需要集群权限。
分工一句话:CI 负责把镜像做好,ArgoCD 负责让集群长成 Git 里写的样子。
#0 环境准备
复现本文需要四样东西,先逐一确认,减少后面「环境问题当配置问题查」的时间:
- Kubernetes 集群:kubectl 能访问即可(k3s / minikube / kind / 云托管集群都可以),版本建议 ≥ 1.24。
- kubectl 已切到目标集群:
kubectl cluster-info能正常返回。 - argocd CLI:和 ArgoCD server 交互用(安装见下)。
- 一个 Git 仓库放部署清单:本文统一叫「manifests 仓库」,地址形如
https://github.com/<you>/manifests.git。下文所有<you>、<PAT>都替换成你自己的值。
安装 argocd CLI(Linux):
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64macOS(Apple Silicon 用 arm64,Intel 用 amd64):
curl -sSL -o argocd-darwin-arm64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-darwin-arm64
sudo install -m 555 argocd-darwin-arm64 /usr/local/bin/argocd
rm argocd-darwin-arm64验证:
argocd version --client
kubectl cluster-info#1 安装 ArgoCD
建命名空间并应用官方安装清单(stable 指向当前稳定版):
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
# 或者
kubectl -n argocd wait --for=condition=available deployment --all --timeout=300s全部 Running 且 READY 1/1 后再继续。装完有四个核心组件,它们的分工决定了后续排查先看谁:
argocd-server:对外 API / Web UI / CLI 入口,负责鉴权和接收 webhook。argocd-repo-server:拉取并缓存 Git 仓库内容,Application 的渲染结果都从这里来。argocd-application-controller:reconciliation loop 本体,持续比对 Git 与集群并执行同步。argocd-redis:缓存状态与分布式锁,controller 和 server 共用,删了会短暂互相踩状态。
验证 Application 是 CRD 对象(后面声明的东西靠它):
kubectl get crd applications.argoproj.io#2 登录并修改初始密码
初始密码机制:安装时生成一次性 admin 密码,放在 Secret argocd-initial-admin-secret 的 data.password(base64 编码)。第一次登录改密后,这个 Secret 会被自动删除。
取密码并暴露服务(port-forward 会占住终端,另开一个终端做登录):
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用 CLI 登录并改密:
INITIAL_PW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d)
argocd login 127.0.0.1:8080 --insecure --username admin --password "$INITIAL_PW"
argocd account update-password --current-password "$INITIAL_PW" --new-password '<你的新密码>'验证登录态可用:
argocd app list操作要点:
--insecure只用于自签名证书场景(这里是 port-forward);生产环境必须走 Ingress + HTTPS,挂 TLS 证书,不能裸奔。- 验证下
argocd-initial-admin-secret已消失:kubectl -n argocd get secret argocd-initial-admin-secret应报 NotFound。若之后忘记密码,官方重置方法是删除该 Secret 后滚动重启 server:kubectl -n argocd rollout restart deployment argocd-server,会自动再生成一个一次性初始密码。 - 生产加固(超出本文复现范围):Ingress + TLS 终结、SSO(GitHub / OIDC)、RBAC 按团队拆分、admin 平时禁用。
#3 注册 Git 仓库
原理:所有 Application 引用的仓库都由 repo-server 统一拉取和缓存,凭据不写死在配置里,而是由 argocd repo add 存成 argocd 命名空间下的 Secret(label argocd.argoproj.io/secret-type: repository)。私有仓库的凭据必须收敛到「只读」,因为 repo-server 拿到的是能读整个仓库的凭据。
公开仓库直接添加:
argocd repo add https://github.com/<you>/manifests.git私有仓库推荐 HTTPS + 只读 PAT(GitHub PAT 只勾 Contents: Read):
argocd repo add https://github.com/<you>/manifests.git --username git --password <PAT>
argocd repo list验证与排障:
argocd repo list中该仓库STATUS为Successful,才算注册成功。- 出现
REPO-CONNECTION-ERROR时按顺序查:① 仓库地址与网络可达性 ② 凭据是否正确(PAT 是否过期)③ 私有仓库的 PAT 是否给了Contents: Read。 - 想确认凭据落点:
kubectl -n argocd get secret -l argocd.argoproj.io/secret-type=repository。
#4 声明第一个 Application
Application 是一条声明:「Git 仓库的某个路径 → 某个集群的某个 namespace」。它本身是 CRD 对象(第 1 节已确认)。
先在 manifests 仓库里放一个最小应用,让整个链路真的能跑(目录 apps/demo-api/):
# apps/demo-api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
spec:
replicas: 1
selector:
matchLabels:
app: demo-api
template:
metadata:
labels:
app: demo-api
spec:
containers:
- name: demo-api
image: ghcr.io/<you>/demo-api:v1注意 Deployment 里不写
metadata.namespace,由 Application 的destination.namespace决定落到哪个命名空间,避免两处打架。
在本地写 Application 声明:
# application-demo-api.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/<you>/manifests.git
targetRevision: main
path: apps/demo-api
destination:
server: https://kubernetes.default.svc
namespace: demo
syncPolicy:
automated:
sync: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true提交并观察:
kubectl apply -f application-demo-api.yaml
argocd app list # demo-api 出现,先 OutOfSync,自动同步后变 Synced
kubectl -n demo get deploy demo-api # 自动同步应已创建出 Deployment三个字段拆开理解:
source:从哪个仓库、哪个分支、哪个路径取期望状态。destination:同步到哪个集群、哪个命名空间;https://kubernetes.default.svc就是当前集群(in-cluster)。syncPolicy.automated:自动同步三件套,各自独立,职责不同:sync:Git 与集群有差异就自动应用(这是「上线」)。selfHeal:集群被手工改动就拉回 Git 状态(这是「防漂移」)。prune:资源在 Git 里已不存在就删除(这是「防孤儿」)。
只开 sync = 能上线但防不了漂移;sync + selfHeal = 能拉平但删不掉孤儿;全开才是完整收敛。
手动操作(排查、回滚用):
argocd app get demo-api # 看 SYNC STATUS 和 HEALTH STATUS
argocd app sync demo-api # 手动触发一次同步
argocd app history demo-api # 每次同步对应一个 Git revision
argocd app rollback demo-api 2 # 回滚到 history 里的第 2 条app get 输出里两个维度要分开看:SYNC STATUS(OutOfSync / Synced)= Git 与集群是否一致;HEALTH STATUS(Healthy / Degraded)= 应用运行是否正常。两类问题不互通,OutOfSync 是声明问题,Degraded 是运行问题。
#5 App of Apps:用递归声明组织多应用
原理:Application 本身也是集群里的资源,所以它也能被另一个 Application 管理(递归)。于是「一个根 Application 管 apps/ 目录下的 Application 清单,每个子 Application 管自己的资源目录」,集群的部署拓扑就全写进 Git 了。
目录结构:
manifests/
├── apps/ # 根 Application 管这里
│ ├── demo-api.yaml # 第 4 节的 application-demo-api.yaml 挪到这里(保留 namespace: argocd)
│ └── monitoring.yaml
└── demo-api/ # 子 Application 管这里
├── deployment.yaml
└── service.yaml根 Application(path 指向 apps/,destination.namespace 必须是 argocd,子 Application 才能被 controller 识别):
# root-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/<you>/manifests.git
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
sync: true
prune: true
selfHeal: true提交并验证递归是否生效:
kubectl apply -f root-apps.yaml
argocd app list
# 先出现 root-apps;根应用把 apps/demo-api.yaml 同步进 argocd 命名空间后,
# 列表应自动出现 demo-api;再等一轮,demo 命名空间里出现 Deployment
kubectl -n demo get deploy demo-api
argocd app get demo-api关键细节:子 Application 的 metadata.namespace 必须是 argocd——controller 只 watch argocd 命名空间里的 Application。根 Application 的 destination 指向 argocd,就是在做「把子 Application 文件放进 argocd 命名空间」这一步。
新增一个应用 = 往 apps/ 里加一个 Application 文件并 push → 根应用自动同步 → 子应用自动出现。整个发布历史任何人 git log 都能读。
#6 接入 CI:CI 只改 Git,不碰集群
原则:既然集群只跟随 Git,部署权限就该收敛成「Git 变更」。CI 的最后一个动作是更新 manifest 仓库里的镜像 tag 并提交推送,之后 ArgoCD 自己完成部署。CI 从此不需要任何 kubeconfig。
GitHub Actions 完整 job(以 push 到 manifests 仓库为例):
# .github/workflows/deploy.yml(release 阶段的最后一步)
- name: Update image tag in gitops repo
run: |
git clone --depth 1 https://github.com/<you>/manifests.git /tmp/manifests
cd /tmp/manifests
sed -i "s|image: ghcr.io/<you>/demo-api:.*|image: ghcr.io/<you>/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两个前提要自检:
- 该 workflow 的凭据对 manifests 仓库有 push 权限(GitHub Actions 常用自带 token
permissions: contents: write,或独立 bot PAT)。 - 镜像 tag 必须是不可变的:用
${GITHUB_SHA}(或构建产物的 commit SHA),绝不用latest。原因在原理层:ArgoCD 判断「要不要同步」的依据是 Git 内容是否变化,tag 不变 Git 就不变,镜像内容悄悄变了集群也不会更新——GitOps 的信任就塌了。
收敛速度,两种档位:
- 默认:ArgoCD 每 3 分钟轮询一次仓库(180s),不配 webhook 也能收敛,只是慢。
- 秒级:在 GitHub 仓库配置 webhook,URL 填
https://<argocd-server>/api/webhook,事件选 push;GitHub 域名在 ArgoCD 内置白名单内,收到 push 立即刷新对应仓库。若要在 GitHub 侧填 Secret 校验签名,先在argocd-cmConfigMap 里配webhook.github.secret,GitHub webhook 里填同一个值。
验证闭环:
argocd app history demo-api # 出现新 revision,与 push 的 commit 一一对应
kubectl -n demo get deploy demo-api -o jsonpath='{.spec.template.spec.containers[0].image}'
# 应输出 ghcr.io/<you>/demo-api:<git sha>出问题回滚就是改 Git,或者 argocd app rollback demo-api <revision> 一条命令。
#7 常见问题定位手册
每一条都先看 argocd app get <app> 的两个状态列,再下沉到 Pod 层,按「现象 → 根因 → 定位 → 处理」操作:
1. 手工改的副本数/配置几分钟内被还原
- 根因:
selfHeal: true在正常工作,集群里的手工改动在 GitOps 里不算数。 - 定位:
argocd app get demo-api显示Synced(已拉回)。 - 处理:改动一律走 Git(改 manifest → push → 自动同步),不要直接
kubectl改集群。
2. Git 里删了资源,集群里还在、流量还在
- 根因:Application 没开
prune,孤儿资源无人清理。 - 定位:
kubectl get svc,deploy -n demo确认残留资源。 - 处理:确认该资源确实不再需要后,打开
prune: true并argocd app sync demo-api;若开 prune 前已想删,直接手动删除并确认 Git 里也没有对应声明。
3. HEALTH=Degraded 但 SYNC=Synced
- 根因:声明一致但应用没跑起来,最常见是探针配错或启动参数错误,Pod 永远不就绪。
- 定位:
kubectl describe pod -n demo -l app=demo-api(看 Events 的探针失败原因)+kubectl logs -n demo deploy/demo-api。 - 处理:修探针或启动参数,push 后自动同步;健康状态取决于 Deployment 进展,这一步和普通排障没区别。
4. 镜像内容变了,集群里还是旧镜像
- 根因:tag 用了
latest这类可变 tag。Git 内容没变 → controller 认为无需同步 → 新旧镜像内容不一致。 - 定位:
argocd app diff demo-api显示无差异,但实际运行镜像对不上。 - 处理:改为不可变 tag(commit SHA),push 后自动同步;这是 GitOps 信任链的底线,不可妥协。
#8 卸载(复现完清理)
kubectl delete -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl delete crd applications.argoproj.io appprojects.argoproj.io applicationsets.argoproj.io
kubectl delete namespace argocd第一条删除安装清单里的命名空间内对象与 RBAC;不删 CRD 的话,Application 对象还在,controller 只是停了,所以复现环境直接把 CRD 连同其管理的对象一起删掉,最后清理命名空间。
#复现自检清单
-
kubectl cluster-info正常,argocd version --client有版本输出 - argocd 命名空间 4 个 Deployment 全部就绪
- 改密后
argocd-initial-admin-secret已消失 -
argocd repo list显示 manifests 仓库Successful - 第一个 Application 创建后自动变
Synced,kubectl -n demo get deploy demo-api存在 - App of Apps:apply 根应用后,子应用自动出现在
argocd app list - CI push 新 tag 后,
argocd app history出现新 revision,线上镜像是新 SHA - 卸载后 CRD、命名空间、相关资源全部清除
#生产环境上线前检查(不展开复现)
非生产环境先跑熟 sync + selfHeal + prune;webhook、SSO(GitHub/OIDC)、RBAC 配齐;admin 禁用、未授权不得开。上线前确认镜像 tag 策略是不可变 SHA、prune 全开、每一层 Application 都有对应 owner 的变更记录——等线上出问题,git log 就能回答「谁、什么时候、改了哪个文件」,可追溯性才是这套体系最大的收益。
相关阅读
Related留言讨论
0 条还没有留言,来说点什么吧。