Skip to content

关于本次勘察的边界

本文档中的“现状”部分全部来自对 106.75.30.92只读读取(kubectl get / describe / exec cat 配置文件、读取 Jenkins 配置与 workspace 文件、curl 探测网络)。未执行任何 apply / patch / create / delete / restart / 镜像拉取等写操作,集群与 Jenkins 状态与改造前一致。下面第四章之后的所有命令都由你在确认后自行执行。

01 现状勘察结论

先把“当前真实跑着什么”钉死。下面每一条都是现场读出来的,不是推测。

1.1 节点与集群底座

4 个节点全部通过 Tailscale 组网,kubectl 的 API Server 挂在 Jenkins 那台机器上,它同时兼任控制平面:

节点名角色Tailscale IPCPU / 内存(allocatable)当前内存占用状态
jenkinscontrol-plane(etcd / apiserver / controller-manager / scheduler 全在这)100.68.117.722C / 7.4Gi71%Ready(带 control-plane 污点)
k8s-node1worker100.125.80.984C / 3.5Gi76%Ready
k8s-node2worker100.97.166.904C / 3.5Gi93%Ready(内存最紧张)
k8s-node3worker100.78.249.1062C / 7.4Gi70%Ready(内存余量最大,≈2.2Gi)
组件现状
Kubernetesv1.28.14(kubeadm 部署,kubernetes-admin@kubernetes 上下文)
容器运行时containerd 2.3.3;/etc/containerd/certs.d 已按 registry 维度配置了 hosts.toml 加速
CNI / 代理Calico(VXLAN,vxlan.calico),kube-proxy 为 IPVS 模式
Ingressingress-nginx(IngressClass 名 nginx,控制器实际 Release v1.0.0),Service 为 NodePort 30080(HTTP) / 30443(HTTPS)
存储无 StorageClass、无 PV、无 PVC —— 全集群无持久化卷(Argo CD 安装本身不需要,但后续做有状态服务要留意)
可观测metrics-server 已装;kube-system 有 filebeat DaemonSet 对接 k8s-elk 节点;Kuboard v4 以 docker 容器方式跑在宿主机
已有 CRD仅 Calico + Kuboard,没有 Argo CD / Istio / Prometheus Operator 等

1.2 若依微服务的实际形态

应用全部落在 ruoyi 命名空间,形态是「7 个 Deployment + 7 个 NodePort Service + 1 个 Ingress」:

Deployment容器端口Service NodePort当前镜像 tag
ruoyi-gateway-deploy80803108020260827145338
ruoyi-auth-deploy92003108120260827145033
ruoyi-modules-system-deploy92013109320260827145603
ruoyi-modules-gen-deploy92023009120260827161313
ruoyi-modules-job-deploy92033009020260827162653
ruoyi-modules-file-deploy93003009220260827152043
ruoyi-visual-monitor-deploy91003009520260827191643

另外几个关键事实:

  • 镜像仓库:私有 Harbor 106.75.29.47:10086,项目 ruoyi-cloud,tag 一律是「秒级时间戳」(yyyyMMddHHmmss)。
  • 拉取凭据ruoyi 命名空间里有 harbor-secret / harbor-cred 两个 dockerconfigjson,Deployment 通过 imagePullSecrets 引用 harbor-secret
  • 配置管理:该命名空间只有一个 ConfigMap ruoyi-gateway-config(以 subPath 挂到 /home/ruoyi/application.yml);其余服务的配置是打进镜像里的。Redis 指向 106.75.29.47:6379
  • Ingressruoyi-gateway-ingressingressClassName: nginx,无域名(host*),全部 / 转发到 gateway。
  • 前端不在集群里ruoyi-ui 是宿主机 Nginx 直接托管 /opt/ruoyi/dist/etc/nginx/conf.d/ruoyi.confserver_name 106.75.30.92)。仓库里虽然有 ruoyi-ui 的 k8s 清单,但没有真的部署到集群
  • 中间件全在另一台机器106.75.29.47 上开放了 3306(MySQL)、6379(Redis)、8848/9848(Nacos)、10086(Harbor)、80/8080。

1.3 Jenkins 现状

现状
Jenkins Home/var/lib/jenkins(rpm 安装、systemd 托管,服务 active)
执行器numExecutors=2
Job 形态全部是 Multibranch PipelineJenkinsfile 在各仓库根目录
代码来源Gitee:https://gitee.com/pang_le/ruoyi-{gateway,auth,modules-system,modules-gen,modules-job,modules-file,visual-monitor,ui,dependencies,common}.git,分支过滤 prod*(实际分支 prod-k8s),未配置凭据 → 公开仓库
构建工具Maven-3.8.9 + JDK-17(Java 侧);前端用 npm + registry.npmmirror.com
凭据清单harbor-credentials(Harbor账号密码)、gitee-enterprise-tokendeploy-ssh-keynexus-admin
宿主机角色既是 Jenkins,又是 k8s 控制平面;nginx 占 80(Jenkins 反代 + 若依前端),java 占 8080/50000

现有 Jenkinsfile 的部署逻辑(以 gateway 为例,原文摘要)

groovy
stage('代码编译打包') → mvn clean package -Dmaven.test.skip=true
stage('构建 Docker 镜像') → docker build -t ${HARBOR_URL}:latest . && docker tag ... :$VERSION
stage('推送镜像到 Harbor') → docker push ...:latest && docker push ...:$VERSION
stage('部署到 K8s') → sed -i 's/:latest/:$VERSION/g' k8s/deployment.yaml
 kubectl apply -f k8s/deployment.yaml -n ruoyi
post.success → docker rmi ...(清理本地镜像)

其中 VERSION = date '+%Y%m%d%H%M%S'k8s/deployment.yaml 随代码仓库一起维护。

这套流程里有 4 个必须改造的点

  1. CI 承担了 CD:Jenkins 持有集群 admin kubeconfig 并直接 kubectl apply,构建与发布耦合,失败无法自动回滚。
  2. sed -i 污染工作区:Jenkinsfile 在构建时直接改写仓库里的 k8s/deployment.yaml,导致工作区 git 树长期处于 dirty 状态,清单文件在本地被“改坏”,下次全量构建会带着上次的 tag 继续滚。
  3. 镜像 tag 不可追溯:时间戳无法定位到代码提交,出问题只能靠时间猜。docker push 还同时推 latest 这种可变标签。
  4. 集群实况与仓库清单已经漂移:线上 ruoyi-gateway-deploy 实际挂载了 ruoyi-gateway-config(有 volumeMounts),但仓库里的 k8s/deployment.yaml 这段是被注释掉的。也就是说漏跑一次 CI 或重装一次集群,网关的配置文件挂载就会丢。这正是 GitOps 要解决的问题

02 目标架构与职责边界

改造后的职责划分用一句话概括:**Jenkins 只负责“把代码变成镜像并把镜像题写进 Git”,Argo CD 负责“把 Git 里的期望状态搬到集群”。**Jenkins 不再需要集群的写权限。

开发者push / MR业务代码仓库 (Gitee)ruoyi-gateway / auth / …分支 prod-k8s + JenkinsfileJenkins106.75.30.92 : 8080① mvn package② docker build / push③ 改 GitOps 仓库镜像 tag(不再持有集群写权限)Harbor 镜像仓库106.75.29.47:10086GitOps 清单仓库 (Gitee)ruoyi-gitops : 唯一事实来源镜像 tag 只在这里被改动Argo CD(部署在集群内 · argocd 命名空间)application-controller 持续比对 Git ↔ 集群repo-server 渲染清单 · server 提供 UI/API/CLI自动同步 / 自愈 / 漂移检测 / 一键回滚按 manifest 拉取镜像,不做构建Kubernetes v1.28.14 · ruoyi 命名空间(7 个服务)pushcommit(仅改 tag)watchsync拉取镜像
图 1 目标链路:代码 → Jenkins 构建镜像 → 改写 GitOps 仓库中的镜像版本 → Argo CD 同步到集群

