GitOps 改造方案 · 只读勘察 + 设计

Argo CD 部署与 Jenkins(CI) / Argo CD(CD) 流程改造方案

面向 106.75.30.92 上已有的 Jenkins + Kubernetes(若依微服务已上线运行)环境,在不影响现网的前提下引入 GitOps 持续交付。

勘察时间2026-09-23 17:2x(本机时间)
勘察方式SSH 只读登录,未执行任何变更
集群版本Kubernetes v1.28.14(kubeadm)
选定版本Argo CD v2.14.21(含兼容性论证)
镜像策略全部走国内镜像源,已逐条实测
现网状态7 个若依服务 Running,前端由宿主机 Nginx 提供
关于本次勘察的边界

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

  1. 现状勘察结论
  2. 目标架构与职责边界
  3. 版本选型与兼容性论证
  4. 阶段一:安装 Argo CD(国内镜像全链路)
  5. 阶段二:构建 GitOps 清单仓库
  6. 阶段三:Argo CD 应用与项目配置
  7. 阶段四:Jenkins 流水线改造
  8. 阶段五:灰度切换与验收
  9. 回滚、故障处理与运维
  10. 风险清单与注意事项
  11. 附录:命令速查

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

另外几个关键事实:

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 为例,原文摘要)

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 + Jenkinsfile Jenkins 106.75.30.92 : 8080 ① mvn package ② docker build / push ③ 改 GitOps 仓库镜像 tag (不再持有集群写权限) Harbor 镜像仓库 106.75.29.47:10086 GitOps 清单仓库 (Gitee) ruoyi-gitops : 唯一事实来源 镜像 tag 只在这里被改动 Argo CD(部署在集群内 · argocd 命名空间) application-controller 持续比对 Git ↔ 集群 repo-server 渲染清单 · server 提供 UI/API/CLI 自动同步 / 自愈 / 漂移检测 / 一键回滚 按 manifest 拉取镜像,不做构建 Kubernetes v1.28.14 · ruoyi 命名空间(7 个服务) push commit(仅改 tag) watch sync 拉取镜像
图 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 目前已经不在补丁窗口内,意味着不会有安全补丁。两条路线:

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
不可用的地址(别浪费时间)

4.3 第一步:准备安装清单

# 在 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,便于管理和设置保留策略:

# 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)都要配:

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 第三步:改造清单并安装

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

#!/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 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 单用户,可以把它去掉以省资源:

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 侧终结或直接明文内网访问):

# 让 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"}}'
# 存为 /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
访问入口的两个注意点

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

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 导出并清洗清单

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 版本,不引入额外插件):

# 存为 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 独立同步、独立回滚):

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 这一行

# 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 注入:

    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 密码公开。

5.4 推送仓库

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 仓库、能往哪些命名空间、能创建哪些资源类型」约束住。这是多团队环境下最重要的安全边界:

# 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
kubectl apply -f argocd/project-ruoyi.yaml
argocd proj get ruoyi

6.2 每个服务一个 Application

# 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 建议:

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

          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 并配置(可选):

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 改回上一个值。

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

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

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 之后):

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

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

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* 分支」。这个模式本身没问题,改造时可以保留,只需注意:

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 关键验收动作(可执行的验证脚本)

# 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 验收通过标准

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 日常运维要点

10风险清单与注意事项

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

# 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 常用运维命令

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