主题
关于本次勘察的边界
本文档中的“现状”部分全部来自对 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 IP | CPU / 内存(allocatable) | 当前内存占用 | 状态 |
|---|---|---|---|---|---|
jenkins | control-plane(etcd / apiserver / controller-manager / scheduler 全在这) | 100.68.117.72 | 2C / 7.4Gi | 71% | Ready(带 control-plane 污点) |
k8s-node1 | worker | 100.125.80.98 | 4C / 3.5Gi | 76% | Ready |
k8s-node2 | worker | 100.97.166.90 | 4C / 3.5Gi | 93% | Ready(内存最紧张) |
k8s-node3 | worker | 100.78.249.106 | 2C / 7.4Gi | 70% | Ready(内存余量最大,≈2.2Gi) |
| 组件 | 现状 |
|---|---|
| Kubernetes | v1.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 模式 |
| Ingress | ingress-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-deploy | 8080 | 31080 | 20260827145338 |
ruoyi-auth-deploy | 9200 | 31081 | 20260827145033 |
ruoyi-modules-system-deploy | 9201 | 31093 | 20260827145603 |
ruoyi-modules-gen-deploy | 9202 | 30091 | 20260827161313 |
ruoyi-modules-job-deploy | 9203 | 30090 | 20260827162653 |
ruoyi-modules-file-deploy | 9300 | 30092 | 20260827152043 |
ruoyi-visual-monitor-deploy | 9100 | 30095 | 20260827191643 |
另外几个关键事实:
- 镜像仓库:私有 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。 - Ingress:
ruoyi-gateway-ingress,ingressClassName: nginx,无域名(host为*),全部/转发到 gateway。 - 前端不在集群里:
ruoyi-ui是宿主机 Nginx 直接托管/opt/ruoyi/dist(/etc/nginx/conf.d/ruoyi.conf,server_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 Pipeline,Jenkinsfile 在各仓库根目录 |
| 代码来源 | 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-token、deploy-ssh-key、nexus-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 个必须改造的点
- CI 承担了 CD:Jenkins 持有集群 admin kubeconfig 并直接
kubectl apply,构建与发布耦合,失败无法自动回滚。 sed -i污染工作区:Jenkinsfile 在构建时直接改写仓库里的k8s/deployment.yaml,导致工作区 git 树长期处于 dirty 状态,清单文件在本地被“改坏”,下次全量构建会带着上次的 tag 继续滚。- 镜像 tag 不可追溯:时间戳无法定位到代码提交,出问题只能靠时间猜。
docker push还同时推latest这种可变标签。 - 集群实况与仓库清单已经漂移:线上
ruoyi-gateway-deploy实际挂载了ruoyi-gateway-config(有volumeMounts),但仓库里的k8s/deployment.yaml这段是被注释掉的。也就是说漏跑一次 CI 或重装一次集群,网关的配置文件挂载就会丢。这正是 GitOps 要解决的问题。
02 目标架构与职责边界
改造后的职责划分用一句话概括:**Jenkins 只负责“把代码变成镜像并把镜像题写进 Git”,Argo CD 负责“把 Git 里的期望状态搬到集群”。**Jenkins 不再需要集群的写权限。
图 1 目标链路:代码 → Jenkins 构建镜像 → 改写 GitOps 仓库中的镜像版本 → Argo CD 同步到集群
2.1 改造前后对比
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 部署指令来源 | Jenkins 执行 kubectl apply | Argo CD 读取 Git,自动同步 |
| 集群写权限 | Jenkins 持有 admin kubeconfig | 只有 Argo CD 的 ServiceAccount 持有 |
| 期望状态存放 | 散落在各业务仓库的 k8s/ 目录 | 集中在一个 GitOps 仓库,可审计、可 Review |
| 镜像 tag | 时间戳 + latest | Git 短 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.3 | v1.36~v1.32 | 否 | 最新特性最全,但要求集群较新 |
| 2.14 | v1.31, v1.30, v1.29, v1.28 | 是 | 本次选型,2.x 线最后一个覆盖 1.28 的分支 |
| 2.13 | v1.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-controller | StatefulSet | 200m / 384Mi | 1000m / 768Mi |
| argocd-repo-server | Deployment | 100m / 192Mi | 500m / 512Mi |
| argocd-server | Deployment | 50m / 128Mi | 300m / 256Mi |
| argocd-redis | Deployment | 30m / 64Mi | 200m / 128Mi |
| argocd-applicationset-controller | Deployment | 30m / 64Mi | 200m / 128Mi |
| argocd-notifications-controller | Deployment | 20m / 64Mi | 100m / 128Mi |
| argocd-dex-server | Deployment | —(可选,见 §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.21 的 install.yaml(1425427 字节)一共 59 个资源段,需要的外部镜像只有 3 个:
| 清单中的镜像 | 用途 | 国内镜像源(已实测可用) |
|---|---|---|
quay.io/argoproj/argocd:v2.14.21 | server / controller / repo-server / applicationset / notifications 共 8 处引用 | quay.m.daocloud.io/argoproj/argocd:v2.14.21 |
ghcr.io/dexidp/dex:v2.41.1 | SSO(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.21 | manifest 200 · amd64/arm64 | DaoCloud quay 代理,首选 |
| Argo CD 主镜像(备) | quay.nju.edu.cn/argoproj/argocd:v2.14.21 | manifest 200 | 南京大学镜像站 |
| Dex | ghcr.m.daocloud.io/dexidp/dex:v2.41.1 | manifest 200 | ghcr 代理 |
| Redis | m.daocloud.io/docker.io/library/redis:7.2.11-alpine | manifest 200 | Docker Hub 代理 |
| 安装清单 install.yaml | https://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml | 200 · 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.yaml | 200 · 1425427 B | jsDelivr |
| argocd CLI 二进制 | https://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64 | 200 · 14868892 B | DaoCloud 文件代理,走这个 |
| Helm Chart 仓库 | https://argoproj.github.io/argo-helm | index.yaml 200 | 本环境可直连;备选 https://ben-wangz.github.io/helm-chart-mirror/charts |
不可用的地址(别浪费时间)
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/quay.io/argoproj/argocd→ 404(该路径下无此镜像)registry.cn-hangzhou.aliyuncs.com/argoproj/argocd→ 401(该命名空间下无此镜像)docker.m.daocloud.io/argoproj/argocd→ 403(该域只代理 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 --client4.4 第二步:把 3 个镜像搬进自己的 Harbor
虽然 quay.io 在本环境能直连,但用它拉取有两个问题:速度不稳定、集群对公网有外部依赖。既然已经有 Harbor,把它当作集群的唯一镜像入口是更干净的架构——顺带也让 Argo CD 自身的升级走同一套流程。
统一镜像映射规则
全部收敛到一个 Harbor 项目 infra,便于管理和设置保留策略:
quay.io/argoproj/argocd:v2.14.21→106.75.29.47:10086/infra/argocd:v2.14.21ghcr.io/dexidp/dex:v2.41.1→106.75.29.47:10086/infra/dex:v2.41.1redis:7.2.11-alpine→106.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.io、registry.k8s.io、106.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":""}}'反过来,你这台机器上已经跑着 Casdoor(docker 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,并且从集群外访问时流量要走 NodePort30080。如果只是本机学习,最简单的做法是用kubectl port-forward或直接改/etc/hosts指到100.78.249.106(Tailscale 里就能通)。 argocdCLI 走的是 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-secret4.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 ruoyi | 7 个 Pod 仍然 Running,重启次数不增加 |
| 节点无压力 | kubectl top nodes | k8s-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 # 应为 05.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 Secrets 或 External 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 main06 阶段三: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: truebash
kubectl apply -f argocd/project-ruoyi.yaml
argocd proj get ruoyi6.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 只有预期的内容之后,再打开 prune 和 selfHeal。具体节奏见 §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-monitor10:gateway20: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: 206.4 让 Argo CD 感知到 Git 变更
Argo CD 默认每 3 分钟轮询一次 Git。要“提交即同步”,两种方式:
| 方式 | 做法 | 适用 |
|---|---|---|
| Jenkins 主动触发(本轮采用) | CI 末尾调用 argocd app sync 并 --wait,把部署结果带回构建日志 | 希望「构建日志里能看到部署成功/失败」,闭环清晰 |
| Gitee Webhook → Argo CD | Argo 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-a3e752d7.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 个具体变化
- 删除了
kubectl apply—— Jenkins 不再需要集群写权限。 - 删除了
sed -i ... k8s/deployment.yaml—— 不再污染构建工作区,清单文件保持干净。 - 不再推
latest—— 只推不可变 SHA tag,避免「谁在跑什么版本」说不清。 - 新增「等 Argo CD 同步完成」阶段 —— 构建结果与部署结果统一在一处呈现,失败会被 Jenkins 标红。
7.3 需要新增的两样东西
| 类型 | ID | 内容 | 用途 |
|---|---|---|---|
| Username/Password 凭据 | gitee-gitops-credentials | Gitee 账号 + 私人令牌(或仓库访问令牌) | CI 推送 GitOps 仓库 |
| Secret text | argocd-api-token | Argo CD 生成的 API Token | CI 触发并等待同步 |
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-dependencies、ruoyi-common这类只被依赖的父 POM 仓库,可以继续用现有方式(只需 install 到 Nexus / 本地仓库),不产生镜像也就不参与 Argo CD。- 第一个先改的 Job,建议选
ruoyi-modules-gen或ruoyi-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: true | Git 里删一个无用资源,集群里同步消失 | 关闭 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 状态为
Synced且Healthy。 - 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 开着时会被纠回,需先暂停自动同步) |
紧急情况下的正确操作顺序
- 先关自动同步:
argocd app set <app> --sync-policy none(或 UI 上 Disable Auto-Sync)。否则你手工操作会被 selfHeal 反复覆盖。 - 再做应急处置。
- 事后把最终状态补回 Git,再打开自动同步,否则下次同步会把你的应急操作推翻。
9.2 常见故障速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
Application 一直 OutOfSync,diff 里有 creationTimestamp / resourceVersion | 导出清单时没清洗运行时字段 | 用 §5.2 的脚本重新清洗 |
diff 只有 status 或 metadata.annotations 差异 | 被其他控制器(如 kubectl edit)写入 | 用 ignoreDifferences 忽略,或接受 Argo CD 纠偏 |
| repo-server 报克隆失败 | Gitee 网络波动 / 凭据过期 | kubectl -n argocd logs deploy/argocd-repo-server;公开仓库不需要凭据 |
Pod 卡在 ImagePullBackOff | Harbor 不可达 / harbor-secret 过期 | kubectl -n ruoyi describe pod ...;确认 106.75.29.47:10086 连通 |
| Argo CD 自身被 OOMKilled | repo-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-metricsService 已存在)。可以接到你已有的 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 风险清单与注意事项
| # | 风险 / 事项 | 影响 | 应对 |
|---|---|---|---|
| 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 的三种后续路径
- 保持现状(本轮):宿主机 Nginx 托管,不进集群,不纳入 GitOps。
- 容器化进集群:用现有
Dockerfile构建ruoyi-ui镜像,Deployment + ConfigMap(ruoyi-ui-nginx-config仓库里已有)+ Ingress,然后交给 Argo CD。 - 保留静态目录但用 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 -3011.3 本次实测过的镜像 / 下载地址汇总
| 用途 | 地址 | 结果 |
|---|---|---|
| Argo CD 镜像 | quay.m.daocloud.io/argoproj/argocd:v2.14.21 | 200 |
| Argo CD 镜像(备) | quay.nju.edu.cn/argoproj/argocd:v2.14.21 | 200 |
| Dex 镜像 | ghcr.m.daocloud.io/dexidp/dex:v2.41.1 | 200 |
| Redis 镜像 | m.daocloud.io/docker.io/library/redis:7.2.11-alpine | 200 |
| install.yaml | https://gh-proxy.com/https://raw.githubusercontent.com/argoproj/argo-cd/v2.14.21/manifests/install.yaml | 200 / 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.yaml | 200 / 1425427 B |
| argocd CLI | https://files.m.daocloud.io/github.com/argoproj/argo-cd/releases/download/v2.14.21/argocd-linux-amd64 | 200 / 14868892 B |
| Helm 仓库 | https://argoproj.github.io/argo-helm | 200 |
| Helm 仓库(备) | https://ben-wangz.github.io/helm-chart-mirror/charts | 200 |
文档说明:本方案基于 2026-09-23 对 106.75.30.92 的只读勘察结果编写,所有「现状」描述均有现场依据;所有镜像与下载地址均已在该服务器上实测。文中命令尚未执行,请按 §8 的顺序在确认后逐步落地。
建议把本文档与 GitOps 仓库一起纳入版本管理,作为架构决策记录(ADR)留存。