2.1 改造前后对比

维度改造前改造后
部署指令来源Jenkins 执行 kubectl applyArgo CD 读取 Git,自动同步
集群写权限Jenkins 持有 admin kubeconfig只有 Argo CD 的 ServiceAccount 持有
期望状态存放散落在各业务仓库的 k8s/ 目录集中在一个 GitOps 仓库,可审计、可 Review
镜像 tag时间戳 + latestGit 短 SHA(不可变、可追溯)
漂移处理无人感知(手动 kubectl edit 无人知道)Argo CD 标记 OutOfSync,selfHeal 自动纠偏
回滚重新跑一次旧版本构建Git revert 或 Argo CD 一键回滚到任一历史版本
发布可见性只有构建日志UI 上能看到每个资源的同步状态、健康度、diff

2.2 关于 ruoyi-ui 前端的一个明确取舍

现状前端是宿主机 Nginx 托管静态目录,不在集群内。本次改造建议不动它,理由:它的发布物是静态文件而非镜像,纳入 GitOps 需要先把 ruoyi-ui 容器化后进集群,属于独立的架构调整,与本轮「引入 Argo CD」目标不同。文档里会给出一条可选路径(见 §10.6),但不作为本轮必须项。

03 版本选型与兼容性论证

这一步是整份方案里最容易踩坑的地方,必须先定死。

结论先行:不能装最新版 Argo CD 3.x

当前集群是 Kubernetes v1.28.14,而 Argo CD 官方「已测试版本矩阵」显示:3.3 / 3.4 / 3.5 只测试了 v1.32~v1.36,3.x 完全不覆盖 v1.28。硬装 3.x 会出现 CRD 版本与控制器行为不匹配的风险,且官方不背书。

官方矩阵中仍覆盖 v1.28 的最新分支是 2.14(测试 v1.31/v1.30/v1.29/v1.28),2.13 覆盖 v1.30/v1.29/v1.28/v1.27。

Argo CD 分支官方测试过的 Kubernetes 版本是否覆盖 v1.28说明
3.5 / 3.4 / 3.3v1.36~v1.32最新特性最全,但要求集群较新
2.14v1.31, v1.30, v1.29, v1.28本次选型,2.x 线最后一个覆盖 1.28 的分支
2.13v1.30, v1.29, v1.28, v1.27可作备选(若 2.14 出问题)
2.12 及更早v1.29 及以下过旧,不推荐新装

因此本方案锁定 Argo CD v2.14.21(2.14 分支末版)。

需要你知情的一个取舍

按 Argo CD 的支持策略,只维护最近 3 个 minor。2.14 目前已经不在补丁窗口内,意味着不会有安全补丁。两条路线:

  • 路线 A(本轮采用):先用 2.14.21 把 GitOps 链路跑通、把流程和习惯建立起来。集群版本不动,风险可控。
  • 路线 B(后续演进):把集群升级到 v1.33+,再切到 Argo CD 3.5.x。kubeadm 集群用 kubeadm upgrade 逐个小版本升级即可,1.28→1.33 需要跨 5 次,建议排在 GitOps 稳定运行之后做,并单独出升级方案。

3.1 装机位置与资源预算

集群内存偏紧(node1 76%、node2 93%),而 jenkins 节点带 control-plane 污点、Argo CD 的 Pod 不会调度上去。因此选 k8s-node3(7.4Gi 分配,当前 70%,余量约 2.2Gi,2 核)作为 Argo CD 的落脚点,并用 nodeSelector 固定,避免被调度到只剩几百 Mi 的 node1/node2 上导致 OOM。

组件类型建议 requests建议 limits
argocd-application-controllerStatefulSet200m / 384Mi1000m / 768Mi
argocd-repo-serverDeployment100m / 192Mi500m / 512Mi
argocd-serverDeployment50m / 128Mi300m / 256Mi
argocd-redisDeployment30m / 64Mi200m / 128Mi
argocd-applicationset-controllerDeployment30m / 64Mi200m / 128Mi
argocd-notifications-controllerDeployment20m / 64Mi100m / 128Mi
argocd-dex-serverDeployment—(可选,见 §4.5)
合计(不含 dex)≈430m / 900Mi≈2.3C / 1.9Gi

limits 之和超过 node3 的 2 核,但这是 limit 不是预留,实际稳态占用通常在 300m / 600Mi 量级。requests 的 900Mi 是真正把 node3 余量吃掉的部分,仍在安全范围内。

04 阶段一:安装 Argo CD(国内镜像全链路)

安装分三步:准备清单 → 把 3 个镜像搬进自己的 Harbor → apply 并打补丁。全程不依赖 quay.io 的裸网络。

4.1 先看清要装的到底是什么

v2.14.21install.yaml(1425427 字节)一共 59 个资源段,需要的外部镜像只有 3 个

清单中的镜像用途国内镜像源(已实测可用)
quay.io/argoproj/argocd:v2.14.21server / controller / repo-server / applicationset / notifications 共 8 处引用quay.m.daocloud.io/argoproj/argocd:v2.14.21
ghcr.io/dexidp/dex:v2.41.1SSO(Dex)ghcr.m.daocloud.io/dexidp/dex:v2.41.1
redis:7.2.11-alpine缓存m.daocloud.io/docker.io/library/redis:7.2.11-alpine

安装会产生:3 个 CRD、7 个 ServiceAccount、若干 Role/Binding、7 个 ConfigMap、2 个 Secret、8 个 Service、6 个 Deployment、1 个 StatefulSet(application-controller)、7 个 NetworkPolicy。

4.2 国内镜像地址实测结果

以下地址全部在 106.75.30.92 上实测通过

测试方法:按 Docker Registry v2 协议做匿名 token 换取后请求 manifest,返回 200 且能取到 amd64 架构即为可用。

用途地址实测结果备注
Argo CD 主镜像quay.m.daocloud.io/argoproj/argocd:v2.14.21manifest 200 · amd64/arm64DaoCloud quay 代理,首选
Argo CD 主镜像(备)quay.nju.edu.cn/argoproj/argocd:v2.14.21manifest 200南京大学镜像站
Dexghcr.m.daocloud.io/dexidp/dex:v2.41.1manifest 200ghcr 代理
Redism.daocloud.io/docker.io/library/redis:7.2.11-alpinemanifest 200Docker Hub 代理
安装清单 install.yamlhttps://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml200 · 1425427 B与官方 raw 内容字节一致
安装清单(备)https://ghfast.top/https://raw.githubusercontent.com/...200 · 1425427 B备选加速
安装清单(备)https://cdn.jsdelivr.net/gh/argoproj/argo-cd@v2.14.21/manifests/install.yaml200 · 1425427 BjsDelivr
argocd CLI 二进制https://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64200 · 14868892 BDaoCloud 文件代理,走这个
Helm Chart 仓库https://argoproj.github.io/argo-helmindex.yaml 200本环境可直连;备选 https://ben-wangz.github.io/helm-chart-mirror/charts

不可用的地址(别浪费时间)

  • swr.cn-north-4.myhuaweicloud.com/ddn-k8s/quay.io/argoproj/argocd404(该路径下无此镜像)
  • registry.cn-hangzhou.aliyuncs.com/argoproj/argocd401(该命名空间下无此镜像)
  • docker.m.daocloud.io/argoproj/argocd403(该域只代理 Docker Hub,不代理 quay)
  • registry-1.docker.io不可达,所以 redis 这类官方镜像必须走 m.daocloud.io/docker.io/ 前缀

4.3 第一步:准备安装清单

bash
# 在 106.75.30.92 上执行
mkdir -p /root/argocd/{manifests,offline} && cd /root/argocd

# 下载 v2.14.21 官方安装清单(走国内加速)
curl -fL --retry 3 -o manifests/install-v2.14.21.yaml \
 https://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml

# 校验:应为 1425427 字节
wc -c manifests/install-v2.14.21.yaml

# 下载 argocd CLI(可选,但强烈建议装,后面验收和触发同步都要用)
curl -fL --retry 3 -o /usr/local/bin/argocd \
 https://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64
chmod +x /usr/local/bin/argocd && argocd version --client

4.4 第二步:把 3 个镜像搬进自己的 Harbor

虽然 quay.io 在本环境能直连,但用它拉取有两个问题:速度不稳定、集群对公网有外部依赖。既然已经有 Harbor,把它当作集群的唯一镜像入口是更干净的架构——顺带也让 Argo CD 自身的升级走同一套流程。

统一镜像映射规则

全部收敛到一个 Harbor 项目 infra,便于管理和设置保留策略:

  • quay.io/argoproj/argocd:v2.14.21106.75.29.47:10086/infra/argocd:v2.14.21
  • ghcr.io/dexidp/dex:v2.41.1106.75.29.47:10086/infra/dex:v2.41.1
  • redis:7.2.11-alpine106.75.29.47:10086/infra/redis:7.2.11-alpine
bash
# 0) Harbor 里创建 infra 项目(若不存在)
# 也可以直接在执行 docker push 时由 Harbor 自动创建(若开启了自动创建项目)
# 手动创建:Harbor UI → 项目 → 新建项目 → 名称 infra → 私有

# 1) 登录 Harbor
docker login 106.75.29.47:10086 -u admin -p '@Pl000000'

# 2) 从国内镜像源拉取,重新打标签,推入 Harbor
set -e
HARBOR=106.75.29.47:10086

docker pull quay.m.daocloud.io/argoproj/argocd:v2.14.21
docker tag quay.m.daocloud.io/argoproj/argocd:v2.14.21 ${HARBOR}/infra/argocd:v2.14.21
docker push ${HARBOR}/infra/argocd:v2.14.21

docker pull ghcr.m.daocloud.io/dexidp/dex:v2.41.1
docker tag ghcr.m.daocloud.io/dexidp/dex:v2.41.1 ${HARBOR}/infra/dex:v2.41.1
docker push ${HARBOR}/infra/dex:v2.41.1

docker pull m.daocloud.io/docker.io/library/redis:7.2.11-alpine
docker tag m.daocloud.io/docker.io/library/redis:7.2.11-alpine ${HARBOR}/infra/redis:7.2.11-alpine
docker push ${HARBOR}/infra/redis:7.2.11-alpine

# 3) 清理本地临时镜像,避免占满 /(当前 99G 盘已用 58%)
docker rmi quay.m.daocloud.io/argoproj/argocd:v2.14.21 \
 ghcr.m.daocloud.io/dexidp/dex:v2.41.1 \
 m.daocloud.io/docker.io/library/redis:7.2.11-alpine || true

如果你更希望“不经过本地 docker”,也可以直接在集群节点上走 containerd 的 registry 镜像配置

这套环境已有 /etc/containerd/certs.d/<registry>/hosts.toml 机制(docker.ioregistry.k8s.io106.75.29.47:10086 三个目录已经存在)。加一个 quay.io 的即可,所有节点(jenkins / node1 / node2 / node3)都要配:

bash
mkdir -p /etc/containerd/certs.d/quay.io
cat > /etc/containerd/certs.d/quay.io/hosts.toml <<'EOF'
server = 'https://quay.io'

[host.'https://quay.m.daocloud.io']
 capabilities = ['pull', 'resolve']
 override_path = true
EOF
systemctl restart containerd # 注意:重启 containerd 会短暂影响该节点上的容器运行时

两种方式二选一。推荐用 Harbor 方案(§4.4 主线):集群不依赖外网、镜像有留存、后续升级路径统一;containerd 方案适合“先快速验证”的场景。

4.5 第三步:改造清单并安装

bash
cd /root/argocd
HARBOR=106.75.29.47:10086
cp manifests/install-v2.14.21.yaml manifests/install-harbor.yaml

# 把 3 个外部镜像全部替换成自己的 Harbor 地址
sed -i \
 -e "s#quay.io/argoproj/argocd:#${HARBOR}/infra/argocd:#g" \
 -e "s#ghcr.io/dexidp/dex:#${HARBOR}/infra/dex:#g" \
 -e "s#image: redis:7.2.11-alpine#image: ${HARBOR}/infra/redis:7.2.11-alpine#g" \
 manifests/install-harbor.yaml

# 确认替换干净:这里应该只剩下 infra/ 开头的 3 类镜像,没有 quay.io / ghcr.io / 裸 redis
grep -n 'image:' manifests/install-harbor.yaml | sort -u

# 创建命名空间并安装(CRD 体积大,必须 --server-side)
kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts -f manifests/install-harbor.yaml

随后给所有工作负载打上「节点固定 + 镜像拉取凭据 + 资源限额」的补丁。把下面这段存成 patch-argocd.sh

bash
#!/usr/bin/env bash
set -euo pipefail
NS=argocd
NODE=k8s-node3 # 内存余量最大的 worker
SECRET=harbor-secret # 与 ruoyi 命名空间同源,在 argocd 命名空间里重建一份

# 先在 argocd 命名空间创建拉取凭据(从 ruoyi 命名空间复制,避免明文再写一遍)
kubectl get secret harbor-secret -n ruoyi -o yaml \
 | sed -e 's/namespace: ruoyi/namespace: argocd/' \
 -e '/resourceVersion:/d' -e '/uid:/d' -e '/creationTimestamp:/d' \
 -e '/kubectl.kubernetes.io\/last-applied-configuration/d' \
 | kubectl apply -f -

# 统一补丁:节点选择 + 拉取凭据
patch_one() {
 local kind=$1 name=$2
 kubectl -n $NS patch $kind/$name --type=strategic -p "{
 \"spec\":{\"template\":{\"spec\":{
 \"nodeSelector\":{\"kubernetes.io/hostname\":\"$NODE\"},
 \"imagePullSecrets\":[{\"name\":\"$SECRET\"}]
 }}}}" || echo "!! $kind/$name 打补丁失败"
}

patch_one statefulset argocd-application-controller
for d in argocd-repo-server argocd-server argocd-redis \
 argocd-applicationset-controller argocd-notifications-controller argocd-dex-server; do
 patch_one deployment $d
done

# 资源限额(贴近 §3.1 的预算)
kubectl -n $NS set resources statefulset/argocd-application-controller \
 --requests=cpu=200m,memory=384Mi --limits=cpu=1000m,memory=768Mi --containers='*'
kubectl -n $NS set resources deployment/argocd-repo-server \
 --requests=cpu=100m,memory=192Mi --limits=cpu=500m,memory=512Mi --containers='*'
kubectl -n $NS set resources deployment/argocd-server \
 --requests=cpu=50m,memory=128Mi --limits=cpu=300m,memory=256Mi --containers='*'
kubectl -n $NS set resources deployment/argocd-redis \
 --requests=cpu=30m,memory=64Mi --limits=cpu=200m,memory=128Mi --containers='*'
kubectl -n $NS set resources deployment/argocd-applicationset-controller \
 --requests=cpu=30m,memory=64Mi --limits=cpu=200m,memory=128Mi --containers='*'
kubectl -n $NS set resources deployment/argocd-notifications-controller \
 --requests=cpu=20m,memory=64Mi --limits=cpu=100m,memory=128Mi --containers='*'
bash
bash patch-argocd.sh

# 等待就绪
kubectl -n argocd rollout status statefulset/argocd-application-controller --timeout=300s
kubectl -n argocd rollout status deployment/argocd-server --timeout=300s
kubectl -n argocd get pods -o wide

预期 7 个 Pod(server / repo-server / application-controller / redis / applicationset-controller / notifications-controller / dex-server)全部 Running,且都落在 k8s-node3 上。

关于 Dex 的取舍

你现在没有接 SSO,Dex 只用不到就属于纯开销。如果暂时只用 admin 单用户,可以把它去掉以省资源:

bash
kubectl -n argocd scale deployment/argocd-dex-server --replicas=0
kubectl -n argocd patch cm argocd-cm --type=merge -p '{"data":{"dex.config":""}}'

反过来,你这台机器上已经跑着 Casdoordocker ps 可见),它本身就是 OIDC Provider,后面接 SSO 时可以直接用 Casdoor 替代 Dex(见 §10.5)。

4.6 暴露 Web UI

集群已有 nginx IngressClass(NodePort 30080/30443),复用它是成本最低的做法。为了走纯 HTTP 便于起步,先把 Argo CD 自身的 TLS 关掉(由 Nginx 侧终结或直接明文内网访问):

bash
# 让 argocd-server 以 HTTP 提供服务(Ingress 后端用 HTTP)
kubectl -n argocd patch cm argocd-cmd-params-cm --type=merge \
 -p '{"data":{"server.insecure":"true"}}'
kubectl -n argocd rollout restart deployment/argocd-server
kubectl -n argocd rollout status deployment/argocd-server

# 顺带把资源跟踪方式改成注解,避免 Argo CD 往你现有的资源上塞 label
kubectl -n argocd patch cm argocd-cm --type=merge \
 -p '{"data":{"application.resourceTrackingMethod":"annotation"}}'
yaml
# 存为 /root/argocd/manifests/argocd-server-ingress.yaml
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: argocd-server-ingress
 namespace: argocd
 annotations:
 nginx.ingress.kubernetes.io/backend-protocol: "HTTP" # 已设 server.insecure=true
 nginx.ingress.kubernetes.io/ssl-redirect: "false"
 nginx.ingress.kubernetes.io/proxy-body-size: "0"
 nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
 ingressClassName: nginx
 rules:
 - host: argocd.wanfeng.com # 参考你现有的 jenkins.wanfeng.com 命名习惯
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: argocd-server
 port:
 number: 80
EOF

访问入口的两个注意点

  • Ingress 只是把流量转进来,还需要把 argocd.wanfeng.com 解析到某个节点 IP,并且从集群外访问时流量要走 NodePort 30080。如果只是本机学习,最简单的做法是用 kubectl port-forward 或直接改 /etc/hosts 指到 100.78.249.106(Tailscale 里就能通)。
  • argocd CLI 走的是 gRPC,通过 Ingress 时需要 HTTP/2。学习阶段建议 CLI 用 port-forward 直连,最省事: kubectl -n argocd port-forward svc/argocd-server 8080:80 --address 0.0.0.0 argocd login 127.0.0.1:8080 --insecure --username admin
    --password "$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d)"

首次登录后立刻改掉初始密码:

bash
argocd account update-password # 或 kubectl -n argocd delete secret argocd-initial-admin-secret

4.7 安装验收清单

检查项命令期望
Pod 就绪kubectl -n argocd get pods -o wide全部 Running,NODE 列都是 k8s-node3
镜像来源正确kubectl -n argocd get pods -o jsonpath='{..image}' | tr ' ' '\n' | sort -u只出现 106.75.29.47:10086/infra/
无外部依赖kubectl -n argocd get events --field-selector reason=Failed无 ImagePullBackOff / ErrImagePull
若依未受影响kubectl get pods -n ruoyi7 个 Pod 仍然 Running,重启次数不增加
节点无压力kubectl top nodesk8s-node3 内存未超过 85%
UI 可访问浏览器打开入口能登录并看到 Applications 页面

05 阶段二:构建 GitOps 清单仓库

5.1 核心原则:第一版清单必须从“活着的集群”导出

不要直接复用各业务仓库里的 k8s/deployment.yaml

勘察已经证实:线上 ruoyi-gateway-deploy 额外挂了 ruoyi-gateway-config 这个 ConfigMap,而仓库里的清单是注释掉的。如果拿仓库清单建 GitOps 仓库,Argo CD 第一次同步就会把网关的配置挂载删掉,网关会因缺少 application.yml 而异常。

正确做法:从集群导出当前真实状态,作为 GitOps 仓库的第一版,这样首次同步的 diff 为空,切换零风险。

5.2 导出并清洗清单

bash
mkdir -p /root/argocd/gitops-bootstrap && cd /root/argocd/gitops-bootstrap

# 导出 ruoyi 命名空间的应用类资源(含 Deployment / Service / Ingress / ConfigMap)
kubectl get deploy,svc,ingress,cm -n ruoyi -o yaml > ruoyi-live-raw.yaml

# 检查:确认 gateway 的 volumeMounts 在导出的内容里(这是关键校验)
grep -n -A3 'volumeMounts' ruoyi-live-raw.yaml | head -20
grep -n 'ruoyi-gateway-config' ruoyi-live-raw.yaml

导出结果里会带一批必须剥掉的运行时字段,否则每次同步都会显示漂移。用下面这个清洗脚本处理(kubectl neat 或手工 jq 都可以,这里给纯 jq 版本,不引入额外插件):

python
# 存为 clean.py,在 bootstrap 目录执行
cat > clean.py <<'PYEOF'
import subprocess, yaml, sys, re

DROP_META = ["creationTimestamp","resourceVersion","uid","generation",
 "selfLink","managedFields","annotations"]
KEEP_ANNOT = {"nginx.ingress.kubernetes.io/proxy-body-size",
 "nginx.ingress.kubernetes.io/proxy-read-timeout",
 "nginx.ingress.kubernetes.io/backend-protocol"}

docs = list(yaml.safe_load_all(open(sys.argv[1], encoding="utf-8")))
out = []
for d in docs:
 if not d or d.get("kind") == "List":
 for it in (d or {}).get("items", []): out.append(it)
 continue
 out.append(d)

def scrub(obj):
 if isinstance(obj, dict):
 for k in list(obj.keys()):
 if k in ("creationTimestamp","resourceVersion","uid","generation",
 "selfLink","managedFields"):
 obj.pop(k, None)
 elif k == "annotations":
 a = obj.pop("annotations") or {}
 keep = {kk: vv for kk, vv in a.items()
 if kk in KEEP_ANNOT or not kk.startswith(("kubectl.kubernetes.io",
 "deployment.kubernetes.io",
 "argocd.argoproj.io"))}
 if keep: obj["annotations"] = keep
 else:
 scrub(obj[k])
 elif isinstance(obj, list):
 for i in obj: scrub(i)
 return obj

res = [scrub(d) for d in out]
print(yaml.safe_dump_all(res, allow_unicode=True, sort_keys=False, default_flow_style=False))
PYEOF

python3 clean.py ruoyi-live-raw.yaml > ruoyi-live-clean.yaml
grep -n 'ruoyi-gateway-config' ruoyi-live-clean.yaml # 仍然要在
grep -c 'last-applied-configuration' ruoyi-live-clean.yaml # 应为 0

5.3 仓库目录结构

新建一个独立仓库,例如 https://gitee.com/pang_le/ruoyi-gitops.git。结构如下(按服务拆分,便于 Argo CD 用不同 Application 独立同步、独立回滚):

bash
ruoyi-gitops/
├── README.md
├── argocd/ # Argo CD 自身资源(AppProject + Application)
 ├── project-ruoyi.yaml
 ├── app-ruoyi-gateway.yaml
 ├── app-ruoyi-auth.yaml
 ├── app-ruoyi-modules-system.yaml
 ├── app-ruoyi-modules-gen.yaml
 ├── app-ruoyi-modules-job.yaml
 ├── app-ruoyi-modules-file.yaml
 ├── app-ruoyi-visual-monitor.yaml
 └── app-ruoyi-ingress.yaml
└── environments/
 └── prod/
 ├── kustomization.yaml
 ├── namespace.yaml # 只在首次创建时用,之后 CreateNamespace=false
 ├── ingress.yaml
 ├── config/
 └── ruoyi-gateway-config.yaml
 └── services/
 ├── gateway/
 ├── kustomization.yaml
 ├── deployment.yaml
 └── service.yaml
 ├── auth/…
 ├── modules-system/…
 ├── modules-gen/…
 ├── modules-job/…
 ├── modules-file/…
 └── visual-monitor/…

每个服务的 kustomization.yaml 长这样,Jenkins 后续只改 newTag 这一行

yaml
# environments/prod/services/gateway/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ruoyi
resources:
 - deployment.yaml
 - service.yaml
images:
 - name: 106.75.29.47:10086/ruoyi-cloud/ruoyi-gateway
 newTag: prod-k8s-a3e752d # ← Jenkins 每次只改这一行

deployment.yaml 里镜像就写“不含 tag”的地址,由 Kustomize 注入:

bash
spec:
 containers:
 - name: ruoyi-gateway
 image: 106.75.29.47:10086/ruoyi-cloud/ruoyi-gateway
 imagePullPolicy: IfNotPresent # 原来 Always,改成 IfNotPresent 更省拉取
 volumeMounts: # ← 从线上导出的,务必保留
 - name: gateway-config
 mountPath: /home/ruoyi/application.yml
 subPath: application.yml
 volumes:
 - name: gateway-config
 configMap:
 name: ruoyi-gateway-config

关于 harbor-secret:不要提交进 Git

harbor-secret / harbor-cred 是明文的 dockerconfigjson,进 Git 等于把 Harbor 密码公开。

  • 本轮:保持它手工存在(现在就在集群里),GitOps 仓库里不包含 Secret,Application 里把 Secret 排除在追踪之外即可。
  • 进阶(推荐后续做):引入 Sealed SecretsExternal Secrets Operator,把加密后的密文提交进 Git,实现全量声明式。这与本轮改造解耦,可单独排期。

5.4 推送仓库

bash
cd /root/argocd/gitops-bootstrap
# 按 §5.3 的目录把 ruoyi-live-clean.yaml 拆分到各服务目录(拆分是一次性工作)
git init -b main
git add -A && git commit -m "chore: bootstrap GitOps manifests from live cluster (2026-09-23)"
git remote add origin https://gitee.com/pang_le/ruoyi-gitops.git
git push -u origin main

06 阶段三:Argo CD 应用与项目配置

6.1 用 AppProject 划边界

AppProject 把「这个项目能用哪些 Git 仓库、能往哪些命名空间、能创建哪些资源类型」约束住。这是多团队环境下最重要的安全边界:

yaml
# argocd/project-ruoyi.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
 name: ruoyi
 namespace: argocd
 finalizers:
 - resources-finalizer.argocd.argoproj.io
spec:
 description: 若依微服务(RuoYi-Cloud)生产环境
 sourceRepos:
 - https://gitee.com/pang_le/ruoyi-gitops.git
 destinations:
 - namespace: ruoyi
 server: https://kubernetes.default.svc
 # 只允许部署这些类型,防止误建 ClusterRole 之类的高危资源
 clusterResourceWhitelist: []
 namespaceResourceWhitelist:
 - group: apps
 kind: Deployment
 - group: ""
 kind: Service
 - group: ""
 kind: ConfigMap
 - group: networking.k8s.io
 kind: Ingress
 orphanedResources:
 warn: true
bash
kubectl apply -f argocd/project-ruoyi.yaml
argocd proj get ruoyi

6.2 每个服务一个 Application

yaml
# argocd/app-ruoyi-gateway.yaml(其余 6 个服务同构,只改名称与路径)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
 name: ruoyi-gateway
 namespace: argocd
 annotations:
 argocd.argoproj.io/sync-wave: "10" # 网关后于后端服务同步
 finalizers:
 - resources-finalizer.argocd.argoproj.io
spec:
 project: ruoyi
 source:
 repoURL: https://gitee.com/pang_le/ruoyi-gitops.git
 targetRevision: main
 path: environments/prod/services/gateway
 destination:
 server: https://kubernetes.default.svc
 namespace: ruoyi
 syncPolicy:
 automated:
 prune: true # Git 里删掉的资源,集群里也删掉
 selfHeal: true # 有人手动改集群,自动纠偏回来
 allowEmpty: false
 syncOptions:
 - CreateNamespace=false # ruoyi 已存在
 - ApplyOutOfSyncOnly=true # 只 apply 有差异的资源,减少 API 压力
 - PruneLast=true
 - ServerSideApply=true
 retry:
 limit: 5
 backoff:
 duration: 10s
 factor: 2
 maxDuration: 3m
 revisionHistoryLimit: 20 # 保留 20 个历史版本,方便回滚

prune: true 是把双刃剑,第一次上线务必先关

prune 会把「集群里有、Git 里没有」的资源删掉。第一次接管的正确姿势:prune: false + 手动同步,确认 diff 只有预期的内容之后,再打开 pruneselfHeal。具体节奏见 §8。

6.3 关于同步顺序的一个细节

若依的服务之间存在依赖(gateway 依赖后端服务注册到 Nacos,auth 依赖 system)。Argo CD 的 sync-wave 建议:

  • 0:ConfigMap(ruoyi-gateway-config
  • 5:auth、modules-system、modules-gen、modules-job、modules-file、visual-monitor
  • 10:gateway
  • 20:Ingress

注意每个服务的 readinessProbe 是保证顺序的必要前提——目前 Deployment 没有配置探针(勘察确认 readinessProbe/livenessProbe 均为空)。没有探针时 Pod 一启动就被视为 Ready,滚动更新会出现“新 Pod 还没起来就被切断流量”的窗口。强烈建议这次一起补上,否则 Argo CD 的同步状态无法反映真实可用性。

bash
readinessProbe:
 tcpSocket: { port: 8080 } # 或 /actuator/health
 initialDelaySeconds: 40
 periodSeconds: 10
 failureThreshold: 3
livenessProbe:
 tcpSocket: { port: 8080 }
 initialDelaySeconds: 90
 periodSeconds: 20

6.4 让 Argo CD 感知到 Git 变更

Argo CD 默认每 3 分钟轮询一次 Git。要“提交即同步”,两种方式:

方式做法适用
Jenkins 主动触发(本轮采用)CI 末尾调用 argocd app sync--wait,把部署结果带回构建日志希望「构建日志里能看到部署成功/失败」,闭环清晰
Gitee Webhook → Argo CDArgo CD 提供 /api/webhook,在 Gitee 仓库配 Webhook 指向它有人直接改 GitOps 仓库时也能秒级生效
什么都不配等默认 3 分钟轮询学习阶段最省事

读取 webhook 的 secret 并配置(可选):

bash
kubectl -n argocd get secret argocd-secret -o jsonpath='{.data.webhook\.gitee\.secret}' | base64 -d; echo
# Gitee 侧:仓库 → 管理 → WebHooks → URL 填
# http://<argocd入口>/api/webhook
# 密钥:用 argocd-cm 里 webhook.gitee.secret 配置的那个值

07 阶段四:Jenkins 流水线改造

7.1 版本号规则:换成 Git 短 SHA

镜像 tag 用 Git SHA 是 GitOps 的地基

Argo CD 靠“清单里的内容变了”来决定是否同步。如果 tag 是 latest 或者复用同一个 tag 反复覆盖,清单内容不变 → Argo CD 认为没有变化 → 不会触发同步;即便强制同步,回滚也无从下手(同一个 tag 指不同的东西)。

换成不可变的 Git 短 SHA 后:tag ↔ 提交一一对应,diff 天然可见,回滚只是把这个 tag 改回上一个值。

bash
// pipeline 顶层 environment
VERSION = "${BRANCH_NAME}-${GIT_COMMIT.take(7)}" // 例:prod-k8s-a3e752d

7.2 改造后的 Jenkinsfile(以 gateway 为例)

groovy
pipeline {
 agent any
 tools {
 maven 'Maven-3.8.9'
 jdk 'JDK-17'
 }
 environment {
 APP_NAME = 'ruoyi-gateway'
 NAMESPACE = 'ruoyi'
 HARBOR = '106.75.29.47:10086'
 IMAGE_REPO = "${HARBOR}/ruoyi-cloud/${APP_NAME}"
 HARBOR_CRED_ID = 'harbor-credentials'
 GITOPS_REPO = 'https://gitee.com/pang_le/ruoyi-gitops.git'
 GITOPS_DIR = 'environments/prod/services/gateway'
 GITOPS_CRED_ID = 'gitee-gitops-credentials' // 新增凭据,见 §7.3
 ARGOCD_APP = 'ruoyi-gateway'
 ARGOCD_SERVER = 'argocd-server.argocd.svc.cluster.local:80'
 VERSION = "${env.BRANCH_NAME}-${env.GIT_COMMIT.take(7)}"
 MAVEN_OPTS = '-Dmaven.compiler.source=17 -Dmaven.compiler.target=17 -Dproject.build.sourceEncoding=UTF-8'
 }
 options { disableConcurrentBuilds(); timestamps() }

 stages {
 stage('1. 编译打包 (CI)') {
 steps { sh 'mvn clean package -Dmaven.test.skip=true' }
 }

 stage('2. 构建并推送镜像 (CI)') {
 steps {
 withCredentials([usernamePassword(credentialsId: env.HARBOR_CRED_ID,
 usernameVariable: 'HU', passwordVariable: 'HP')]) {
 sh """
 echo "\$HP" | docker login ${HARBOR} -u "\$HU" --password-stdin
 docker build --build-arg JAR_FILE=target/${APP_NAME}.jar \
 -t ${IMAGE_REPO}:${VERSION} .
 docker push ${IMAGE_REPO}:${VERSION}
 docker rmi ${IMAGE_REPO}:${VERSION} || true
 """
 }
 }
 }

 stage('3. 更新 GitOps 仓库镜像版本 (CD 触发点)') {
 steps {
 withCredentials([usernamePassword(credentialsId: env.GITOPS_CRED_ID,
 usernameVariable: 'GU', passwordVariable: 'GP')]) {
 sh """
 rm -rf .gitops-tmp
 git clone -q \
 https://\${GU}:\${GP}@gitee.com/pang_le/ruoyi-gitops.git .gitops-tmp
 cd .gitops-tmp
 git config user.email "jenkins@wanfeng.com"
 git config user.name "Jenkins CI"

 cd ${GITOPS_DIR}
 # 只改 kustomization.yaml 里的 newTag 一行,幂等且无副作用
 sed -i -E "s#(newTag:).*#\\1 ${VERSION}#" kustomization.yaml
 cd ../..

 git add -A
 git diff --cached --quiet && echo "镜像版本未变化,跳过提交" || {
 git commit -m "ci(${APP_NAME}): bump image to ${VERSION} [skip ci]"
 git push origin HEAD:main
 }
 """
 }
 }
 }

 stage('4. 等待 Argo CD 完成同步 (CD)') {
 steps {
 withCredentials([string(credentialsId: 'argocd-api-token', variable: 'ARGOCD_TOKEN')]) {
 sh """
 argocd app sync ${ARGOCD_APP} --grpc-web --insecure \
 --server ${ARGOCD_SERVER} --auth-token \$ARGOCD_TOKEN
 argocd app wait ${ARGOCD_APP} --grpc-web --insecure \
 --server ${ARGOCD_SERVER} --auth-token \$ARGOCD_TOKEN \
 --health --timeout 300
 """
 }
 }
 }
 }

 post {
 success { echo "✅ 构建并发布成功:${IMAGE_REPO}:${VERSION}" }
 failure {
 echo "❌ 构建或发布失败,镜像 ${IMAGE_REPO}:${VERSION}"
 // 可选:自动回滚 —— 把 GitOps 仓库回退到上一个提交
 // sh "cd .gitops-tmp && git revert --no-edit HEAD && git push origin HEAD:main"
 }
 }
}

相比旧流水线,这里有 4 个具体变化

  1. 删除了 kubectl apply —— Jenkins 不再需要集群写权限。
  2. 删除了 sed -i ... k8s/deployment.yaml —— 不再污染构建工作区,清单文件保持干净。
  3. 不再推 latest —— 只推不可变 SHA tag,避免「谁在跑什么版本」说不清。
  4. 新增「等 Argo CD 同步完成」阶段 —— 构建结果与部署结果统一在一处呈现,失败会被 Jenkins 标红。

7.3 需要新增的两样东西

类型ID内容用途
Username/Password 凭据gitee-gitops-credentialsGitee 账号 + 私人令牌(或仓库访问令牌)CI 推送 GitOps 仓库
Secret textargocd-api-tokenArgo CD 生成的 API TokenCI 触发并等待同步

Argo CD Token 的生成方式(在装好 CLI 之后):

bash
# 建议为 CI 单独建一个账号,而不是用 admin
argocd account create ci-bot
argocd account generate-token --account ci-bot --expires-in 8760h

同时给它收权限(避免 CI 用超管令牌):

bash
kubectl -n argocd patch cm argocd-rbac-cm --type=merge -p '{"data":{
 "policy.csv":"p, role:ci, applications, sync, ruoyi/*, allow\np, role:ci, applications, get, ruoyi/*, allow\ng, ci-bot, role:ci\n"
}}'

7.4 各服务的分支与 Job 命名建议

你现在的模式是「一个服务一个 Multibranch Job、监听 prod* 分支」。这个模式本身没问题,改造时可以保留,只需注意:

  • 新增的 GitOps 仓库是第 11 个仓库,它不需要建 Jenkins Job(它由 Argo CD 消费,不由 Jenkins 构建)。
  • ruoyi-dependenciesruoyi-common 这类只被依赖的父 POM 仓库,可以继续用现有方式(只需 install 到 Nexus / 本地仓库),不产生镜像也就不参与 Argo CD
  • 第一个先改的 Job,建议选 ruoyi-modules-genruoyi-visual-monitor——它们在最边上,出错影响面最小。

08 阶段五:灰度切换与验收

核心原则:新旧两套机制并存一段时间,逐个服务切换,任何一步都能立刻退回。

8.1 切换顺序

步骤动作验证点回退方式
0备份:kubectl get all,cm,secret,ingress -n ruoyi -o yaml > backup-$(date +%F).yaml;导出 Jenkins JENKINS_HOME 关键配置备份文件可读、非空
1安装 Argo CD(§4),不动任何业务负载若依 7 个 Pod 无变化kubectl delete ns argocd
2建 GitOps 仓库(§5),只做仓库,先不建 Application清单里含 gateway 的 volumeMounts删仓库
3建 AppProject + 只建 1 个 Application(选影响最小的服务),automated 先关掉UI 上显示 Synced 或 diff 仅为你预期的内容kubectl delete app <name> -n argocd(资源不会被删)
4手动 Sync 一次,观察 diff 与结果Pod 未重启(或正常滚动一次);接口可用argocd app rollback <name>
5打开 selfHeal: true,先不开 prune手动改集群里一个 label,看是否被自动纠正改回 selfHeal: false
6改造该服务的 Jenkinsfile(删掉 kubectl 阶段),跑一次完整流水线镜像 tag 变化 → GitOps 仓库出现 commit → Argo CD 自动同步 → Pod 更新Jenkins Job 回滚到旧 Jenkinsfile(git revert)
7确认稳定后,打开 prune: trueGit 里删一个无用资源,集群里同步消失关闭 prune
8按同样节奏推广到其余 6 个服务 + Ingress逐个服务独立验证逐个回退

8.2 关键验收动作(可执行的验证脚本)

bash
# A. Argo CD 视角:哪些应用不同步 / 不健康
argocd app list -o wide
argocd app get ruoyi-gateway
argocd app diff ruoyi-gateway # 看具体差异在哪

# B. 集群视角:期望副本 vs 实际副本
kubectl -n ruoyi get deploy -o custom-columns=\
'NAME:.metadata.name,DESIRED:.spec.replicas,READY:.status.readyReplicas,IMAGE:.spec.template.spec.containers[0].image'

# C. 端到端可用性(沿用你已有的测试方式,补一条命令版)
curl -s -o /dev/null -w 'gateway HTTP %{http_code}\n' http://100.78.249.106:31080/
curl -s -o /dev/null -w 'ingress HTTP %{http_code}\n' -H 'Host: 106.75.30.92' http://100.78.249.106:30080/

# D. 漂移检测演练(验证 selfHeal 确实在工作)
kubectl -n ruoyi label deploy ruoyi-gateway-deploy chaos-test=yes
sleep 60
kubectl -n ruoyi get deploy ruoyi-gateway-deploy -o jsonpath='{.metadata.labels}'; echo
# 期望:chaos-test 标签被 Argo CD 自动清除

# E. 回滚演练
argocd app history ruoyi-gateway
argocd app rollback ruoyi-gateway <REVISION_ID>

8.3 验收通过标准

  • 所有 Application 状态为 SyncedHealthy
  • CI 触发一次代码变更后,全链路自动完成:无需人工 kubectl 介入。
  • 故意手动改动集群 → 60 秒内被 selfHeal 纠正回来。
  • 回滚演练成功:能在 2 分钟内把某个服务回到上一个版本。
  • Jenkins 凭据中不再需要集群 kubeconfig。

09 回滚、故障处理与运维

9.1 三级回滚手段

层级手段耗时适用
一:应用级argocd app rollback <app> <revision>秒级发错版本、需要立刻回退。Argo CD 用 revisionHistoryLimit 保留历史
二:Git 级在 GitOps 仓库 git revert <commit> 并推送1~2 分钟需要留审计记录、正规回滚
三:K8s 原生kubectl -n ruoyi rollout undo deploy/<name>秒级Argo CD 不可用时的应急通道(注意:selfHeal 开着时会被纠回,需先暂停自动同步)

紧急情况下的正确操作顺序

  1. 先关自动同步:argocd app set <app> --sync-policy none(或 UI 上 Disable Auto-Sync)。否则你手工操作会被 selfHeal 反复覆盖。
  2. 再做应急处置。
  3. 事后把最终状态补回 Git,再打开自动同步,否则下次同步会把你的应急操作推翻。

9.2 常见故障速查

现象可能原因处理
Application 一直 OutOfSync,diff 里有 creationTimestamp / resourceVersion导出清单时没清洗运行时字段用 §5.2 的脚本重新清洗
diff 只有 statusmetadata.annotations 差异被其他控制器(如 kubectl edit)写入ignoreDifferences 忽略,或接受 Argo CD 纠偏
repo-server 报克隆失败Gitee 网络波动 / 凭据过期kubectl -n argocd logs deploy/argocd-repo-server;公开仓库不需要凭据
Pod 卡在 ImagePullBackOffHarbor 不可达 / harbor-secret 过期kubectl -n ruoyi describe pod ...;确认 106.75.29.47:10086 连通
Argo CD 自身被 OOMKilledrepo-server 处理大仓库时内存飙升调大 repo-server limits;给 repo-server 加 --parallelismlimit
同步成功但服务不可用缺少 readinessProbe,Pod 未真正就绪就被判 Healthy补探针(§6.3)
NetworkPolicy 导致 UI 打不开Calico 拦截kubectl -n argocd get netpol;Argo CD 默认策略是宽松的,若被改过需检查

9.3 日常运维要点

  • 备份:Argo CD 的配置都在 argocd 命名空间,定期 kubectl -n argocd get cm,secret -o yaml > argocd-backup-$(date +%F).yaml。Application/AppProject 本身也建议纳入 Git(argocd/ 目录即可)。
  • 升级:Argo CD 自身也用 GitOps 管起来是终态,但起步阶段手工升级更直观:替换 install.yaml 的版本号 → 重新 apply → 观察。升级前务必查当次 release note 的 breaking change。
  • 监控:Argo CD 自带 Prometheus 指标(argocd-metrics / argocd-server-metrics Service 已存在)。可以接到你已有的 ELK 或后续上 Prometheus。核心告警指标:argocd_app_info{sync_status!="Synced"}argocd_app_info{health_status!="Healthy"}
  • 清理:Harbor 上给 ruoyi-cloud 项目配保留策略(比如保留最近 30 个 tag),否则 SHA tag 会快速堆积。当前 / 已用 58%(57G/99G)。

10 风险清单与注意事项

#风险 / 事项影响应对
1Argo CD 2.14 已不在补丁窗口因集群 v1.28 无法上 3.x。方案:本轮用 2.14.21,内网使用 + RBAC 收敛;后续安排集群升级到 v1.33+ 后切 3.5.x
2k8s-node2 内存已用 93%Argo CD 必须用 nodeSelector 钉在 k8s-node3,并设 requests 上限。建议同时排查 node2 上是什么占了内存(6 个 Pod,含 4 个若依服务)
3首次同步可能删掉 gateway 的 ConfigMap 挂载必须用「从线上导出」的第一版清单(§5.1/§5.2),并核对 ruoyi-gateway-config 在导出内容中
4prune: true 误删资源切换期先关闭(§8.1 步骤 5→7),确认应用清单完整后再打开
5无 readinessProbe,健康状态失真本次一并补齐探针,否则 Argo CD 的 Healthy 判断和滚动更新都不可靠
6Jenkins 与控制平面同机,且磁盘已用 58%Argo CD 装好后 Jenkins 负载会下降;但仍要给 / 留余量,Harbor 保留策略要配
7集群无 StorageClass / PVArgo CD 本身不需要,但若后续要 Argo CD Image Updater、或把 Nacos/MySQL 搬进集群,必须先解决存储
8Gitee 作为唯一 GitOps 源Gitee 抖动会阻塞发布。已确认 gitee.com 可达、仓库为公开可读;建议给 GitOps 仓库开保护分支 + 单独令牌,并留意 85.137.247.103:8765 的 GitLab 当前不可达,不要作为依赖
9NetworkPolicy 与 Calicoinstall.yaml 自带 7 条 NetworkPolicy,默认宽松;若后续收紧网络策略需回归验证 UI/CLI 可用性
10ingress-nginx 控制器为 v1.0.0较老,但已稳定运行 28 天且能承载现有 Ingress。给 Argo CD 加 Ingress 前先在测试路径验证;如遇 HTTP/2 问题改用 CLI port-forward
11Harbor 明文密码散布Jenkinsfile 注释里、containerd hosts.toml 里都存在 admin/@Pl000000。建议改用 Harbor robot account 专用凭据,并撤销 admin 的长期使用

10.1 关于 imagePullPolicy

现在的 Deployment 是 Always。换成不可变 SHA tag 后,Always 只会带来额外的一次 registry 查询而不会有正确性收益,建议改 IfNotPresent,能明显减少 Harbor 压力和 Pod 启动时间。

10.2 关于 NodePort 泛滥

7 个 Service 全是 NodePort,其中 gateway 同时被 NodePort 31080 和 Ingress 暴露。这属于历史遗留。本次改造不建议顺手改(会变更访问入口),但可以在 GitOps 稳定后单独排期收敛为 ClusterIP + Ingress

10.3 关于多个并发构建

Jenkins numExecutors=2,而每个 Job 都设了 disableConcurrentBuilds()。7 个服务同时提交时会有排队。改造后每条流水线多了一个「等 Argo CD 同步」的阶段(最长 5 分钟),需要评估构建队列是否会变长。如果出现堆积,可以把 --timeout 调小到 180s,或把「等待同步」改成只提交不等待(由 Argo CD 轮询/webhook 兜底)。

10.4 关于 ruoyi-ui 的三种后续路径

  1. 保持现状(本轮):宿主机 Nginx 托管,不进集群,不纳入 GitOps。
  2. 容器化进集群:用现有 Dockerfile 构建 ruoyi-ui 镜像,Deployment + ConfigMap(ruoyi-ui-nginx-config 仓库里已有)+ Ingress,然后交给 Argo CD。
  3. 保留静态目录但用 Argo CD 管清单:把 Nginx 配置纳入 GitOps(这块其实更适合 Ansible,不推荐)。

10.5 可选增强:用已有的 Casdoor 做 SSO

你机器上已经跑着 casbin/casdoor:latest,它本身就是 OIDC Provider。这样可以不装 Dex,直接在 argocd-cm 里配 oidc.config,用 Casdoor 做统一登录,和 /opt/maxkey/opt/CASdoor 的现有 IAM 体系统一。属于本轮之后的增量优化。

10.6 可选增强:Argo CD Image Updater

如果你希望「CI 只管推镜像,GitOps 仓库的 tag 由 Argo CD 自动改写」,可以引入 Image Updater。但它需要写回 Git,并且要额外访问 Harbor 的 registry API。本轮不推荐——由 Jenkins 写 tag 更直观、可控性更强,也更符合「CI 管构建、CD 管部署」这个你想学的心智模型。

11 附录:命令速查

11.1 安装链路(按顺序执行)

bash
# 1 准备清单与 CLI
mkdir -p /root/argocd/manifests && cd /root/argocd
curl -fL --retry 3 -o manifests/install-v2.14.21.yaml \
 https://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml
curl -fL --retry 3 -o /usr/local/bin/argocd \
 https://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64
chmod +x /usr/local/bin/argocd

# 2 镜像入库(Harbor 项目 infra)
HARBOR=106.75.29.47:10086
docker login ${HARBOR} -u admin -p '@Pl000000'
docker pull quay.m.daocloud.io/argoproj/argocd:v2.14.21 && \
 docker tag quay.m.daocloud.io/argoproj/argocd:v2.14.21 ${HARBOR}/infra/argocd:v2.14.21 && \
 docker push ${HARBOR}/infra/argocd:v2.14.21
docker pull ghcr.m.daocloud.io/dexidp/dex:v2.41.1 && \
 docker tag ghcr.m.daocloud.io/dexidp/dex:v2.41.1 ${HARBOR}/infra/dex:v2.41.1 && \
 docker push ${HARBOR}/infra/dex:v2.41.1
docker pull m.daocloud.io/docker.io/library/redis:7.2.11-alpine && \
 docker tag m.daocloud.io/docker.io/library/redis:7.2.11-alpine ${HARBOR}/infra/redis:7.2.11-alpine && \
 docker push ${HARBOR}/infra/redis:7.2.11-alpine

# 3 改镜像并安装
sed -i -e "s#quay.io/argoproj/argocd:#${HARBOR}/infra/argocd:#g" \
 -e "s#ghcr.io/dexidp/dex:#${HARBOR}/infra/dex:#g" \
 -e "s#image: redis:7.2.11-alpine#image: ${HARBOR}/infra/redis:7.2.11-alpine#g" \
 manifests/install-v2.14.21.yaml
kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts -f manifests/install-v2.14.21.yaml

# 4 打补丁(节点/凭据/资源/HTTP)
bash patch-argocd.sh
kubectl -n argocd patch cm argocd-cmd-params-cm --type=merge -p '{"data":{"server.insecure":"true"}}'
kubectl -n argocd patch cm argocd-cm --type=merge -p '{"data":{"application.resourceTrackingMethod":"annotation"}}'
kubectl -n argocd rollout restart deployment/argocd-server

# 5 登录
kubectl -n argocd port-forward svc/argocd-server 8080:80 --address 0.0.0.0 &
argocd login 127.0.0.1:8080 --insecure --username admin \
 --password "$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d)"

11.2 常用运维命令

bash
argocd app list -o wide # 所有应用状态一览
argocd app get <app> # 单应用详情(含资源树)
argocd app diff <app> # 差异明细
argocd app sync <app> --prune # 手动同步
argocd app history <app> # 历史版本
argocd app rollback <app> <rev> # 回滚
argocd app set <app> --sync-policy none # 关自动同步(应急处置前必做)
argocd app set <app> --sync-policy automated --self-heal --auto-prune

kubectl -n argocd logs -f statefulset/argocd-application-controller # 控制器日志
kubectl -n argocd logs -f deploy/argocd-repo-server # 渲染/拉取 Git 的问题看这里
kubectl -n argocd get events --sort-by=.lastTimestamp | tail -30

11.3 本次实测过的镜像 / 下载地址汇总

用途地址结果
Argo CD 镜像quay.m.daocloud.io/argoproj/argocd:v2.14.21200
Argo CD 镜像(备)quay.nju.edu.cn/argoproj/argocd:v2.14.21200
Dex 镜像ghcr.m.daocloud.io/dexidp/dex:v2.41.1200
Redis 镜像m.daocloud.io/docker.io/library/redis:7.2.11-alpine200
install.yamlhttps://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml200 / 1425427 B
install.yaml(备)https://ghfast.top/https://raw.githubusercontent.com/…200 / 1425427 B
install.yaml(备)https://cdn.jsdelivr.net/gh/argoproj/argo-cd@v2.14.21/manifests/install.yaml200 / 1425427 B
argocd CLIhttps://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64200 / 14868892 B
Helm 仓库https://argoproj.github.io/argo-helm200
Helm 仓库(备)https://ben-wangz.github.io/helm-chart-mirror/charts200

文档说明:本方案基于 2026-09-23 对 106.75.30.92 的只读勘察结果编写,所有「现状」描述均有现场依据;所有镜像与下载地址均已在该服务器上实测。文中命令尚未执行,请按 §8 的顺序在确认后逐步落地。

建议把本文档与 GitOps 仓库一起纳入版本管理,作为架构决策记录(ADR)留存。

基于 VitePress 构建 · 内容为个人学习与工程实践笔记