CI/CD(GitOps)学习与部署手册

argocd+jenkins+gitlab+harbor+多环境k8s集群

这份文档给谁看 :想搞懂这套 CI/CD 是怎么跑的、想照着部署一套、或想接手维护的人。 文档定位 :先讲"为什么"和"怎么跑通",再讲"每个文件干什么" ,最后才是部署与排障。 覆盖仓库 :ops/devops/zcyw-jenkins-shared-library(共享库)| slp/gitops(GitOps 配置)| slp/vue(业务示例) 文档基准版本:2026-09-21


怎么用这份文档

你的情况 建议阅读顺序
完全不懂这套东西,想学 1.5(五方如何配合,全文总纲) → 第一篇其余(概念)→ 第三篇 9 章(谁在哪配)→ 第二篇(代码)
要部署一套新环境 第三篇 14 章(检查清单)→ 第四篇 15 章(SOP)
要接入一个新项目 第四篇 16 章(接入指南)→ 第二篇第 8 章(map 参数)
构建/部署报错了 第四篇 17 章(排障手册)
想改某个行为 附录 E(变更操作手册:改 X 要去哪改)

目录

第一篇 · 概念与全景(建议先读)

  • [1. 这套系统在解决什么问题](#1. 这套系统在解决什么问题)

  • [2. 五个核心概念](#2. 五个核心概念)

  • [3. 一次构建的完整旅程](#3. 一次构建的完整旅程)

  • [4. 命名规则总表](#4. 命名规则总表)

第二篇 · 代码逐层拆解

  • [5. 三个仓库的职责](#5. 三个仓库的职责)

  • [6. 共享库详解](#6. 共享库详解)

  • [7. GitOps 仓库详解](#7. GitOps 仓库详解)

  • [8. 业务仓库详解](#8. 业务仓库详解)

第三篇 · 配置全景(谁在哪配)

  • [9. 配置归属总表:手工 vs 代码](#9. 配置归属总表:手工 vs 代码)

  • [10. Jenkins](#10. Jenkins)

  • [11. ArgoCD](#11. ArgoCD)

  • [12. Harbor 镜像仓库](#12. Harbor 镜像仓库)

  • [13. 目标集群 / DNS / 基础镜像](#13. 目标集群 / DNS / 基础镜像)

  • [14. 部署前置准备检查清单](#14. 部署前置准备检查清单)

第四篇 · 运维与排障

  • [15. 首次部署操作步骤(SOP)](#15. 首次部署操作步骤(SOP))

  • [16. 接入指南:新项目 / 新环境 / 新集群](#16. 接入指南:新项目 / 新环境 / 新集群)

  • [17. 排障手册](#17. 排障手册)

  • [18. 已知隐患与改进建议](#18. 已知隐患与改进建议)

附录

  • [附录 A · 术语表](#附录 A · 术语表)

  • [附录 B · 学习路径与动手实验](#附录 B · 学习路径与动手实验)

  • [附录 C · Jenkins 凭证与账号速查](#附录 C · Jenkins 凭证与账号速查)

  • [附录 D · 部署前一页纸检查清单](#附录 D · 部署前一页纸检查清单)

  • [附录 E · 变更操作手册](#附录 E · 变更操作手册)

  • [附录 F · 后续需要完善的功能与存在的不足](#附录 F · 后续需要完善的功能与存在的不足)


本项目实例(全文示例的真实值)

全文的讲解、命令、日志片段都以 slp-vue-demo 这个项目为例。 下表是示例中出现的全部真实值 ------ 阅读时对照它,就不会把占位符和实际值搞混。

项目与仓库

项 真实值
Jenkins Job 名 / 项目名 / K8s 命名空间 slp-vue-demo
业务代码仓库 http://gitlab.yunwdh.cn/slp/vue
GitOps 配置仓库 http://gitlab.yunwdh.cn/slp/gitops
共享库仓库 http://gitlab.yunwdh.cn/ops/devops/zcyw-jenkins-shared-library

环境 / 集群 / 域名 / 镜像

环境 集群名 API Server 访问域名 镜像仓库地址
dev bj-dev-01 https://192.168.10.107:6443 slp-vue-demo-dev.yunwdh.cn bj-harbor.yunwdh.cn/dev/slp-vue-demo/slp-vue-demo
test cq-test-01 https://192.168.10.110:6443 slp-vue-demo-test.yunwdh.cn cq-harbor.yunwdh.cn/test/slp-vue-demo/slp-vue-demo
prod sz-prod-01 https://192.168.10.112:6443 slp-vue-demo-prod.yunwdh.cn sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo

cq-test-01 集群的 Ingress 对外入口实测为 192.168.10.111(80 端口)。

平台地址

项 真实值
GitLab http://gitlab.yunwdh.cn
Jenkins http://jenkins.yunwdh.cn(本项目 webhook:http://jenkins.yunwdh.cn/project/slp-vue-demo)
ArgoCD https://argocd.yunwdh.cn(命名空间固定为 argo-cd)
Harbor http://{bj,cq,sz}-harbor.yunwdh.cn(项目:dev / test / prod)
Jenkins Agent 命名空间 jenkins

一次真实构建(prod / sz-prod-01)的产物

项 真实值
构建号 #47
业务代码 commit 46faaaa4(slp/vue 的一次提交)
镜像 tag 202609200731-46faaaa4-v47(= 日期 + commit8 + 构建号)
GitOps 仓库提交信息 jenkins ci auto commit
父 Application slp-vue-demo
ApplicationSet(prod) slp-vue-demo-slp-vue-demo-prod-sz-prod-01
子 Application(prod) slp-vue-demo-prod-sz-prod-01-slp-vue-demo
子 Application(公共资源) slp-vue-demo-prod-sz-prod-01-common-k8s-resource
集群里的资源 Namespace slp-vue-demo → Deployment / Service / Ingress(同名)

第一篇 · 概念与全景

1. 这套系统在解决什么问题

1.1 没有 CI/CD 之前是怎么干的

一个前端项目上线,传统做法是:

复制代码
本地 npm run build  →  手工打包 dist/  →  scp 到服务器  →  解压覆盖  →  重启 nginx
                                                                ↑
                                                        出问题只能靠回忆改过什么

痛点:

痛点 后果
构建靠人 不同人本地环境不一致,"我这儿是好的"
上线靠手 漏文件、漏重启、改错目录
无记录 出事不知道改了啥、怎么回滚
多环境 dev/test/prod 各维护一套命令,经常搞混
需要集群权限 每个开发都要拿 K8s 凭证,权限失控

1.2 CI/CD 分别是什么

缩写 全称 干什么
CI Continuous Integration 持续集成 代码提交后自动构建、测试、产出制品(这里是 Docker 镜像)
CD Continuous Delivery/Deployment 持续交付/部署 把制品自动部署到目标环境

这套系统的实现:

复制代码
CI = Jenkins 的 PreRunCheck / GitClone / ImageBuild 三个阶段
CD = Jenkins 的 SedYaml / DeployK8s + ArgoCD 的同步

1.3 关键设计:为什么用 GitOps,而不是 Jenkins 直接 kubectl

传统做法(反例):

复制代码
Jenkins ── kubectl apply ──► 业务 K8s 集群
   ↑
   └─ 需要把每个集群的 kubeconfig 都配在 Jenkins 上(安全风险 + 难以审计)

本系统的做法(GitOps):

复制代码
Jenkins ──只做两件事──► ① 推镜像到 Harbor
                       ② 改 GitOps 仓库的 YAML 并 git push
                                    │
                                    ▼
                            ArgoCD 监听仓库 ──► apply 到目标集群
                            (集群凭证只配在 ArgoCD 侧)

换来五个好处:

好处 说明
Jenkins 不需要集群凭证 攻击面变小;新增集群不用改 Jenkins
所有变更都是 Git 提交 谁、什么时候、改了什么,一目了然;回滚 = git revert
配置与代码分离 业务仓库只管业务代码;部署配置在独立的 GitOps 仓库
状态可观测 ArgoCD 持续对比"期望状态 vs 实际状态",手动改动会被自动纠偏(selfHeal)
多环境天然隔离 dev/test/prod 就是三份 overlay 目录

1.4 一张图看懂全部角色

复制代码
┌──────────────┐
│   开发者      │  git push
└──────┬───────┘
       ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ GitLab(代码的源头,共 3 个仓库)                                          │
│   slp/vue                                  业务代码                       │
│   ops/devops/zcyw-jenkins-shared-library   流水线逻辑(共享库)            │
│   slp/gitops                               部署配置(GitOps 仓库)         │
└──────┬────────────────────────────────────────────────────────────────────┘
       │ ① 读取 Jenkinsfile + 加载共享库
       ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Jenkins(流水线调度者:构建 + 改配置,不碰业务集群)                        │
│   在 K8s 上动态起 Pod 跑构建(ci-build / cd-deploy / jnlp 三个容器)      │
│   6 个阶段:PreRunCheck→GitClone→ImageBuild→SedYaml→DeployK8s             │
└──────┬──────────────────────────────────┬─────────────────────────────────┘
       │ ② docker push                    │ ③ git push(改 YAML)
       ▼                                  ▼
┌──────────────────────┐        ┌──────────────────────────────────────────┐
│ Harbor(镜像仓库)    │        │ GitLab: slp/gitops                        │
│ 项目 = 环境名         │        │   template/           脚手架模板          │
│ dev / test / prod     │        │   argocd/<项目>/      ApplicationSet      │
└──────────┬───────────┘        │   <项目>/<应用>/base  Deployment+Service  │
           │                    │   <项目>/<应用>/overlays/<环境>/           │
           │                    └────────────────┬─────────────────────────┘
           │                                     │ ④ 监听(每 3 分钟)
           │                                     ▼
           │                    ┌──────────────────────────────────────────┐
           │                    │ ArgoCD(部署执行者)                      │
           │                    │   父 Application → ApplicationSet →       │
           │                    │   子 Application → 渲染 Kustomize 清单    │
           │                    └────────────────┬─────────────────────────┘
           │ ⑤ kubelet 拉取镜像                   │ ⑥ apply
           ▼                                     ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ 目标 K8s 集群(bj-dev-01 / cq-test-01 / sz-prod-01)                       │
│   命名空间 = 项目名 → Deployment + Service + Ingress                       │
│   对外域名 = <应用名>-<环境>.yunwdh.cn                                     │
└───────────────────────────────────────────────────────────────────────────┘

一句话先记住 :Jenkins 负责"打镜像 + 改 GitOps 仓库",ArgoCD 负责"读 GitOps 仓库 + apply 到集群",GitLab 只负责"存东西"。 五个角色的完整职责边界与交接方式,见下一节 1.5。

1.5 五方如何配合:一次变更的完整协作链路

这一节是全文的总纲 。 CI/CD 是目标,Jenkins / ArgoCD / GitLab / GitOps 是达成目标的五个角色 ------ 理解它们怎么配合,整套系统就没有黑盒了。

1.5.1 核心洞察:五方之间只流动"两样东西"

整套系统里,跨组件的交流只有两种介质:

介质 流向 载体
Git 提交 开发者 → GitLab → Jenkins / ArgoCD 业务代码、流水线逻辑、期望状态 YAML
Docker 镜像 Jenkins → Harbor → 目标集群 构建产物

没有第三种。 任何环节出问题,都能在这两个介质上定位 ------ 这是这套架构最值得记住的一点。

1.5.2 一次变更的七棒交接

以 slp-vue-demo 部署到 test / cq-test-01 为例:

棒次 从 → 到 交接物 由谁触发 / 靠什么
1 开发者 → GitLab 业务仓库 代码提交 git push
2 业务仓库 → Jenkins 代码 + Jenkinsfile GitLab webhook(或手动触发)
3 Jenkins → Harbor Docker 镜像 docker push
4 Jenkins → GitLab GitOps 仓库 期望状态 YAML(一次 commit) git push(SedYaml 阶段)
5 GitOps 仓库 → ArgoCD 期望状态 ArgoCD 每 3 分钟拉取(或手工 app sync)
6 ArgoCD → 目标集群 K8s 资源 ArgoCD 执行 apply
7 目标集群 → Harbor 拉取镜像 kubelet 按 imagePullSecrets 拉取

第 3 棒和第 4 棒是"并行"的 :Jenkins 同时把镜像推到 Harbor、把期望状态写进 GitOps 仓库。 第 6、7 棒完全没有 Jenkins 参与 ------ 这是 GitOps 的关键:部署阶段与构建阶段彻底解耦。

1.5.3 五个角色的职责边界

角色 在 CI/CD 中扮演 只做 绝不做 为什么这样切
GitLab(三个仓库) 唯一事实来源 存业务代码、流水线逻辑、期望状态;提供变更记录 不执行任何动作 一切变更可审计、可回滚
Jenkins(CI) 构建执行者 拉代码、跑编译、打镜像、推镜像、改 GitOps 仓库并 push 不连任何业务 K8s 集群 业务集群凭证不需要放到 Jenkins
Harbor 制品仓库 存镜像(按环境分项目)、供各集群拉取 不参与调度与部署 镜像不可变,tag 带 commit 可溯源
ArgoCD(CD) 部署执行者 读 GitOps 仓库、渲染 Kustomize、apply 到集群、持续纠偏 不构建镜像 集群凭证只配在这里
GitOps 仓库 CI 与 CD 的契约 / 解耦层 用 YAML 描述"集群应该长什么样" 不放业务代码 Jenkins 与 ArgoCD 互不认识,只通过它交接

1.5.4 为什么中间必须有 GitOps 这一层

维度 没有 GitOps(Jenkins 直接 kubectl) 有 GitOps(本系统)
凭证 每个集群的 kubeconfig 都要配在 Jenkins 只需配在 ArgoCD
审计 只知道"Jenkins 改过",不知道改成了什么 每次部署 = 一次 Git 提交,diff 可见
回滚 只能重新跑一次旧构建 git revert 一次提交即可
状态漂移 手工改动无人纠正,越漂越远 selfHeal 自动纠偏回 Git 状态
多环境 每个环境一套脚本/参数 每个环境一个 overlay 目录
新增集群 改 Jenkins 配置 + 加凭证 给集群打个 cluster-name 标签即可

1.5.5 七个交接点"断了会怎样"

排障时先判断断在哪一棒,再去对应章节:

交接点 断了的症状 去哪查
① 代码 → Jenkins 构建根本不触发 第 10.1 节 Triggers / GitLab webhook
② Jenkins → Harbor docker login failed / denied 第 12.2、12.4 节
③ Jenkins → GitOps 仓库 git push ... 403 / rejected 第 14.3 节(权限)、并发构建开关
④ GitOps 仓库 → ArgoCD 一直 OutOfSync、子 App 不生成 第 11.2 节(集群 label)、第 17.5 节
⑤ ArgoCD → 目标集群 cluster ... is not accessible、application is deleting 第 11.2、17.3 节
⑥ 目标集群 → Harbor Pod ImagePullBackOff 第 12.6 节(harbor-secret)
⑦ 集群 → 用户 域名 404 / 503 / 连不上 第 17.4 节(Ingress / endpoints / DNS)

1.5.6 一句话概括协作模式

Jenkins 负责"把代码变成镜像 + 把期望状态写进 Git"; ArgoCD 负责"把 Git 里的期望状态变成集群里的现实"。 两者不直接对话 ------ GitOps 仓库就是它们之间的契约。


2. 五个核心概念

这一章用最小例子解释文档后面反复出现的五个词。懂了这五个,整套系统就没有黑盒了。

2.1 Jenkins Pipeline 与共享库

什么是 Pipeline

Jenkins 的流水线就是一段 Groovy 脚本,描述"按顺序做哪些事":

复制代码
pipeline {
    agent any
    stages {
        stage('构建') { steps { sh 'npm run build' } }
        stage('部署') { steps { sh 'kubectl apply -f k8s.yaml' } }
    }
}

为什么需要"共享库"

假设有 20 个项目,每个项目的流水线都写 200 行 ------ 只要流程要改一处(比如换个镜像仓库),就得改 20 个地方。

Jenkins 共享库(Shared Library) 就是把这些公共逻辑抽出来单独放一个 Git 仓库:

复制代码
共享库仓库(只维护一份)
 ├── vars/     ← 目录下的文件名 = 全局可调用的方法名
 └── src/      ← 普通 Groovy 类,按包名组织

业务方只需要:

复制代码
library(identifier: "zcyw-jenkins-shared-library@master", retriever: modernSCM([...]))
def map = [:]                        // 声明差异
map.put('gitUrl', 'http://.../slp/vue')
map.put('buildCmd', 'npm run build')
Pipeline_GitOps_Run(map)             // 调用共享库入口

关键机制:

  • vars/Pipeline_GitOps_Run.groovy 里的 def call(Map map) 是隐式入口,所以能直接写 Pipeline_GitOps_Run(map)

  • library(...) 里写了 @master,每次构建都会去拉共享库最新版本 → 改共享库,所有项目立即生效

本系统的共享库就是 zcyw-jenkins-shared-library,它承载了全部流水线逻辑(约 9 个 Groovy 文件)。

2.2 Kustomize(base / overlay)

问题:dev / test / prod 的 Deployment 99% 相同,只有镜像地址、副本数等不同。复制三份 → 改一处要改三处。

Kustomize 的解法:

复制代码
base/                      ← 公共部分(只写一份)
  deployment.yaml
  service.yaml
  kustomization.yaml       ← 列出 base 包含哪些资源
overlays/
  dev/kustomization.yaml   ← 只写"和 base 的差异"
  test/kustomization.yaml
  prod/kustomization.yaml

一个 overlay 的典型内容:

复制代码
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: slp-vue-demo        # 给所有资源统一加命名空间
resources:
  - ../../base                 # 引用 base
  - ingress-patch.yaml         # 再加上本环境特有的资源
images:                        # 镜像替换器:把 base 里的锚点名替换成真实地址
  - name: app-image
    newName: sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo
    newTag: 202609200731-46faaaa4-v47

base 里对应写一个锚点名:

复制代码
containers:
  - name: slp-vue-demo
    image: app-image:latest        # ← 会被上面的 images 替换器改写

渲染命令:

复制代码
kubectl kustomize overlays/prod       # 输出最终要 apply 的 YAML

⚠️ kustomization.yaml 会严格校验字段 :写了不存在的字段会直接报错 (例如 imagePullSecrets 不是 它的合法字段,必须写在 Deployment 的 spec.template.spec 里)。

2.3 ArgoCD Application / ApplicationSet

概念 是什么 类比
Application 一条"部署指令":从哪个仓库的哪个路径,部署到哪个集群的哪个命名空间 一份"施工任务单"
ApplicationSet 一个"任务单生成器":按条件批量生成多个 Application 一个"模板 + 循环"
AppProject 权限边界:限定哪些仓库/集群/命名空间可以被部署 一个"项目组"

为什么需要 ApplicationSet? 假设有 3 个集群,要部署同一个应用:

  • 不用 ApplicationSet:手写 3 个 Application,加一个集群就要加一个

  • 用 ApplicationSet:写 1 个,靠集群标签自动匹配

复制代码
spec:
  generators:
    - clusters:                    # 集群生成器
        selector:
          matchLabels:
            cluster-name: sz-prod-01   # 只挑打了这个标签的集群
  template:
    metadata:
      name: slp-vue-demo-prod-sz-prod-01-slp-vue-demo   # 生成的 Application 名
    spec:
      destination:
        server: '{{server}}'       # 自动注入被选中集群的地址

所以给 ArgoCD 添加集群时必须打 cluster-name 标签,否则 ApplicationSet 匹配不到,子 Application 永远不生成。

2.4 Harbor 与环境化镜像

Harbor 是私有 Docker 镜像仓库。本系统给镜像定的规则:

复制代码
{registryServer}/{环境}/{项目名}/{应用名}:{日期}-{commit8}-v{构建号}
      │              │        │         │
 bj-harbor...     prod   slp-vue-demo  slp-vue-demo

为什么 Harbor 的"项目"要按环境划分 (dev / test / prod 三个项目)?

好处 说明
权限隔离 dev 镜像可以人人可推,prod 镜像只有流水线能推
保留策略不同 prod 镜像保留久、dev 镜像保留短
一眼看出用途 在 Harbor 里看到项目名就知道是哪个环境的

镜像 tag 为什么要带 commit 哈希 :202609200731-46faaaa4-v47 里的 46faaaa4 是业务仓库的 commit 前 8 位 → 任何一个镜像都能反查出对应的代码版本,回滚和排查时极其有用。

2.5 GitOps 仓库

一句话:把「集群里应该长什么样」全部用 YAML 写在 Git 里,让工具去对比和落地。

本系统的 GitOps 仓库 = slp/gitops,它的内容变化只有两个来源:

来源 谁 提交信息
流水线自动 Jenkins SedYaml 阶段 jenkins ci auto commit
人工调整 运维 fix: ... / reset: ...

所以这个仓库每次构建都会前进一个提交 ------ 这是完全正常的。


3. 一次构建的完整旅程

这一章用一次真实构建 (prod + sz-prod-01)把整条链路走一遍。建议对照 Jenkins 构建日志一起读。

3.1 怎么触发

方式 说明
手动 Jenkins → Job → Build with Parameters → 选分支 / 环境 / 集群
自动 业务仓库 push → GitLab webhook 打到 http://jenkins.yunwdh.cn/project/<Job名> → 触发构建

⚠️ 自动触发时用的是 Job 里保存的默认参数。所以"自动部署"前一定要确认默认值指向哪个环境。

3.2 十个步骤的时间线

# 阶段 动作 日志关键字
1 准备 加载共享库 @master,解析 Jenkinsfile 的 map Loading library zcyw-jenkins-shared-library@master
2 准备 properties() 生成三个构建参数 [Pipeline] properties
3 准备 在 K8s 上创建 Agent Pod(3 容器) Created Pod: kubernetes jenkins/<job>-<N>-xxxx
4 PreRunCheck 打印参数 → 校验集群 → prod 权限门 >>>用户: xxx, 管理员权限: true<<<
5 GitClone 拉业务代码,取 commit 前 8 位 commitHash: 46faaaa4,代码获取成功
6 ImageBuild 识别技术栈 → 生成 Dockerfile → 构建 → 推送 Login Succeeded → Successfully tagged ...
7 SedYaml 克隆 GitOps 仓库 → 改 YAML → 提交推送 >>>部署新服务<<< / >>>部署新环境<<< / >>>已部署过服务或环境,执行服务更新镜像<<<
8 DeployK8s 登录 ArgoCD → 建 Project/App → 同步 'admin:login' logged in successfully → Phase: Succeeded
9 收尾 post 打印结果 >>>构建成功!<<<
10 ArgoCD 自动同步 拉 GitOps 仓库 → 渲染 Kustomize → apply (不在 Jenkins 日志里,看 ArgoCD UI)

3.3 逐阶段详解

①-③ 准备阶段

Agent Pod 由 resources/Pod-CICD-Base-Build-Env.yaml 定义,含三个容器:

容器 用在哪 用途
jnlp GitClone Jenkins 入站代理(名称是插件写死的,不能改)
ci-build PreRunCheck / ImageBuild 默认容器,跑 docker 命令打镜像
cd-deploy SedYaml / DeployK8s 改 GitOps 仓库、调 argocd CLI

workspace 是三个容器共享的 emptyDir ------ 所以 jnlp 里拉下来的代码,ci-build 也能看到。

④ PreRunCheck ------ 三道检查

复制代码
preRunCheck.printBuildParameters()   // 打印:分支 / 环境 / 集群
preRunCheck.paramSelectCheck()       // 校验:必须且只能选一个集群
preRunCheck.prodPermissionCheck()    // 管控:prod 只允许 Jenkins 管理员

prod 权限门判定链:

  1. 取 BUILD_USER_ID(Build User Vars 插件提供)

  2. 为空 → 非 prod 放行;prod 报错中止

  3. User.getById(id) 查不到用户 → 同上

  4. 判 Jenkins.ADMINISTER → 管理员放行;非管理员只有非 prod 放行

这就是为什么必须启用 Jenkins 安全认证 :否则 BUILD_USER_ID = anonymous,prod 永远发不了。

⑤ GitClone

复制代码
checkout scmGit(branches: [[name: "${params.Git_Branch_Name_Parameter}"]], ...)
def fullCommitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
env.commitHash = fullCommitHash.take(8)      // 取前 8 位写进 env

用 env.commitHash 而不是裸赋值 ------ 裸赋值会被 Jenkins 警告"可能内存泄漏",且 env 变量跨阶段可见。

⑥ ImageBuild ------ 技术栈自动识别

复制代码
def registryServer = 'bj-harbor.yunwdh.cn'
if      (customDockerfile 有值)                        buildByWriteDockerfile()
else if (dockerFile 有值)                              buildByDockerfile()
else if (fileExists('./pom.xml'))                      javaBuild()
else if (fileExists('./package.json') || './app.vue')  vueBuild()
else if (fileExists('./go.mod'))                       goBuild()    // ⚠️ 未实现
else if (fileExists('./gcc'))                          gccBuild()   // ⚠️ 未实现
else if (fileExists('./php'))                          phpBuild()   // ⚠️ 未实现
else                                                   netBuild()   // ⚠️ 未实现

以 vueBuild 为例,会动态生成两阶段 Dockerfile:

复制代码
# 阶段一:构建
FROM registry.cn-chengdu.aliyuncs.com/qiuyl01/node:22.23.1-alpine AS builder
WORKDIR /build-app
COPY . .
RUN mkdir -p dist && cp index.html dist/index.html      # ← 这里就是 map.buildCmd

# 阶段二:运行
FROM registry.cn-chengdu.aliyuncs.com/qiuyl01/nginx:alpine
RUN echo "Asia/Shanghai" > /etc/timezone
COPY --from=builder /build-app/./dist /data/app           # ← 这里是 map.compOutputPath
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

产物 :bj-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo:202609200731-46faaaa4-v47

⑦ SedYaml ------ 三种场景(本系统的灵魂)

先清空工作目录,再浅克隆 GitOps 仓库:

复制代码
sh '''rm -rf ./* .git'''
checkout scmGit(branches: [[name: "*/master"]], extensions: [[$class:'CloneOption', depth:1, shallow:true]], ...)
def ingressHost = "${JOB_BASE_NAME}-${params.Release_Env_Type_Parameter}.yunwdh.cn"
def imageTag    = "${date}-${commitHash}-v${BUILD_ID}"

然后按目录是否存在,走三种不同分支:

场景 判定条件 做什么 首次部署会打印
部署新服务 ./<项目>/<应用> 不存在 从 template/ 拷全套 (base + overlays + applicationset + config.json),sed 替换所有占位符 >>>部署新服务<<<
部署新环境 服务在,overlays/<环境> 不存在 只补 overlays、config.json 和当前「环境+集群」的 ApplicationSet >>>部署新环境<<<
服务更新 都存在 只改 newTag >>>已部署过服务或环境,执行服务更新镜像<<<
复制代码
} else {
    // 服务更新分支:只改镜像 tag,其他什么都不动
    sh """ sed -i 's/newTag:.*/newTag: ${imageTag}/' ${projectName}/${JOB_BASE_NAME}/overlays/${env}/kustomization.yaml """
}

⚠️ 关键推论 :改了 template/ 之后,已存在的服务和环境不会自动更新 ------ 因为模板只在「部署新服务」分支被复制。 要让已有应用生效,必须直接改 gitops 里已生成的文件(或清空项目目录让它重建)。

最后提交推送:

复制代码
git add -A
git config user.name "${BUILD_USER_ID}"
git commit -m "jenkins ci auto commit" || echo ''
git pull --depth=1 http://${GIT_USERNAME}:${GIT_PASSWORD}@gitlab.yunwdh.cn/slp/gitops.git master
git push      http://${GIT_USERNAME}:${GIT_PASSWORD}@gitlab.yunwdh.cn/slp/gitops.git master

⑧ DeployK8s ------ ArgoCD 三层模型

复制代码
// ① Project 不存在则创建
if (projCount == '0') sh "argocd proj create slp-vue-demo -d '*,*' -s '*' --allow-cluster-resource '*/*'"

// ② 父 Application 不存在则创建(path 指向存放 ApplicationSet 的目录)
if (fatherApplicationCount == '0') sh """
    argocd app create --project default --name slp-vue-demo \
        --repo http://gitlab.yunwdh.cn/slp/gitops.git \
        --path argocd/slp-vue-demo \
        --dest-server https://kubernetes.default.svc --dest-namespace argo-cd \
        --revision master --sync-policy automated
"""

// ③ 同步父 App(让 ApplicationSet 生效)→ 再同步子 App(下面以 prod / sz-prod-01 为例)
"argocd app sync slp-vue-demo > /dev/null"
'sleep 5s'
"argocd app sync slp-vue-demo-prod-sz-prod-01-slp-vue-demo --prune --timeout 180"
"argocd app sync slp-vue-demo-prod-sz-prod-01-common-k8s-resource --prune --timeout 180"

三层对象的关系 (各级对象的字段取值详见 7.4):

复制代码
父 Application(slp-vue-demo)
   ├─► ApplicationSet(slp-vue-demo-slp-vue-demo-prod-sz-prod-01)         ← 按集群标签筛选
   │      └─► 子 Application(slp-vue-demo-prod-sz-prod-01-slp-vue-demo)         → 业务资源
   └─► ApplicationSet(slp-vue-demo-common-k8s-resource-prod-sz-prod-01)
          └─► 子 Application(slp-vue-demo-prod-sz-prod-01-common-k8s-resource) → 公共资源

⑨-⑩ 收尾与 ArgoCD 自动同步

子 Application 的 syncPolicy 是 automated: {prune: true, selfHeal: true},targetRevision: HEAD:

  • 自动同步:ArgoCD 每 3 分钟拉一次仓库,发现差异就 apply

  • 自动纠偏:有人手工改集群里的资源,会被改回去

  • prune:只删「之前被 ArgoCD 管理、现在清单里没有了」的资源


4. 命名规则总表

设 JOB_NAME = JOB_BASE_NAME = projectName = appNamespace = slp-vue-demo,环境 prod,集群 sz-prod-01:

对象 规则 示例
K8s 命名空间 appNamespace slp-vue-demo
项目名(GitOps 目录名) 同 appNamespace slp-vue-demo
Harbor 镜像地址 {registryServer}/{环境}/{项目名}/{JOB_BASE_NAME} sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo
镜像 tag {日期}-{commit8}-v{BUILD_ID} 202609200731-46faaaa4-v47
AppProject {项目名} slp-vue-demo
父 Application {项目名} slp-vue-demo
父 App 的 source path argocd/{项目名} argocd/slp-vue-demo
ApplicationSet(业务) {项目名}-{JOB_BASE_NAME}-{环境}-{集群} slp-vue-demo-slp-vue-demo-prod-sz-prod-01
ApplicationSet(公共资源) {项目名}-common-k8s-resource-{环境}-{集群} slp-vue-demo-common-k8s-resource-prod-sz-prod-01
子 Application(业务) {项目名}-{环境}-{集群}-{JOB_BASE_NAME} slp-vue-demo-prod-sz-prod-01-slp-vue-demo
子 Application(公共资源) {项目名}-{环境}-{集群}-common-k8s-resource slp-vue-demo-prod-sz-prod-01-common-k8s-resource
Service 名 {JOB_BASE_NAME} slp-vue-demo
Ingress 域名 {JOB_BASE_NAME}-{环境}.yunwdh.cn slp-vue-demo-prod.yunwdh.cn
GitOps 提交信息 固定 jenkins ci auto commit

⚠️ appNamespace 的坑 :echo "${JOB_NAME}" | awk -F'/' '{print $NR}' 取的是第一段 而非最后一段。 Job 名带 / 分组(如 yunwdh/ywdh-vue)时结果是 yunwdh,所以 Job 必须建在 Jenkins 根目录,不能分组。



第二篇 · 代码逐层拆解

5. 三个仓库的职责

仓库 GitLab 地址 角色 谁维护 变更频率
共享库 http://gitlab.yunwdh.cn/ops/devops/zcyw-jenkins-shared-library 生产者:承载全部流水线逻辑 运维 / DevOps 低(改一次影响所有项目)
GitOps 仓库 http://gitlab.yunwdh.cn/slp/gitops 部署配置:模板 + 各项目实际 YAML 流水线自动提交为主,人工兜底 每次构建都会变
业务仓库 http://gitlab.yunwdh.cn/slp/vue 消费者 :业务代码 + 自己的 Jenkinsfile 业务开发 跟随业务迭代

三者的关系:

复制代码
业务仓库(声明差异:我要构建什么、怎么构建)
    │  Jenkinsfile
    ▼
共享库(定义流程:怎么构建、怎么改配置、怎么部署)
    │  SedYaml 阶段写入
    ▼
GitOps 仓库(描述结果:集群里应该长什么样)
    │  ArgoCD 读取
    ▼
目标集群

6. 共享库详解

仓库:ops/devops/zcyw-jenkins-shared-library

6.1 目录结构与文件作用

复制代码
zcyw-jenkins-shared-library/
├── README.md                              # 简要说明(架构、目录、技术栈支持)
├── vars/
│   └── Pipeline_GitOps_Run.groovy         # ★ 流水线主入口(文件名 = 全局方法名)
├── src/org/devops/
│   ├── Common.groovy                      # 彩色日志输出工具
│   ├── GetParameters.groovy               # 动态生成构建参数(分支→环境→集群 三级级联)
│   ├── PreRunCheck.groovy                 # 构建前校验(参数校验 + prod 权限门)
│   ├── DockerBuild.groovy                 # 多技术栈 Dockerfile 生成与镜像构建
│   ├── SedYaml.groovy                     # ★ GitOps 仓库 YAML 修改与提交
│   └── Deploy.groovy                      # ★ ArgoCD Project / Application / ApplicationSet 编排
├── resources/
│   ├── Jenkinsfile                        # 消费者 Jenkinsfile 模板(含 customDockerfile 示例)
│   ├── Jenkinsfile-test                   # 同上,测试用
│   ├── Pod-CICD-Base-Build-Env.yaml       # ★ K8s Pod Agent 定义(三容器)
│   └── Pvc-CICD-Base-Build-Env.yaml       # Node.js 运行时 PVC(需手动预创建)
├── demo/
│   ├── 声明式-demo.groovy                  # 声明式 pipeline{} 教学示例
│   └── 脚本式-demo.groovy                  # 脚本式 node{}+try/catch 教学示例
├── docs/
│   └── 代码功能分析.md                     # 详细代码分析
└── 报错.txt                               # 历史报错排查记录

为什么 vars/ 和 src/ 分两个目录?

  • vars/ 下的文件名 就是 Jenkins 的全局方法名(vars/MyStep.groovy → 可以写 MyStep())

  • src/ 下是普通 Groovy 类,需要 new org.devops.XXX() 才能用 所以只有"入口"放在 vars/,其他业务逻辑放 src/。

6.2 vars/Pipeline_GitOps_Run.groovy --- 流水线主入口

Agent 定义

复制代码
agent {
    kubernetes {
        yaml libraryResource('Pod-CICD-Base-Build-Env.yaml')  // 从 resources/ 加载 Pod 定义
        cloud 'kubernetes'          // ★ 对应 Jenkins「系统管理 → 云」中的名字,必须一致
        defaultContainer 'ci-build' // 未显式指定的 sh 都在此容器执行
        instanceCap 30              // 并发 Pod 上限
        podRetention never()        // 用完立即删除
    }
}

⚠️ 不要加 retries ------ Declarative 会用它把整个 stages 包进一次 retry:某个 stage 失败会导致整条流水线重跑一遍(第二遍因 currentBuild.result 已是 FAILURE 而全部秒跳过,只在阶段视图里多出一整套空列)。

Options

复制代码
options {
    timestamps()                                    // 日志带时间戳
    timeout(time: 1, unit: 'HOURS')                 // 整条流水线超时 1 小时
    buildDiscarder(logRotator(numToKeepStr: '15'))  // 保留最近 15 次构建
}

Environment(全局变量)

复制代码
environment {
    appNamespace = sh(returnStdout: true, script: "echo \"${JOB_NAME}\"| awk -F'/' '{print \$NR}'").trim()
    projectName  = "${appNamespace}"
    date         = sh(returnStdout: true, script: 'date +%Y%m%d%H%M').trim()
    code         = sh(returnStdout: true, script: 'head /dev/urandom | tr -dc 0-9 | head -c 6').trim()

    // 三个凭证都有 UUID 兜底 —— 业务方不传也能跑(前提是 Jenkins 里存在对应凭证)
    gitCredId    = "${map?.gitCredId    ?: 'a3dea81a-e05d-47db-827e-e4effb10cd6f'}"
    imgRegCredId = "${map?.imgRegCredId ?: '54b99cbd-eb0e-4602-bd72-eb70b9c28454'}"
    argocdCerdId = "${map?.argocdCerdId ?: '0ee5a2bf-ba0d-41c7-84af-d07475cb7eb2'}"
    gitopsBranch = 'master'
    allowProdWithoutUserIdentity = "${map?.allowProdWithoutUserIdentity ?: 'false'}"

    nodeVersion = "${map.nodeVersion}";  jdkVersion = "${map.jdkVersion}"
    goVersion   = "${map.goVersion}";    dotnetVersion = "${map.dotnetVersion}"

    buildCmd         = "${map?.buildCmd ?: 'null'}".replace('env_type', params.Release_Env_Type_Parameter)
    jarPath          = "${map?.jarPath ?: './'}"
    nexusProfile     = "${map.nexusProfile}";   startupPath = "${map.startupPath}"
    portNumber       = "${map?.portNumber ?: '8080'}"
    dockerFile       = "${map?.dockerFile ?: 'null'}"
    customDockerfile = "${map?.customDockerfile ?: 'null'}"
    compOutputPath   = "${map?.compOutputPath ?: 'dist'}"
}

两个最巧妙的设计:

  1. buildCmd 的 env_type 替换

    复制代码
    buildCmd = "${map?.buildCmd ?: 'null'}".replace('env_type', params.Release_Env_Type_Parameter)

    业务方写 npm run build:env_type,选 prod 时自动变成 npm run build:prod ------ 构建命令随环境切换,无需为每个环境写一份 Jenkinsfile。

  2. 所有 map 参数都有默认值 ------ 降低接入门槛。

六个执行阶段

所有阶段都有统一守卫,前置阶段失败则后续全部 skip:

复制代码
when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' } }
阶段 容器 重试 做什么
PreRunCheck ci-build --- 打印参数 → 校验集群选择 → prod 权限门
GitClone jnlp retry(6) 按所选分支拉码,取 commit 前 8 位
ImageBuild ci-build --- 按文件特征分发到不同构建方法
SedYaml cd-deploy retry(8) 浅克隆 GitOps 仓库,改 YAML + commit + push
DeployK8s cd-deploy 内部 retry(2) ArgoCD 登录、建 Project/App、sync

镜像仓库二级路由(SedYaml 阶段)

复制代码
def clusterPrefix = sh(returnStdout: true, script: "echo \"${params.Dest_Cluster_Name_Parameter}\"| awk -F'-' '{print \$NR}'").trim()
switch (clusterPrefix) {
    case 'sz':  imageServer = 'sz-harbor.yunwdh.cn'; break
    case 'cq':  imageServer = 'cq-harbor.yunwdh.cn'; break
    case 'bj':  imageServer = 'bj-harbor.yunwdh.cn'; break
    default:    imageServer = 'bj-harbor.yunwdh.cn'; break
}

按目标集群名前缀选 Harbor :sz-prod-01 → sz-harbor,cq-test-01 → cq-harbor,bj-dev-01 → bj-harbor。

而 ImageBuild 阶段的推送地址是写死的:

复制代码
def registryServer = 'bj-harbor.yunwdh.cn'      // ⚠️ 与上面的拉取路由不一致,见第 18 章隐患 #3

Post

复制代码
post {
    always  { println('always') }
    success { printOutputColor('构建成功!', 'green'); currentBuild.description = '构建成功!' }
    failure { printOutputColor('构建失败!', 'red');   currentBuild.description = '构建失败!' }
    aborted { printOutputColor('构建取消!', 'blue');  currentBuild.description = '构建取消!' }
}

6.3 GetParameters.groovy --- 动态生成参数表单

复制代码
def getParameters(gitUrl) {
    properties([ parameters([
        gitParameter(name: 'Git_Branch_Name_Parameter', type: 'PT_BRANCH_TAG', useRepository: gitUrl, defaultValue: 'master', ...),
        [$class: 'CascadeChoiceParameter', name: 'Release_Env_Type_Parameter',  referencedParameters: 'Git_Branch_Name_Parameter', ...],
        [$class: 'CascadeChoiceParameter', name: 'Dest_Cluster_Name_Parameter', referencedParameters: 'Release_Env_Type_Parameter', ...]
    ]) ])
}

properties() 会把参数定义持久化到 Job 配置里 ------ 这就是为什么你在 Jenkins 页面上能看到这三个参数。

环境列表脚本

复制代码
def dev = ["dev","test","prod"]
def release = ["dev","test","prod"]
def masterList = ["dev","test","prod"]
def other = ["dev","test","prod"]
// 按分支名 switch,返回对应列表

⚠️ 四个分支返回完全相同的列表 ,所以级联「限制」实际未生效 ------ 任何分支都能选 prod。 真正的防线是 PreRunCheck.prodPermissionCheck()。

集群列表脚本(这一步级联是有效的)

复制代码
def devCluster  = ["bj-dev-01"]
def testCluster = ["cq-test-01"]
def prodCluster = ["sz-prod-01"]
def mergedCluster = devCluster + testCluster + prodCluster
// 按 Release_Env_Type_Parameter 返回

⚠️ Dest_Cluster_Name_Parameter 声明为多选 PT_CHECKBOX,但 paramSelectCheck() 又报错禁止含逗号 → 伪单选 。 ⚠️ fallbackScript 里的集群名(cd-gx-az01-biz-priv-dev-01)与正式脚本(bj-dev-01)不一致,建议统一。

6.4 PreRunCheck.groovy --- 三道检查

复制代码
def paramSelectCheck() {
    if (params.Dest_Cluster_Name_Parameter == 'null' || ... == '') error('错误: 未勾选发布的目标集群')
    if (params.Dest_Cluster_Name_Parameter?.contains(',')) error('错误: 只允许选择一个目标集群')
}

prodPermissionCheck() 判定链:

复制代码
def allowWithoutIdentity = "${allowProdWithoutUserIdentity ?: 'false'}" == 'true'
def buildUserId = "${BUILD_USER_ID}"

if (!buildUserId || buildUserId == 'null' || buildUserId == '') {
    handleUnidentifiedUser(envType, '未识别到构建用户 (BUILD_USER_ID 为空)', allowWithoutIdentity); return
}
def user = hudson.model.User.getById(buildUserId, false)
if (user == null) {
    handleUnidentifiedUser(envType, "Jenkins 中未找到用户: ${buildUserId}", allowWithoutIdentity); return
}
def isAdmin = Jenkins.instance.getACL().hasPermission(user.impersonate(), jenkins.model.Jenkins.ADMINISTER)
// 管理员 → 放行;非管理员 + 非 prod → 放行;非管理员 + prod → 报错中止

报错信息(已带解决办法):

复制代码
权限错误: prod 环境要求可识别的 Jenkins 管理员,但 Jenkins 中未找到用户: anonymous。
解决办法: 在 Jenkins「系统管理 → 全局安全配置」中启用安全认证,并用管理员账号登录后触发构建;
若确认该 Jenkins 无需认证,可在 Jenkinsfile 的 map 中设置 allowProdWithoutUserIdentity=true 临时放行。

6.5 DockerBuild.groovy --- 动态生成 Dockerfile

方法 触发条件 行为
buildByWriteDockerfile map.customDockerfile 有值 writeFile('./Dockerfile', customDockerfile) 后构建(优先级最高)
buildByDockerfile map.dockerFile 有值 用指定路径的 Dockerfile 构建
vueBuild package.json 或 app.vue 按 nodeVersion 选基础镜像(22/23/24),区分 nuxt.config.js / nuxt.config.ts / 普通 Vue,生成两阶段 Dockerfile
javaBuild pom.xml Maven 构建 → JRE 运行,内置 JAVA_OPTS(容器内存感知、OOM dump、/dev/./urandom)
goBuild / gccBuild / phpBuild / netBuild --- ⚠️ 未实现 (会抛 MissingMethodException)

四个已实现方法的收尾一致:

复制代码
docker.withRegistry("https://${registryServer}", "${imgRegCredId}") {
    docker.build("${registryServer}/${params.Release_Env_Type_Parameter}/${projectName}/${JOB_BASE_NAME}:${date}-${commitHash}-v${BUILD_ID}", '-f ./Dockerfile .').push()
    sh "docker rmi ${registryServer}/${params.Release_Env_Type_Parameter}/${projectName}/${JOB_BASE_NAME}:${date}-${commitHash}-v${BUILD_ID}"
}

6.6 SedYaml.groovy --- GitOps 的灵魂

这是全库最重要的文件,详见第 3.3 节 ⑦ 与第 7 章。要点:

要点 说明
每次清空重建 sh '''rm -rf ./* .git''' 保证工作目录干净
浅克隆 CloneOption(depth:1, shallow:true) 加速
三种场景 部署新服务 / 部署新环境 / 服务更新(只改 tag)
兜底逻辑 独立校验当前「环境+集群」的 ApplicationSet 是否存在,缺失就补(与目录分支解耦,是"一个应用多环境并存"的关键)
common-k8s-resource 项目级公共资源(ConfigMap / Secret 等),独立一套 ApplicationSet
推送 用 UsernamePasswordMultiBinding 注入凭证,git add/commit/pull/push

6.7 Deploy.groovy --- ArgoCD 编排

复制代码
def gitOps(argocdCerdId) {
    withCredentials([usernamePassword(credentialsId: argocdCerdId, passwordVariable: 'password', usernameVariable: 'username')]) {
        sh "argocd login argocd.yunwdh.cn --username ${username} --password ${password} --insecure --grpc-web"
        def projCount              = ... // argocd proj list | grep -w <项目名> | wc -l
        def fatherApplicationCount = ... // argocd app get <项目名> | wc -l
        def sonApplicationCount    = ... // argocd app list | grep <项目>-<环境>-<集群>-<应用> | wc -l
        def sonApplicationServer   = ... // argocd app get <项目>-<环境>-<集群>-<应用> | grep Server | awk '{print $2}'

        if (projCount == '0')             sh "argocd proj create ..."
        if (fatherApplicationCount == '0') sh "argocd app create ... --path argocd/<项目名> ..."
        if (sonApplicationCount == '0' || sonApplicationServer != destCluster) { /* 完整流程 */ }
        else { /* 仅同步子 App */ }

        retry(2) { try { sh shellCommand.join('; ') } catch (e) { sleep(10); error(...) } }
    }
}

注 :sonApplicationServer != destCluster 是「集群 server URL」与「集群名」比较,恒为真 → 总是走完整流程。功能上无害,但属冗余判断。

6.8 resources/Pod-CICD-Base-Build-Env.yaml --- 构建环境定义

三容器 Pod(jenkins 命名空间):

容器 镜像 用途 关键点
initContainers/fix-workspace-permissions busybox chmod -R 777 /home/jenkins/agent 解决 workspace 权限
jnlp inbound-agent Jenkins 入站代理 名称不可改(插件源码写死)
cd-deploy CentOS 7.9 + git GitOps 操作与 ArgoCD 部署 挂 hostPath 的 argocd CLI
ci-build busybox 默认容器,镜像构建 privileged: true、挂 docker.sock、limits 4C/8Gi

Volumes:

卷 类型 挂载点 说明
workspace-volume emptyDir /home/jenkins/agent 并发构建天然隔离(曾用 hostPath 会撞 .git/config.lock)
docker-sock hostPath /var/run/docker.sock 复用宿主机 Docker 守护进程
docker-cli hostPath (File) /usr/bin/docker 复用宿主机 docker 客户端
argocd-cli hostPath (File) /usr/local/bin/argocd 复用宿主机 argocd CLI
m2-cache hostPath /root/.m2 Maven 缓存加速
go-cache hostPath /root/go/pkg Go 模块缓存加速
nodejs-package PVC /root/.nvm/versions/node/(只读) Node 运行时预装在宿主机,Pod 不下载

⚠️ hostPath 要求 Pod 被调度到"预置了这些路径的节点" ,否则 Pod 启动失败。建议给这类节点打标签并用 nodeSelector 限定。

6.9 resources/Pvc-CICD-Base-Build-Env.yaml

复制代码
# 需事先在K8s构建集群手动创建,待基础构建Pod运行时就会自动与之绑定
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nodejs-package
  namespace: jenkins
spec:
  accessModes: [ReadWriteMany]      # ★ 必须 RWX,否则多节点并发构建失败
  resources:
    requests:
      storage: 20Gi

⚠️ PVC 不会被流水线自动创建,必须手工预建并把 Node 运行时放进去。


7. GitOps 仓库详解

仓库:http://gitlab.yunwdh.cn/slp/gitops,分支固定 master。

7.1 目录结构

复制代码
slp/gitops/
├── template/                                    # ★ 脚手架模板(人工维护,绝对不能删)
│   ├── kustomize/                               # 业务应用模板
│   │   ├── base/deployment.yaml                 #    Deployment(含 imagePullSecrets)
│   │   ├── base/service.yaml                    #    Service(ClusterIP)
│   │   ├── base/kustomization.yaml              #    resources: [deployment.yaml, service.yaml]
│   │   ├── overlays/env/kustomization.yaml      #    overlay(namespace + ingress 引用 + images 替换)
│   │   └── overlays/env/ingress-patch.yaml      #    Ingress
│   ├── kustomize-common-resource/               # 项目级公共资源模板
│   │   ├── base/configmap.yaml                  #    占位 ConfigMap
│   │   ├── base/kustomization.yaml
│   │   └── overlays/env/kustomization.yaml
│   └── argocd/                                  # ArgoCD 模板
│       ├── applicationset.yaml                  #    业务应用的 ApplicationSet
│       ├── common-k8s-resource-applicationset.yaml
│       └── applicationset-config/clusterNameEnv/config.json
│
├── argocd/                                      # ★ 流水线生成的 ApplicationSet(父 App 的 source path)
│   └── <项目名>/
│       ├── <项目>-<应用>-<环境>-<集群>-applicationset.yaml
│       └── <项目>-common-k8s-resource-<环境>-<集群>-applicationset.yaml
│
└── <项目名>/                                     # ★ 项目目录
    ├── <应用名>/                                 # 业务应用
    │   ├── base/deployment.yaml                 #   已实例化
    │   ├── base/service.yaml
    │   ├── base/kustomization.yaml
    │   ├── overlays/<环境>/kustomization.yaml   #   每个环境一份(镜像地址 + tag)
    │   ├── overlays/<环境>/ingress-patch.yaml
    │   └── applicationset-config/<集群>/config.json
    └── common-k8s-resource/                     # 项目级公共资源(同结构)

当前实际内容 (项目 slp-vue-demo,应用 slp-vue-demo,3 个环境):

复制代码
argocd/slp-vue-demo/
  slp-vue-demo-slp-vue-demo-dev-bj-dev-01-applicationset.yaml
  slp-vue-demo-slp-vue-demo-test-cq-test-01-applicationset.yaml
  slp-vue-demo-slp-vue-demo-prod-sz-prod-01-applicationset.yaml
  slp-vue-demo-common-k8s-resource-dev-bj-dev-01-applicationset.yaml
  slp-vue-demo-common-k8s-resource-test-cq-test-01-applicationset.yaml
  slp-vue-demo-common-k8s-resource-prod-sz-prod-01-applicationset.yaml

slp-vue-demo/slp-vue-demo/
  base/{deployment.yaml, service.yaml, kustomization.yaml}
  overlays/{dev,test,prod}/{kustomization.yaml, ingress-patch.yaml}
  applicationset-config/{bj-dev-01,cq-test-01,sz-prod-01}/config.json

7.2 模板占位符对照表(★ 核心)

SedYaml.groovy 通过 sed 把这些占位符替换为实际值:

占位符 出现位置 替换为 示例
serviceNameEnv deployment / service / ingress / kustomization JOB_BASE_NAME(应用名) slp-vue-demo
portNumberEnv deployment / service / ingress portNumber(默认 8080) 80
ingressHostEnv ingress-patch {应用}-{环境}.yunwdh.cn slp-vue-demo-prod.yunwdh.cn
projectNameEnv 全部 projectName(= 命名空间) slp-vue-demo
imagesNameEnv kustomization {Harbor}/{环境}/{项目}/{应用} sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo
imageTagNameEnv kustomization {日期}-{commit8}-v{BUILD_ID} 202609200731-46faaaa4-v47
cdAppNameEnv applicationset ApplicationSet 名 slp-vue-demo-slp-vue-demo-prod-sz-prod-01
appNameEnv applicationset JOB_BASE_NAME slp-vue-demo
appNamespaceEnv applicationset 命名空间 slp-vue-demo
targetClusterEnv applicationset / config.json 目标集群名 sz-prod-01
applicationNameEnv applicationset / config.json 子 Application 名 slp-vue-demo-prod-sz-prod-01-slp-vue-demo
sourceFilePathEnv applicationset / config.json overlay 路径 slp-vue-demo/slp-vue-demo/overlays/prod

⚠️ 占位符是模板的「契约」 :模板里必须保留占位符,grep 才能匹配到文件,sed 才有输入。 一旦把占位符写成写死值(例如把 namespace: serviceNameEnv 改成 namespace: common-k8s-resource), 就会导致 sed: no input files 报错、流水线失败。 改模板时务必用 grep -rl <占位符> template/ 检查一遍。

7.3 各模板文件详解

template/kustomize/base/deployment.yaml

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: serviceNameEnv
  labels: { app: serviceNameEnv }
spec:
  replicas: 1
  selector: { matchLabels: { app: serviceNameEnv } }
  template:
    metadata: { labels: { app: serviceNameEnv } }
    spec:
      imagePullSecrets:
        - name: harbor-secret          # ⚠️ 每个目标命名空间必须存在同名 Secret
      containers:
        - name: serviceNameEnv
          image: app-image:latest      # ← 由 overlay 的 images 替换器改写
          ports: [{ containerPort: portNumberEnv }]
          env: [{ name: PROJECT_NAME, value: projectNameEnv }]

image: app-image:latest 只是一个锚点名 ,overlay 里用 Kustomize 的 images 替换器改成真实地址。

template/kustomize/base/service.yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: serviceNameEnv
  labels:
    app: serviceNameEnv
spec:
  type: ClusterIP
  selector:
    app: serviceNameEnv          # ★ 必须与 Deployment 的 pod 标签一致,否则没有 endpoints
  ports:
    - name: http
      protocol: TCP
      port: portNumberEnv        # ★ Ingress 的 backend.port.number 引用的是这个
      targetPort: portNumberEnv

template/kustomize/base/kustomization.yaml

复制代码
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

template/kustomize/overlays/env/kustomization.yaml

复制代码
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: serviceNameEnv
resources:
  - ../../base
  - ingress-patch.yaml         # ★ 不写这行,ingress-patch.yaml 就不会被渲染
images:
  - name: app-image
    newName: imagesNameEnv
    newTag: imageTagNameEnv

template/kustomize/overlays/env/ingress-patch.yaml

复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: serviceNameEnv
spec:
  rules:
    - host: ingressHostEnv
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: serviceNameEnv
                port:
                  number: portNumberEnv

⚠️ 没有 ingressClassName 也没有 annotations ------ 依赖目标集群存在默认 IngressClass 。 实测 cq-test-01 集群的默认 IngressClass 是 nginx ✅

template/argocd/applicationset.yaml

复制代码
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cdAppNameEnv
  namespace: argo-cd                       # ★ 命名空间硬编码为 argo-cd
spec:
  generators:
    - clusters:
        selector:
          matchLabels:
            cluster-name: targetClusterEnv  # ★ 靠集群 Secret 的 label 筛选
  template:
    metadata:
      name: applicationNameEnv
      labels: { app: appNameEnv, project: projectNameEnv }
    spec:
      project: projectNameEnv
      source:
        repoURL: http://gitlab.yunwdh.cn/slp/gitops.git
        targetRevision: HEAD
        path: sourceFilePathEnv
      destination:
        server: '{{server}}'                # ★ ApplicationSet 自动注入目标集群地址
        namespace: appNameEnv
      syncPolicy:
        automated: { prune: true, selfHeal: true }
        syncOptions: [CreateNamespace=true] # ★ 命名空间自动创建

template/kustomize-common-resource/base/configmap.yaml

复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: common-k8s-resource
  labels: { app: common-k8s-resource }
data:
  README: "此 ConfigMap 由 Jenkins 流水线自动生成,请按项目实际需要替换为真实的公共资源"

用途:放项目级的公共资源(多个服务共用的 ConfigMap / Secret / 中间件),避免每个服务重复部署。

template/argocd/applicationset-config/clusterNameEnv/config.json

复制代码
{
  "cluster":     { "name": "targetClusterEnv" },
  "application": { "name": "applicationNameEnv" },
  "source":      { "repoURL": "http://192.168.10.104/slp/gitops.git", "path": "sourceFilePathEnv" }
}

⚠️ 该文件由流水线生成,但当前 ApplicationSet 模板并未引用它 ------ 属预留/冗余,或供外部平台读取。

7.4 ArgoCD 三层应用模型

复制代码
父 Application:slp-vue-demo
  │   project    = slp-vue-demo
  │   repoURL    = http://gitlab.yunwdh.cn/slp/gitops.git
  │   path       = argocd/slp-vue-demo        ← 该目录下放的都是 ApplicationSet
  │   destination= https://kubernetes.default.svc(in-cluster,即 ArgoCD 所在集群)
  │   dest ns    = argo-cd
  │   syncPolicy = automated
  │
  ├─► ApplicationSet:slp-vue-demo-slp-vue-demo-prod-sz-prod-01
  │      clusters 生成器筛选 cluster-name=sz-prod-01
  │      └─► 子 Application:slp-vue-demo-prod-sz-prod-01-slp-vue-demo
  │             source.path = slp-vue-demo/slp-vue-demo/overlays/prod
  │             dest.server = https://192.168.10.112:6443
  │             dest.ns     = slp-vue-demo
  │             → apply Deployment + Service + Ingress
  │
  └─► ApplicationSet:slp-vue-demo-common-k8s-resource-prod-sz-prod-01
         └─► 子 Application:slp-vue-demo-prod-sz-prod-01-common-k8s-resource
                source.path = slp-vue-demo/common-k8s-resource/overlays/prod
                dest.ns     = common-k8s-resource
                → apply ConfigMap

为什么不直接建 Application,而要多一层 ApplicationSet?

好处 说明
集群维度可扩展 加一个集群只需给它打上 cluster-name 标签,ApplicationSet 自动生成新的子 App,不用改 YAML、不用改流水线
环境维度隔离 ApplicationSet 按「应用+环境+集群」命名,同一应用的不同环境互不干扰
配置集中 所有 ArgoCD 对象都在 GitOps 仓库里,父 App 一同步就全部生效

7.5 GitOps 仓库的变更来源

变更类型 谁做的 提交信息
服务更新(改镜像 tag) 流水线 SedYaml jenkins ci auto commit
新服务 / 新环境接入 流水线 SedYaml jenkins ci auto commit
模板调整、补 Service、引用 ingress 人工 fix: ...
清理历史目录 人工 reset: 清空业务目录

8. 业务仓库详解

以 slp/vue 为例:

复制代码
slp/vue/
├── Jenkinsfile        # ★ 流水线入口:加载共享库 + 传 map
├── index.html         # 前端入口页
├── package.json       # 依赖与构建脚本(决定走 vueBuild)
├── nginx.conf         # 运行时 Nginx 配置(被打进镜像)
├── src/main.js        # createApp(App).mount('#app')
├── src/App.vue        # 根组件
└── .gitignore

8.1 Jenkinsfile(业务方唯一需要写的文件)

复制代码
#!groovy

library(
  identifier: "zcyw-jenkins-shared-library@master", retriever: modernSCM([
      $class: "GitSCMSource",
      remote: "http://gitlab.yunwdh.cn/ops/devops/zcyw-jenkins-shared-library.git",
      credentialsId: 'gitlab-credentials'
  ])
)

def map = [:]
map.put('gitUrl',       'http://gitlab.yunwdh.cn/slp/vue')
map.put('gitCredId',    'gitlab-credentials')
map.put('imgRegCredId', 'harbor-credentials')
map.put('buildCmd',     'mkdir -p dist && cp index.html dist/index.html')
map.put('portNumber',   '80')
map.put('compOutputPath','./dist')
map.put('nodeVersion',  '22')
Pipeline_GitOps_Run(map)

业务仓库使用这套流水线的全部成本,就是上面这几行。

8.2 map 支持的完整参数清单

参数 必填 默认值 说明
gitUrl ✅ --- 业务仓库地址(同时用于 Git Parameter 的分支列表)
gitCredId 建议 a3dea81a-... 拉代码凭证 ID
imgRegCredId 建议 54b99cbd-... Harbor 凭证 ID
argocdCerdId 建议 0ee5a2bf-... ArgoCD 凭证 ID
buildCmd 前端/Java 必填 null 编译命令,支持 env_type 占位
nodeVersion 前端必填 --- 22 / 23 / 24
jdkVersion Java 可选 --- 1.8 / 17
goVersion / dotnetVersion 可选 --- ⚠️ 对应构建方法未实现
portNumber 可选 8080 容器端口(本项目传 80)
compOutputPath 前端可选 dist 静态产物目录,不要写 ./
jarPath Java 可选 ./ jar 相对路径
nexusProfile Java 可选 --- Maven profile
startupPath .NET 可选 --- ⚠️ 对应构建方法未实现
dockerFile 可选 null 自带 Dockerfile 路径
customDockerfile 可选 null 内联 Dockerfile。设置后 buildCmd/portNumber/compOutputPath/nodeVersion 全部失效
allowProdWithoutUserIdentity 可选 false 无法识别构建用户时是否放行 prod。临时开关,不推荐开启

8.3 各文件作用

文件 作用 是否必需
Jenkinsfile 流水线入口,声明 map ✅ 必需
package.json 技术栈识别依据(vueBuild);提供 scripts.build 前端项目必需
nginx.conf 被打进 Nginx 运行阶段镜像:root /data/app、try_files $uri $uri/ /index.html(解决 SPA 刷新 404)、开启 gzip 前端项目必需
pom.xml Java 项目识别依据 Java 项目必需
go.mod Go 项目识别依据 ⚠️ Go 构建方法未实现
Dockerfile 通过 map.dockerFile 指定 可选
src/ 业务源码 ---
.gitignore 排除构建产物 建议

⚠️ 当前 index.html 没有 <script> 标签 去加载 main.js,所以 App.vue / main.js 永远不会执行,页面是空白的。如果要看视觉效果,需要补:

复制代码
<script type="module" src="/src/main.js"></script>


第三篇 · 配置全景(谁在哪配)

9. 配置归属总表:手工 vs 代码

这一章是新手最容易糊涂的地方:打开 Jenkins 页面看到一堆配置,到底哪些是我要手工维护的?哪些是代码自动生成的?

9.1 一张图看清三层

复制代码
Jenkins 页面(✍️ 手工创建,每个项目只做一次)
└── Job「slp-vue-demo」
     ├── Definition: Pipeline script from SCM
     ├── SCM: http://gitlab.yunwdh.cn/slp/vue      ← 去这里取 Jenkinsfile
     ├── 脚本路径: Jenkinsfile
     ├── Branches to build: */master
     ├── 触发器: GitLab webhook
     ├── 凭证: gitlab-credentials
     └── 三个构建参数  ← ⚠️ 你在页面看到它们,但**不是你建的**
              ▲
              │  properties()  自动回写
              │
业务仓库 slp/vue ──► Jenkinsfile ──► library(...) 加载共享库
   (📄 代码)                            │
                                         ▼
                      共享库 zcyw-jenkins-shared-library(📄 代码)
                        ├── GetParameters.groovy   → 生成 3 个构建参数
                        ├── PreRunCheck.groovy     → 参数校验 + prod 权限门
                        ├── DockerBuild.groovy     → 生成 Dockerfile、打镜像推镜像
                        ├── SedYaml.groovy         → 改 gitops 仓库并 push
                        ├── Deploy.groovy          → 创建/同步 ArgoCD 对象
                        └── Pipeline_GitOps_Run    → 串起 6 个阶段

9.2 逐项归属表

配置项 在哪 谁维护 手工 / 自动
Job 对象本身 (名称 <项目名>) Jenkins 页面 你 ✍️ 手工(每个项目建一次)
Definition = Pipeline script from SCM Jenkins 页面 你 ✍️ 手工
SCM 仓库 http://gitlab.yunwdh.cn/slp/vue Jenkins 页面 你 ✍️ 手工
凭证 gitlab-credentials Jenkins 页面 你 ✍️ 手工
脚本路径 Jenkinsfile Jenkins 页面 你 ✍️ 手工
分支 */master Jenkins 页面 你 ✍️ 手工
触发器(GitLab webhook) Jenkins 页面 你 ✍️ 手工
不允许并发构建 / 保留 15 次 Jenkins 页面 你 ✍️ 手工 (代码里也有 buildDiscarder)
三个构建参数(分支 / 环境 / 集群) 页面显示,实际由流水线写入 你 流水线 🤖 自动生成
六个执行阶段 共享库 Pipeline_GitOps_Run.groovy 运维 📄 代码
参数的可选值与级联规则 共享库 GetParameters.groovy 运维 📄 代码
prod 权限管控 共享库 PreRunCheck.groovy 运维 📄 代码
打镜像的方式 / Dockerfile 内容 共享库 DockerBuild.groovy 运维 📄 代码
GitOps 仓库里生成什么文件 gitops 的 template/ + 共享库 SedYaml.groovy 运维 📄 代码
ArgoCD 怎么建 Project/App、怎么同步 共享库 Deploy.groovy 运维 📄 代码
map(gitUrl / buildCmd / 端口 / Node 版本...) 业务仓库 Jenkinsfile 业务开发 📄 代码
部署后集群里的样子(副本数、探针、资源限制) gitops 仓库 base/deployment.yaml 运维 📄 代码(ArgoCD 自动同步)
AppProject / Application / ApplicationSet ArgoCD(由流水线创建) 流水线 🤖 自动
集群注册与 cluster-name 标签 ArgoCD 页面 / kubectl 运维 ✍️ 手工
Harbor 项目(dev/test/prod)、账号 Harbor 页面 运维 ✍️ 手工
各命名空间的 harbor-secret 目标集群 运维 ✍️ 手工
DNS 记录 *-<环境>.yunwdh.cn DNS 服务 运维/网管 ✍️ 手工

9.3 关于那三个参数(最容易误解)

你在 Job 配置页看到:

复制代码
☑ 参数化构建过程
   Git_Branch_Name_Parameter       ← Git 参数(分支或标签)
   Release_Env_Type_Parameter      ← Active Choices Reactive Parameter
   Dest_Cluster_Name_Parameter     ← Active Choices Reactive Parameter

它们不是你手工加的,是流水线第一次运行时自动写进去的:

复制代码
// Pipeline_GitOps_Run.groovy:15   ← 注意:在 pipeline{} 之前调用
parameters.getParameters("${map.gitUrl}")

// GetParameters.groovy:56
def getParameters(gitUrl) {
    properties([ parameters([ ...三个参数定义... ]) ])   // ← properties() 会持久化到 Job 配置
}

properties() 是 Pipeline 的内置步骤,作用是设置当前 Job 的属性并保存到 Job 配置。所以你刷新页面就能看到。

⚠️ 重要推论

你做的事 结果
在页面修改这三个参数的默认值/描述 ❌ 下次构建会被流水线覆盖回去
在页面新增第 4 个参数 ❌ 下次构建会消失 (properties() 用完整列表覆盖)
想改参数的可选值(比如加个 sit 环境) ✅ 要改共享库的 GetParameters.groovy

10. Jenkins

10.1 Job 配置详解(手工部分)

General

配置项 建议值 说明
描述 可空 ---
不允许并发构建 勾选 ⚠️ 重要:避免两个构建同时改 GitOps 仓库导致 push 冲突
丢弃旧的构建 策略 = Log Rotation ,最大个数 = 15 与流水线里的 buildDiscarder 一致

参数化构建过程

这三个参数由流水线自动生成,不要手工改(见 9.3)。这里只列出它们的实际配置,便于理解:

# 名称 插件 关键配置
1 Git_Branch_Name_Parameter Git 参数 参数类型 = 分支或标签 ;默认值 = master
2 Release_Env_Type_Parameter Active Choices Reactive Parameter Choice Type = Single Select ;Referenced parameters = Git_Branch_Name_Parameter;Enable filters: Filter starts at = 1
3 Dest_Cluster_Name_Parameter Active Choices Reactive Parameter Choice Type = Check Boxes ;Referenced parameters = Release_Env_Type_Parameter;Filter starts at = 1

Triggers

触发器 建议
Build when a change is pushed to GitLab ✅ 启用。Webhook URL:http://jenkins.yunwdh.cn/project/<Job名>
定时构建 按需
轮询 SCM ❌ 建议关闭(用 webhook 更实时、更省资源)
其他工程构建后触发 按需

这是唯一必须手工维护的触发配置 。在 GitLab 仓库的 Settings → Webhooks 配同一个 URL,勾选 Push events / Merge request events。

流水线

配置项 值
定义 Pipeline script from SCM
SCM Git
Repository URL http://gitlab.yunwdh.cn/slp/vue
Credentials slp/****** (gitlab-credentials)
Branches to build */master
脚本路径 Jenkinsfile
轻量级检出 勾选

注意 :Job 的 SCM 与 Jenkinsfile 里的 map.gitUrl 是两个独立配置。

  • Job 的 SCM 决定用哪个仓库的 Jenkinsfile 跑流水线 (分支固定 */master)

  • map.gitUrl 决定运行时拉哪个仓库的代码、以及分支参数的来源(可切换分支) 两者通常配成同一个仓库。

10.2 插件清单(必需)

插件 代码中的依据 缺失时的表现
Kubernetes agent { kubernetes { ... } }、libraryResource No such DSL method 'kubernetes'
Pipeline: Declarative pipeline { } 语法不识别
Pipeline: Basic Steps sh/echo/writeFile/fileExists/dir/timeout/retry/sleep/error/wrap 各种 DSL 缺失
Pipeline: SCM Step checkout scmGit(...) No such DSL method 'scmGit'
Git + Git Client SCM 配置与 git 操作 ---
Git Parameter gitParameter(name: 'Git_Branch_Name_Parameter', ...) 分支参数不显示
Active Choices(旧名 uno-choice) CascadeChoiceParameter、GroovyScript 后两个参数不显示
AnsiColor ansiColor('xterm') { ... } 彩色日志失效
Build User Vars BUILD_USER_ID、BUILD_USER、wrap([$class:'BuildUser']) 取不到构建人 → prod 部署被权限门拦下
Docker Pipeline docker.withRegistry / docker.build 镜像构建阶段失败
Credentials Binding withCredentials 凭证注入失败
Timestamper timestamps() No such DSL method
Pipeline: Stage View 阶段视图 UI 仅影响展示
组件 当前环境版本
Jenkins 2.555.3
Pod 内 Git CLI 2.47.3

10.3 Kubernetes 云配置

流水线里写的是 cloud 'kubernetes',必须在 Jenkins 全局配置里存在同名云。

路径 :系统管理 → 云 → 新建云 → Kubernetes

配置项 值 说明
名称 kubernetes ★ 必须与代码完全一致
Kubernetes 地址 构建集群 API Server 如 https://<apiserver>:6443
Kubernetes 命名空间 jenkins Pod 创建位置
凭据 具备创建 Pod 权限的凭证 ServiceAccount Token / kubeconfig
Jenkins 地址 http://jenkins.yunwdh.cn ★ JNLP 容器回连 Jenkins 用,最容易配错

⚠️ Jenkins 地址 填错的表现 :Pod 一直 Pending 或 is offline。

RBAC 要求 (Jenkins 凭证在 jenkins 命名空间需要):

复制代码
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/exec", "pods/log", "pods/portforward",
                "secrets", "configmaps", "persistentvolumeclaims", "events", "services"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

10.4 凭证清单(★ 重点)

流水线共需要 6 个凭证:

# 用途 代码变量 map 参数 本环境实际值 类型 来源 / 所需权限
1 拉业务代码 + 推 GitOps 仓库 gitCredId gitCredId gitlab-credentials (用户名 slp) Username with password GitLab 账号 。需对 slp/gitops 读写 (写是给 SedYaml push 用),对 slp/vue、共享库有读权限
2 Docker 登录 / 推拉镜像 imgRegCredId imgRegCredId harbor-credentials (用户名 admin) Username with password Harbor 账号 。需对 dev/test/prod 有 push + pull。建议改用机器人账号
3 ArgoCD 操作 argocdCerdId argocdCerdId 0ee5a2bf-ba0d-41c7-84af-d07475cb7eb2(默认 UUID) Username with password ArgoCD 账号 (用户名 admin)。需能 proj create / app create / app sync
4 加载共享库 --- --- gitlab-credentials(复用 #1) 同上 在 Jenkinsfile 的 library(...) 里引用
5 连接构建集群(K8s Cloud) --- --- 自定义 Secret text / kubeconfig 构建集群的 ServiceAccount Token
6 GitLab Connection(webhook) --- --- 自定义 GitLab API token GitLab 个人访问令牌(api scope)

凭证 ID 有"可读名"和"UUID"两种写法 。代码对 gitCredId/imgRegCredId/argocdCerdId 都提供了 UUID 兜底 → 说明旧环境用 UUID 命名。新环境建议统一用可读名 并在 map 中显式传入。

创建步骤(以 #1 为例):

复制代码
系统管理 → 凭据 → 系统 → 全局凭据 → 添加凭据
  类型:Username with password
  Username: slp
  Password: <GitLab 密码或 Access Token>
  ID:       gitlab-credentials        ← ★ ID 必须与 Jenkinsfile 里写的一致
  Description: GitLab 代码仓库凭据

验证三个凭证:

凭证 验证方法
gitlab-credentials GitClone 阶段能 checkout;SedYaml 阶段能 push
harbor-credentials ImageBuild 阶段出现 Login Succeeded
ArgoCD 凭证 DeployK8s 阶段出现 'admin:login' logged in successfully

10.5 安全配置(prod 权限门的前提)

路径 :系统管理 → 全局安全配置

配置项 建议值 原因
安全域 Jenkins 专用用户数据库 需要能识别构建用户
授权策略 矩阵授权策略 需要能给用户管理员权限
匿名用户权限 只读或更小 避免匿名触发

再创建管理员用户:系统管理 → 管理用户 → 新建用户,在授权矩阵中勾选 Administer。

⚠️ 不要图省事去开 allowProdWithoutUserIdentity=true ------ 那等于取消 prod 权限管控。

10.6 网络连通性

Jenkins 控制器 需要能访问:构建集群 API Server(6443)、gitlab.yunwdh.cn(80)。

Agent Pod 内的容器需要能访问:

目标 端口 用在哪
gitlab.yunwdh.cn 80 checkout 代码、拉/推 gitops
*-harbor.yunwdh.cn 80 docker login / push
argocd.yunwdh.cn 443 argocd login / app sync
registry.cn-chengdu.aliyuncs.com 443 拉取构建基础镜像
Jenkins 控制器 50000 或 80/443 jnlp 回连
复制代码
curl -sI http://gitlab.yunwdh.cn
curl -s  http://bj-harbor.yunwdh.cn/v2/      # 期望 401
curl -skI https://argocd.yunwdh.cn

11. ArgoCD

11.1 安装与基础配置

项 值 说明
命名空间 argo-cd ⚠️ 代码中硬编码 :ApplicationSet 的 metadata.namespace、父 App 的 --dest-namespace 都是 argo-cd
访问域名 argocd.yunwdh.cn ⚠️ 代码中硬编码 :argocd login argocd.yunwdh.cn
访问方式 HTTPS(代码用 --insecure,说明是自签证书)
gRPC 需支持 --grpc-web(代码中每条 argocd 命令都带此参数)

⚠️ 如果安装到非 argo-cd 命名空间,ApplicationSet 与父 Application 都无法工作,必须同步改共享库代码。

ArgoCD 必须能访问 http://gitlab.yunwdh.cn/slp/gitops.git(父 App 与子 App 的 source 都指向它)。

11.2 添加目标集群(★ 核心)

为什么必须打标签

template/argocd/applicationset.yaml 用的是 cluster 生成器 ,靠集群 Secret 上的标签筛选:

复制代码
  generators:
    - clusters:
        selector:
          matchLabels:
            cluster-name: targetClusterEnv      # 会被 sed 替换成实际集群名(如 sz-prod-01)

→ 所以集群 Secret 必须带 cluster-name: <集群名> 标签 ,且值要与 Jenkins 参数里的集群名逐字符一致。

前置条件

复制代码
kubectl config get-contexts                       # 看本机有哪些 context
kubectl --context=kubernetes-admin@kubernetes get nodes   # 确认能连上目标集群
# 注:本项目三个集群的 master 上,context 名都叫 kubernetes-admin@kubernetes(无法区分)

方式一:CLI 添加(推荐)

复制代码
# 登录 ArgoCD
argocd login argocd.yunwdh.cn --username admin --insecure --grpc-web

# 添加集群
# 本项目三个集群的 master 上 context 名都是 kubernetes-admin@kubernetes,无法区分,
# 所以用 KUBECONFIG 环境变量分别指向各集群的 kubeconfig 文件
KUBECONFIG=~/.kube/config-bj-dev  argocd cluster add kubernetes-admin@kubernetes --name bj-dev-01  -y
KUBECONFIG=~/.kube/config-cq-test argocd cluster add kubernetes-admin@kubernetes --name cq-test-01 -y
KUBECONFIG=~/.kube/config-sz-prod argocd cluster add kubernetes-admin@kubernetes --name sz-prod-01 -y

# 顺便打标签(部分 CLI 版本支持 --label,若报错就用下面的方式二)
KUBECONFIG=~/.kube/config-sz-prod argocd cluster add kubernetes-admin@kubernetes \
    --name sz-prod-01 --label cluster-name=sz-prod-01 -y

argocd cluster add 会自动在目标集群创建:

  • ServiceAccount argocd-manager(kube-system 命名空间)

  • ClusterRole / ClusterRoleBinding argocd-manager-role(cluster-admin 权限 ) 并在 ArgoCD 的 argo-cd 命名空间创建集群 Secret。

方式二:直接给集群 Secret 打标签(通用,不受 CLI 版本影响)

复制代码
# ① 找到集群 Secret 名
kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster

# ② 打标签(集群 Secret 名形如 cluster-<一串字符>,用上一步查到的实际名字)
kubectl -n argo-cd label secret cluster-xxxxxxxx cluster-name=sz-prod-01

# 或直接 patch
kubectl -n argo-cd patch secret cluster-xxxxxxxx --type=merge \
  -p '{"metadata":{"labels":{"cluster-name":"sz-prod-01"}}}'

方式三:声明式创建集群 Secret

复制代码
apiVersion: v1
kind: Secret
metadata:
  name: cluster-sz-prod-01
  namespace: argo-cd
  labels:
    argocd.argoproj.io/secret-type: cluster
    cluster-name: sz-prod-01          # ★ 关键标签
stringData:
  name: sz-prod-01
  server: https://192.168.10.112:6443
  config: |
    {
      "tlsClientConfig": { "insecure": true },
      "bearerToken": "<ServiceAccount Token>",
      "namespace": "kube-system"
    }

需要添加的集群清单

集群名 API Server 环境 必须的标签
bj-dev-01 https://192.168.10.107:6443 dev cluster-name: bj-dev-01
cq-test-01 https://192.168.10.110:6443 test cluster-name: cq-test-01
sz-prod-01 https://192.168.10.112:6443 prod cluster-name: sz-prod-01

⚠️ 集群名必须与 GetParameters.groovy 里 destClusterNameGroovyScript 的取值完全一致 , 否则:Jenkins 选的集群名 → targetClusterEnv 替换值 → 标签匹配不上 → 子 Application 永远不生成。

验证

复制代码
argocd cluster list

期望输出:

复制代码
SERVER                          NAME        VERSION  STATUS      MESSAGE
https://192.168.10.107:6443     bj-dev-01   v1.31.9  Successful
https://192.168.10.110:6443     cq-test-01  v1.31.9  Successful
https://192.168.10.112:6443     sz-prod-01           Unknown     Cluster has no applications and is not being monitored.
https://kubernetes.default.svc  in-cluster  v1.31.9  Successful

✅ Unknown / Cluster has no applications and is not being monitored 是正常的 ------ 意思是"当前没有应用指向这个集群,ArgoCD 不做主动探测"。有应用之后会变成 Successful。

补充验证(不依赖 argocd CLI,最可靠):

复制代码
# ① 集群 API 是否真的通
curl -k -s https://192.168.10.112:6443/version      # 期望返回 gitVersion JSON

# ② 标签是否打上
kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster \
  -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels

⚠️ 集群不可达时的表现 :argocd cluster list 显示 Failed ... connection refused。 此时任何同步都会失败 ;更麻烦的是------如果之前有 Application 正在删除,会永远卡在 application is deleting (因为级联删除需要能连上目标集群去删资源)。处理办法见 17.3。

11.3 AppProject 与 Application(不用手工建)

Deploy.groovy 会自动完成:

复制代码
# Project 不存在则创建
argocd proj create <项目名> --description <项目名> -d '*,*' -s '*' --allow-cluster-resource '*/*'

# 父 Application 不存在则创建
argocd app create --project default --name <项目名> \
  --repo http://gitlab.yunwdh.cn/slp/gitops.git --path argocd/<项目名> \
  --dest-server https://kubernetes.default.svc --dest-namespace argo-cd \
  --revision master --sync-policy automated
对象 名称 是否自动创建
AppProject <项目名> ✅ 自动
父 Application <项目名> ✅ 自动
ApplicationSet <项目>-<应用>-<环境>-<集群> ✅ 由流水线写 GitOps 仓库后,父 App 同步生成
子 Application <项目>-<环境>-<集群>-<应用> ✅ 由 ApplicationSet 生成

⚠️ AppProject 一旦创建就不要手工删(虽然能重建,但会造成短暂中断)。

11.4 权限模型

层级 由谁创建 作用
Cluster Secret 运维手工添加 告诉 ArgoCD 有哪些集群可用
AppProject 流水线自动 限定该项目能用哪些仓库/集群/命名空间
Application / ApplicationSet 流水线 + ApplicationSet 控制器 具体部署

12. Harbor 镜像仓库

12.1 需要几个 Harbor

镜像地址由两处独立逻辑决定,这是整套系统最容易出问题的地方:

环节 代码位置 逻辑
推送镜像 Pipeline_GitOps_Run.groovy def registryServer = 'bj-harbor.yunwdh.cn' ------ 硬编码 bj,与所选环境和集群无关
拉取镜像 SedYaml.groovy(由上方传入 imageServer) 按集群名前缀:sz→sz-harbor、cq→cq-harbor、bj/其他→bj-harbor

结果对照:

部署目标 镜像推到 YAML 里写的 是否一致
dev / bj-dev-01 bj-harbor.../dev/... bj-harbor.../dev/... ✅ 一致
test / cq-test-01 bj-harbor.../test/... cq-harbor.../test/... ❌ 不一致
prod / sz-prod-01 bj-harbor.../prod/... sz-harbor.../prod/... ❌ 不一致
不以 sz/cq/bj 开头的集群(如 gz-test-01) bj-harbor... bj-harbor... ✅ 一致(走 default)

这不一定是错的 ------ "统一推到主仓库 + 各站点从本地仓库拉"是标准的镜像分发架构(上传快、拉取快)。 但前提是 bj 与 cq/sz 之间有共享存储或复制规则 ,否则跨地域集群会 ImagePullBackOff。 详见 12.5 与第 18 章隐患 #3。

12.2 域名与 TLS

域名 用途
bj-harbor.yunwdh.cn 主接收仓库(所有镜像都推这里)
cq-harbor.yunwdh.cn cq 集群拉取
sz-harbor.yunwdh.cn sz 集群拉取
复制代码
curl -s http://bj-harbor.yunwdh.cn/v2/     # 期望返回 401(说明 Harbor 活着)
curl -s http://cq-harbor.yunwdh.cn/v2/
curl -s http://sz-harbor.yunwdh.cn/v2/

⚠️ 如果通过 HTTP (非 443)访问,构建节点的 Docker 必须配置 insecure-registries:

复制代码
// /etc/docker/daemon.json
{
  "insecure-registries": [
    "bj-harbor.yunwdh.cn",
    "cq-harbor.yunwdh.cn",
    "sz-harbor.yunwdh.cn"
  ]
}
复制代码
systemctl restart docker && docker info | grep -A5 "Insecure Registries"

不配会报:http: server gave HTTP response to HTTPS client 症状对照 :connection refused = 服务没起;HTTP response to HTTPS client = 协议/端口配错。

12.3 Harbor 项目(Project)

镜像路径是:

复制代码
${registryServer}/${params.Release_Env_Type_Parameter}/${projectName}/${JOB_BASE_NAME}:${tag}
        │                    │                        │              │
   bj-harbor...          dev/test/prod           slp-vue-demo    slp-vue-demo

→ Harbor 上的项目名 = 环境名,因此必须创建:

Harbor 项目 用途 建议
dev 开发环境镜像 私有
test 测试环境镜像 私有
prod 生产环境镜像 私有 + 不可变 tag + 保留策略

创建方式 :Harbor UI → 项目 → 新建项目 → 名称填 dev / test / prod

或 API:

复制代码
curl -u admin:<密码> -X POST "https://bj-harbor.yunwdh.cn/api/v2.0/projects" \
  -H "Content-Type: application/json" \
  -d '{"project_name":"prod","metadata":{"public":"false"}}'

⚠️ 不预先创建项目 ,docker push 会报 denied: requested access to the resource is denied。

12.4 推送账号与权限

账号 类型 权限
admin 系统管理员 全权限(当前环境在用)
推荐:机器人账号 Robot Account 限定 dev/test/prod 三项目的 push + pull,可轮换、可审计
复制代码
Harbor UI → 项目 dev/test/prod → 机器人账号 → 新建机器人账号
  名称:jenkins
  权限:勾选 push + pull
  → 生成 name(如 robot$jenkins)+ secret,二者即为 Jenkins 凭证的用户名/密码

用机器人账号时,Jenkins 凭证的 Username 要填完整名 (含 robot$ 前缀)。

验证推送权限:

复制代码
docker login -u <账号> -p <密码> https://bj-harbor.yunwdh.cn
docker pull registry.cn-chengdu.aliyuncs.com/qiuyl01/nginx:alpine
docker tag  registry.cn-chengdu.aliyuncs.com/qiuyl01/nginx:alpine bj-harbor.yunwdh.cn/prod/test-push:v1
docker push bj-harbor.yunwdh.cn/prod/test-push:v1
docker rmi  bj-harbor.yunwdh.cn/prod/test-push:v1

12.5 多 Harbor 的镜像同步方案(★ 关键)

因为 push 固定到 bj-harbor,而 test/prod 的 YAML 指向 cq-harbor / sz-harbor,必须解决"镜像如何在多个 Harbor 上都可见"。三种方案,任选其一:

方案 A:共享存储后端(当前环境的做法,零配置)

原理 :三个 Harbor 域名指向同一套 Harbor 服务或同一份存储后端 ,所以推到 bj-harbor 的镜像在 sz-harbor 也能拉到。

优点 缺点
零配置、零延迟、无一致性风险 跨地域拉取要走广域网,慢;一个实例故障影响所有环境

验证方法:

复制代码
# 推到一个域名,从另一个域名拉
docker tag bj-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo:v1 \
          sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo:v1
docker pull sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo:v1

判定是哪种情况:

复制代码
# ① 三个环境是不是同一个实例
curl -u admin:<密码> -s http://bj-harbor.yunwdh.cn/api/v2.0/systeminfo
curl -u admin:<密码> -s http://sz-harbor.yunwdh.cn/api/v2.0/systeminfo
#   对比 harbor_version / registry_url

# ② 有没有配复制规则
curl -u admin:<密码> -s "http://bj-harbor.yunwdh.cn/api/v2.0/replication/policies"
#   或 Harbor UI → 系统管理 → 复制

# ③ 交叉验证:在 bj 推一个独特 tag,立刻去 sz 查
curl -u admin:<密码> -s "https://sz-harbor.yunwdh.cn/api/v2.0/projects/prod/repositories/slp-vue-demo%2Fslp-vue-demo/artifacts?q=tags%3D<tag>"

方案 B:Harbor 复制规则(跨地域推荐)

前提 :两个 Harbor 是独立实例(各自存储),网络互通。

① 在源 Harbor(bj-harbor)上注册目标仓库

复制代码
Harbor UI → 系统管理 → 仓库管理 → 新建目标
  提供者:Harbor
  名称:sz-harbor
  目标 URL:https://sz-harbor.yunwdh.cn
  访问 ID:<sz-harbor 的账号>
  访问密码:<密码>
  验证远程证书:勾选(正式证书时)
  → 测试连接 → 通过后保存

② 创建复制规则

复制代码
Harbor UI → 系统管理 → 复制 → 新建复制规则
  名称:bj-to-sz-prod
  复制模式:Push-based              ← 推送型(源主动推)
  源资源过滤器:
    名称:prod                       ← 只同步 prod 项目
    标签:**                         ← 所有 tag
    资源类型:镜像
  目标仓库:sz-harbor
  触发模式:
    ○ 事件驱动(Event Based)        ← 有 push 动作时自动触发(推荐)
    ○ 定时(Scheduled)              ← 如 cron "0 0 2 * * *"
    ○ 手动(Manual)
  覆盖:✔ 勾选(同 tag 覆盖)

③ 为每个「源项目 × 目标仓库」建一条规则

规则名 源项目 目标
bj-to-cq-test test cq-harbor
bj-to-sz-prod prod sz-harbor

dev 项目不需要复制(dev 集群的 YAML 本来就指向 bj-harbor)。

优点 缺点
跨地域拉取快、各站点自治 需配置与运维;存在同步延迟;需处理同步失败告警

拉取型复制(Pull-based) :反向配置,消费端定时去源端拉。适合"按需拉取"场景,但不保证推送后立即可用 。 本系统是「CI 推完就要部署」,推荐推送型 + 事件驱动。

方案 C:统一镜像地址(根治,需改代码)

把 Pipeline_GitOps_Run.groovy 里的 registryServer 改成与 SedYaml 相同的路由逻辑,使"推到哪儿、YAML 就写哪儿"。

优点 缺点
语义正确、无需复制 ⚠️ 会让 push 变成跨地域上传 (构建节点在 bj,部署在 sz 时要把镜像传到 sz),更慢

结论 :只在构建节点分布在不同区域 时才推荐方案 C。当前架构(统一推 bj + 复制/共享)更优,只是要把它显式化。

12.6 各目标命名空间的拉取凭证(★ 极易遗漏)

template/kustomize/base/deployment.yaml 里有:

复制代码
    spec:
      imagePullSecrets:
        - name: harbor-secret

→ 每一个部署目标命名空间里都必须存在名为 harbor-secret 的 docker-registry Secret ,否则 Pod 会 ImagePullBackOff。

复制代码
kubectl -n <命名空间> create secret docker-registry harbor-secret \
  --docker-server=<该集群对应的 Harbor 域名> \
  --docker-username=<账号> \
  --docker-password=<密码> \
  --docker-email=admin.zcyw@qq.com
集群 命名空间 --docker-server
bj-dev-01 slp-vue-demo bj-harbor.yunwdh.cn
cq-test-01 slp-vue-demo cq-harbor.yunwdh.cn
sz-prod-01 slp-vue-demo sz-harbor.yunwdh.cn
各集群 common-k8s-resource 同集群

⚠️ 命名空间不需要预先创建 (ApplicationSet 里有 CreateNamespace=true,ArgoCD 会自动建), 但 Secret 不会自动创建,必须提前放好或在命名空间创建后立即补。

验证:

复制代码
kubectl -n slp-vue-demo get secret harbor-secret
# 期望:harbor-secret   kubernetes.io/dockerconfigjson   数据 1

更优雅的做法 :把 harbor-secret 纳入 GitOps 管理(放进 common-k8s-resource), 但 Secret 明文入库不安全,需配合 Sealed Secrets / External Secrets / SOPS。

12.7 镜像保留策略建议

环境 建议
dev 只保留最近 10~20 个 tag,节省空间
test 保留最近 30 个
prod 保留全部 (或至少 1 年),并开启不可变 tag(防误删/覆盖)

配置路径:Harbor UI → 项目 → 策略 → 保留策略 / 标签不可变


13. 目标集群 / DNS / 基础镜像

13.1 目标业务集群需要具备什么

检查项 说明 验证命令
集群可达 ArgoCD 能访问其 API Server(6443) curl -k https://<ip>:6443/version
harbor-secret 每个目标命名空间要有 docker-registry Secret kubectl -n <ns> get secret harbor-secret
默认 IngressClass ingress-patch.yaml 没有 ingressClassName 也没有 annotations,完全依赖默认 IngressClass kubectl get ingressclass(找带 (default) 的)
Ingress Controller 需已部署且正常运行 kubectl get svc -A | grep -i ingress
DNS 解析 *-<环境>.yunwdh.cn 必须解析到 Ingress 入口 nslookup slp-vue-demo-prod.yunwdh.cn
StorageClass(如需) 应用若有 PVC 需求 kubectl get sc

⚠️ 实测 cq-test-01 集群的默认 IngressClass 是 nginx ✅(kubectl get ingress -n slp-vue-demo 的 CLASS 列显示 nginx)。

关于 Ingress 的 ADDRESS 为空

裸金属 / NodePort 方式部署的 ingress-nginx,其 Service 是:

复制代码
NAME                       TYPE           EXTERNAL-IP   PORT(S)
ingress-nginx-controller   LoadBalancer   <pending>     80:30327/TCP,443:32323/TCP

EXTERNAL-IP = <pending> 说明没有云厂商 LB ,因此 Ingress 的 ADDRESS 列会空着 ------ 这是正常的,不是故障 。 关键是 Ingress 的 CLASS 列有值(说明控制器已接管)。

实测访问方式(cq-test-01):

复制代码
curl -s -H "Host: slp-vue-demo-test.yunwdh.cn" http://192.168.10.111 | grep app123

说明 192.168.10.111 上有一个统一入口 (前置代理 / VIP)转发到了 ingress。 纯 NodePort 场景则是 <节点IP>:30327。

13.2 DNS 与网络汇总

域名 用途 谁需要访问
gitlab.yunwdh.cn 代码仓库 Jenkins 控制器、Agent Pod 内所有容器
jenkins.yunwdh.cn Jenkins 入口 GitLab(webhook)、jnlp 容器
argocd.yunwdh.cn ArgoCD cd-deploy 容器
bj-harbor.yunwdh.cn Harbor(主) 构建节点的 Docker
cq-harbor.yunwdh.cn Harbor cq-test-01 的 kubelet
sz-harbor.yunwdh.cn Harbor sz-prod-01 的 kubelet
registry.cn-chengdu.aliyuncs.com 阿里云镜像服务 ci-build(拉构建基础镜像)
*-<环境>.yunwdh.cn 业务应用域名 终端用户浏览器
复制代码
# 一次性检查全部
for h in gitlab.yunwdh.cn jenkins.yunwdh.cn argocd.yunwdh.cn \
         bj-harbor.yunwdh.cn cq-harbor.yunwdh.cn sz-harbor.yunwdh.cn; do
  echo -n "$h -> "; nslookup $h | awk '/^Address: /{print $2}' | tail -1
done

业务域名的 DNS 规划

每个接入的应用都会自动产生 <应用名>-<环境>.yunwdh.cn,所以:

方案 说明
推荐:泛解析 配 *.yunwdh.cn(或按环境配 *.test.yunwdh.cn / *.prod.yunwdh.cn)指向对应集群的 ingress 入口,以后新应用接入不用每次找网管
逐条添加 每接一个应用加一条 A 记录,规范但麻烦

⚠️ 不同环境的域名必须指向各自集群的 ingress 入口,不能都指一个集群。

13.3 构建阶段的基础镜像

DockerBuild.groovy 生成的 Dockerfile 依赖这些来自阿里云镜像服务的基础镜像,构建节点必须能拉取(或提前同步到内网):

镜像 用途
registry.cn-chengdu.aliyuncs.com/qiuyl01/node:22.23.1-alpine Node 22 构建阶段
registry.cn-chengdu.aliyuncs.com/qiuyl01/node:23.11.1-alpine Node 23 构建阶段
registry.cn-chengdu.aliyuncs.com/qiuyl01/node:24.16.0-alpine Node 24 构建阶段(也是未知版本的兜底)
registry.cn-chengdu.aliyuncs.com/qiuyl01/nginx:alpine 前端运行阶段
registry.cn-chengdu.aliyuncs.com/qiuyl01/maven:3.8.6-openjdk-8 Java 构建阶段
registry.cn-chengdu.aliyuncs.com/qiuyl01/eclipse-temurin:8-jre Java 运行阶段
registry.cn-chengdu.aliyuncs.com/qiuyl01/busybox:latest Pod 的 init 容器与 ci-build
registry.cn-chengdu.aliyuncs.com/qiuyl01/inbound-agent:3355.v388858a_47b_33-22 Pod 的 jnlp
registry.cn-chengdu.aliyuncs.com/qiuyl01/centos:centos7.9.2009-git Pod 的 cd-deploy

内网环境建议 :把这些镜像一次性 pull + tag + push 到内网 Harbor,再改 DockerBuild.groovy 和 Pod YAML 里的地址(需要改共享库代码)。


14. 部署前置准备检查清单

按「组件」组织,每个组件给出检查项 + 验证命令。建议从上到下逐项完成 ------ 任何一项缺失都会在流水线某个阶段直接失败。

14.1 总览

# 组件 必须完成的事 验证方式
1 K8s 构建集群 jenkins 命名空间、nodejs-package PVC(RWX 20Gi)、节点上预置 Docker / argocd CLI / Node / Maven / Go kubectl -n jenkins get pvc nodejs-package
2 Jenkins 插件齐全、K8s 云配置、6 个凭证、启用安全认证 + 管理员、创建 Job 空跑一次流水线
3 GitLab 三个仓库存在;账号对三者有读写权限;webhook 指向 Jenkins git ls-remote 三个地址
4 Harbor 实例可达、dev/test/prod 三个项目、推送账号、HTTP/insecure-registries docker login + docker push
5 目标集群 harbor-secret(docker-registry 类型)、默认 IngressClass kubectl -n <ns> get secret harbor-secret
6 ArgoCD 安装到 argo-cd、三个集群已添加并打上 cluster-name 标签、能访问 GitLab argocd cluster list
7 DNS / 网络 gitlab / jenkins / argocd / *-harbor .yunwdh.cn 解析;构建节点能访问三者;ArgoCD 能访问目标集群 6443 curl / nslookup

14.2 Kubernetes 构建集群

复制代码
# ① 命名空间
kubectl create namespace jenkins

# ② Node.js 运行时 PVC(★ 必须手动创建,必须 RWX)
kubectl apply -f Pvc-CICD-Base-Build-Env.yaml
kubectl -n jenkins get pvc nodejs-package        # 必须为 Bound

# ③ 宿主机路径(每台可能跑构建的节点)
ls -l /var/run/docker.sock /usr/bin/docker /usr/local/bin/argocd
ls -d /root/.m2 /root/go/pkg

# ④ Node 运行时放入 PVC(目录结构需符合 nvm 规范)
#    /root/.nvm/versions/node/v22.23.1/bin/node

# ⑤ Docker insecure-registries
docker info | grep -A5 "Insecure Registries"

⚠️ hostPath 要求 Pod 被调度到"预置了这些路径的节点" 。 建议 :给这类节点打标签(如 role=jenkins-build),并在 Pod 模板里加 nodeSelector 限定调度范围。

14.3 GitLab

检查项 要求
ops/devops/zcyw-jenkins-shared-library 存在,Jenkins 账号有读权限
slp/gitops 存在,Jenkins 账号有 Developer 及以上(需要 push)
slp/<业务仓库> 存在,Jenkins 账号有读权限
GitOps 仓库的 template/ 10 个模板文件齐全(见 7.1),缺一不可
Webhook (可选)http://jenkins.yunwdh.cn/project/<Job名>
复制代码
git ls-remote http://<用户名>:<密码>@gitlab.yunwdh.cn/ops/devops/zcyw-jenkins-shared-library.git
git ls-remote http://<用户名>:<密码>@gitlab.yunwdh.cn/slp/gitops.git
git ls-remote http://<用户名>:<密码>@gitlab.yunwdh.cn/slp/vue.git

14.4 Harbor

复制代码
# ① 三个域名可达
curl -s http://bj-harbor.yunwdh.cn/v2/     # 期望 401
curl -s http://cq-harbor.yunwdh.cn/v2/
curl -s http://sz-harbor.yunwdh.cn/v2/

# ② dev / test / prod 三个项目已创建(Harbor UI 或 API)

# ③ 推送权限验证
docker login -u admin -p '<密码>' https://bj-harbor.yunwdh.cn
#    期望 Login Succeeded

# ④ 镜像同步方案已确定(共享存储 / 复制规则 / 统一地址)
curl -u admin:<密码> -s "http://bj-harbor.yunwdh.cn/api/v2.0/replication/policies"

14.5 ArgoCD

复制代码
argocd login argocd.yunwdh.cn --username admin --insecure --grpc-web

# 添加集群
argocd cluster add <context> --name bj-dev-01  -y
argocd cluster add <context> --name cq-test-01 -y
argocd cluster add <context> --name sz-prod-01 -y

# 打标签(三选一,见 11.2)
kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster
kubectl -n argo-cd label secret <secret名> cluster-name=<集群名>

# 验证
argocd cluster list
kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster \
  -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels

14.6 目标集群

复制代码
# 每个集群 × 每个将部署的命名空间
kubectl -n slp-vue-demo create secret docker-registry harbor-secret \
  --docker-server=<对应Harbor> --docker-username=<账号> --docker-password=<密码>

kubectl -n slp-vue-demo get secret harbor-secret
kubectl get ingressclass
kubectl get svc -A | grep -i ingress

14.7 一句话自检流程

复制代码
能在 Jenkins 上跑通一次构建(6 个阶段全绿)
    = 构建集群 ✅ + Jenkins 插件/云/凭证 ✅ + GitLab 权限 ✅ + Harbor 推送 ✅
      + GitOps 模板 ✅ + ArgoCD 集群/label ✅

构建成功但 Pod ImagePullBackOff
    = 缺 harbor-secret,或该集群对应的 Harbor 拿不到镜像(见 12.5)

构建成功、Pod Running,但域名访问不通
    = DNS 未解析 / 默认 IngressClass 缺失 / 入口地址不对(见 13.1、17.4)


第四篇 · 运维与排障

15. 首次部署操作步骤(SOP)

每步都有 ✅ 验证点,验证不过不要往下走。

步骤 1 · 准备 GitOps 仓库

复制代码
git clone http://gitlab.yunwdh.cn/slp/gitops.git && cd gitops
ls template/

✅ 验证 :template/ 下 10 个模板文件齐全(见 7.1)。

步骤 2 · 准备 Harbor

复制代码
curl -s http://bj-harbor.yunwdh.cn/v2/     # 期望 401
curl -s http://cq-harbor.yunwdh.cn/v2/
curl -s http://sz-harbor.yunwdh.cn/v2/
docker login -u admin -p '<密码>' https://bj-harbor.yunwdh.cn

✅ 验证 :Login Succeeded;Harbor UI 中能看到 dev / test / prod 三个项目。

步骤 3 · 准备构建集群

复制代码
kubectl -n jenkins get pvc nodejs-package                    # Bound
ls -l /var/run/docker.sock /usr/bin/docker /usr/local/bin/argocd
docker info | grep -A3 "Insecure Registries"

✅ 验证 :PVC 为 Bound;hostPath 全部存在。

步骤 4 · 准备 Jenkins

子步骤 操作
4.1 安装插件(见 10.2)
4.2 配置 Kubernetes 云,名称为 kubernetes ,Jenkins 地址填 http://jenkins.yunwdh.cn
4.3 创建 3 个流水线凭证:gitlab-credentials / harbor-credentials / ArgoCD 凭证
4.4 启用安全认证 + 创建管理员用户
4.5 新建 Pipeline Job,名称 = 项目名(建在根目录)
4.6 Job 配置:Pipeline script from SCM → Git → http://gitlab.yunwdh.cn/slp/vue → 凭证 gitlab-credentials → 分支 */master → 脚本路径 Jenkinsfile
4.7 勾选「不允许并发构建」,丢弃策略设为保留 15 次
4.8 (可选)配置 GitLab webhook

✅ 验证 :系统管理 → 云 测试连接成功。

步骤 5 · 准备 ArgoCD

复制代码
argocd login argocd.yunwdh.cn --username admin --insecure --grpc-web

argocd cluster add <bj-dev-01的context>  --name bj-dev-01
argocd cluster add <cq-test-01的context> --name cq-test-01
argocd cluster add <sz-prod-01的context> --name sz-prod-01

kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster
kubectl -n argo-cd label secret <secret名> cluster-name=<集群名>

argocd cluster list

✅ 验证 :三个集群都出现,label 与 Jenkins 参数中的集群名完全一致。

步骤 6 · 在目标集群创建 harbor-secret

复制代码
kubectl --context=<目标集群> -n slp-vue-demo create secret docker-registry harbor-secret \
  --docker-server=<该集群对应Harbor> --docker-username=<账号> --docker-password=<密码>

命名空间不存在时先 kubectl create namespace slp-vue-demo(或等首次部署由 ArgoCD 自动创建后立即补)。

✅ 验证 :kubectl -n slp-vue-demo get secret harbor-secret。

步骤 7 · 首次构建

复制代码
Jenkins → <Job名> → Build with Parameters
  Git_Branch_Name_Parameter    = master
  Release_Env_Type_Parameter   = <目标环境>
  Dest_Cluster_Name_Parameter  = <目标集群>(只勾一个)

逐阶段观察日志:

阶段 期望日志
PreRunCheck >>>用户: xxx, 管理员权限: true<<<、>>>具有prod环境构建权限<<<
GitClone commitHash: xxxxxxxx,代码获取成功
ImageBuild Login Succeeded → Successfully built → digest: sha256:...
SedYaml >>>部署新服务<<< → xxxx..xxxx master -> master
DeployK8s 'admin:login' logged in successfully → Sync Status: Synced to HEAD (...) → Phase: Succeeded
Post >>>构建成功!<<<

步骤 8 · 验证部署结果

复制代码
# ArgoCD 侧
argocd app list | grep <项目名>
argocd app get <项目>-<环境>-<集群>-<应用>          # Health Status 应为 Healthy

# 集群侧
kubectl -n argo-cd get application,applicationset | grep <项目名>
kubectl -n <项目名> get deploy,pod,svc,ingress
kubectl -n <项目名> get endpoints <应用名>           # ★ 必须不是 <none>
kubectl -n <项目名> describe ingress <应用名>        # 看 Rules 和 Events

# 业务侧
curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Host: <应用>-<环境>.yunwdh.cn" http://<ingress入口IP>
检查项 期望
父 Application Synced / Healthy
子 Application Synced / Healthy
Deployment READY 1/1
Service 存在,且 endpoints 不是 <none>
Ingress 存在,CLASS 有值,Rules 指向正确的 Service:Port
Pod Running,无 ImagePullBackOff
HTTP 200

16. 接入指南:新项目 / 新环境 / 新集群

16.1 新增一个项目

第一步:业务仓库准备文件

文件 必需性
Jenkinsfile ✅ 必需
package.json / pom.xml ✅ 按技术栈
nginx.conf 前端项目必需
Dockerfile 可选(也可用 customDockerfile)

Jenkinsfile 模板:

复制代码
#!groovy
library(
  identifier: "zcyw-jenkins-shared-library@master", retriever: modernSCM([
      $class: "GitSCMSource",
      remote: "http://gitlab.yunwdh.cn/ops/devops/zcyw-jenkins-shared-library.git",
      credentialsId: 'gitlab-credentials'
  ])
)

def map = [:]
map.put('gitUrl',        'http://gitlab.yunwdh.cn/<组>/<业务仓库>')
map.put('gitCredId',     'gitlab-credentials')
map.put('imgRegCredId',  'harbor-credentials')
map.put('buildCmd',      'npm install && npm run build:env_type')   // env_type 会自动替换
map.put('nodeVersion',   '22')
map.put('portNumber',    '80')
map.put('compOutputPath','dist')
Pipeline_GitOps_Run(map)

第二步:在 Jenkins 创建 Job

  • Job 名 = 项目名(会同时成为 K8s 命名空间、GitOps 目录名、ArgoCD Project/父 App 名)

  • 建在根目录,不要分组

  • 配置见 10.1

第三步:确认 Harbor 项目存在

Harbor 项目名 = 环境名(dev / test / prod),通常复用已有项目,无需为每个业务单独建。

第四步:确认 ArgoCD 有目标集群

集群必须已注册且打了 cluster-name 标签(见 11.2)。

第五步:在目标集群准备命名空间与 harbor-secret

复制代码
kubectl create namespace <新项目名>
kubectl -n <新项目名> create secret docker-registry harbor-secret \
  --docker-server=<对应Harbor> --docker-username=<账号> --docker-password=<密码>

命名空间也可以交给 ArgoCD 自动创建(CreateNamespace=true),但 Secret 必须手工补。

第六步:触发首次构建

选 master / 目标环境 / 目标集群。

无需手工写任何 YAML ------ 流水线会走「部署新服务」分支,从 template/ 自动生成:

  • argocd/<项目>/<项目>-<应用>-<环境>-<集群>-applicationset.yaml

  • <项目>/<应用>/base/(Deployment + Service + kustomization)

  • <项目>/<应用>/overlays/<环境>/(kustomization + ingress-patch)

  • <项目>/<应用>/applicationset-config/<集群>/config.json

  • 以及 common-k8s-resource 的对应一套

16.2 新增一个环境(已有项目,加新环境)

前提:该环境对应的集群已在 ArgoCD 中注册并打好标签。

操作:直接用新环境参数触发一次构建即可。

复制代码
Release_Env_Type_Parameter  = <新环境>
Dest_Cluster_Name_Parameter = <新环境对应的集群>

流水线会:

  1. 走「部署新环境」分支 → 生成 overlays/<新环境>/

  2. 生成 applicationset-config/<集群>/config.json

  3. 生成 <项目>-<应用>-<新环境>-<集群>-applicationset.yaml(兜底逻辑保证一定存在)

  4. 同步父 App → ApplicationSet 生效 → 生成子 App → 同步部署

⚠️ 新增的环境名必须在 GetParameters.groovy 的 releaseEnvTypeGroovyScript 里 。 当前环境列表写死了 ["dev","test","prod"],如果要加 sit / uat,必须改共享库代码 ,并把新集群加进 destClusterNameGroovyScript。

16.3 新增一个集群

步骤 操作
1 在 ArgoCD 添加集群:argocd cluster add <context> --name <集群名>
2 给集群 Secret 打标签 :cluster-name: <集群名>
3 在共享库 GetParameters.groovy 的 destClusterNameGroovyScript 里加上这个集群名
4 目标集群创建 harbor-secret(各命名空间)
5 按需配置 Harbor 同步(若集群名以 sz/cq 开头,YAML 会指向对应 Harbor,见 12.5)
6 在 Jenkins 用新集群参数触发一次构建

⚠️ 集群名前缀有语义 :sz* → sz-harbor,cq* → cq-harbor,其他 → bj-harbor。 命名新集群时要注意这一点,否则 YAML 里的镜像地址会和预期不符。


17. 排障手册

17.1 按阶段定位

阶段 报错关键字 原因 处理
全程 Pod 一直 Pending / is offline 构建集群资源不足 / 无匹配节点 / hostPath 不存在 / Jenkins 地址配错 kubectl -n jenkins describe pod <pod> 看 Events;检查 14.2 的路径
全程 No such DSL method 'xxx' Jenkins 缺插件 对照 10.2 装插件
PreRunCheck 权限错误: prod 环境要求可识别的 Jenkins 管理员,但 Jenkins 中未找到用户: anonymous Jenkins 未启用安全认证 启用安全域 + 授权策略,用管理员账号构建(见 10.5)
PreRunCheck 错误: 只允许选择一个目标集群 集群参数勾了多个 只勾一个
PreRunCheck 错误: 未勾选发布的目标集群 没勾集群 勾一个
GitClone Couldn't find any revision to build 分支不存在或凭证无权限 检查分支名与 gitlab-credentials 权限
GitClone Authentication failed 凭证密码过期 更新 Jenkins 凭证
ImageBuild docker login failed + connection refused Harbor 服务不可达 curl http://<harbor>/v2/ 验证;找运维
ImageBuild Login Succeeded 后 denied: requested access to the resource is denied Harbor 项目不存在 / 账号无 push 权限 创建 dev/test/prod 项目;检查账号权限
ImageBuild http: server gave HTTP response to HTTPS client Docker 未配 insecure-registries 配 /etc/docker/daemon.json 后重启 Docker
ImageBuild MissingMethodException: No signature of method ... goBuild Go / gcc / php / 兜底项目的构建方法未实现 改用 dockerFile 或 customDockerfile
ImageBuild COPY failed: file not found ... dist compOutputPath 写错或有 ./ 前缀 改成 dist(不带 ./)
SedYaml sed: no input files 模板里占位符被写死,grep 匹配为空 恢复模板里的占位符(见 7.2 的警告)
SedYaml git push ... 403 gitlab-credentials 对 slp/gitops 无写权限 提升为 Developer 及以上
SedYaml ! [rejected] ... (fetch first) 并发构建导致 GitOps 仓库冲突 Job 勾选「不允许并发构建」
DeployK8s permission denied ArgoCD 凭证权限不足 / app 名查询不到 检查 ArgoCD 账号权限;对照 17.2 的干扰项
DeployK8s application is deleting(重试多次) ArgoCD 里有卡在删除的 Application 见 17.3
DeployK8s FailedPrecondition desc = application is deleting 同上 同上
DeployK8s cluster "https://...:6443" is not accessible 目标集群不可达 curl -k https://<ip>:6443/version
DeployK8s 子 App 一直不生成 集群 label 与参数名不匹配 kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster -o yaml 检查 cluster-name
DeployK8s ComparisonError / kustomize 报错 kustomization.yaml 有非法字段 见 17.5
部署后 Pod ImagePullBackOff 命名空间缺 harbor-secret,或镜像没推到 YAML 指向的 Harbor 建 Secret;或配 Harbor 同步(见 12.5、12.6)
部署后 域名访问 404 / 502 / 503 Ingress 未创建 / 无默认 IngressClass / Service 无 endpoints 见 17.4

17.2 按现象定位(学习用)

现象:Jenkins 日志里出现 PermissionDenied desc = permission denied

先在日志里找这一行:

复制代码
+ argocd app get slp-vue-demo-prod-slp-vue-demo | grep Server | awk '{print $2}'
time="..." level=fatal msg="rpc error: code = PermissionDenied desc = permission denied"

判断方法 :看 argocd app get 后面的 app 名 。 如果名字是 <项目>-<环境>-<应用>(只有三段,缺少集群段 ),那这是已知的残留查询 (共享库已修复,见 18.3), 它不影响构建结果,只是日志噪音 ------ 修好后不会再出现。 如果名字是完整四段,那才是真的权限/app 不存在问题。

现象:构建成功但 ArgoCD 里子是 Unknown 状态

复制代码
application.argoproj.io/slp-vue-demo-test-cq-test-01-slp-vue-demo   Unknown   Healthy
SYNC STATUS 含义
Synced 已同步 ✅
OutOfSync 有差异,等待自动同步或手工 sync
Unknown 还没完成对比 。常见原因:刚创建、集群刚接入、或对比报错(看 argocd app get 的 Conditions)
复制代码
argocd app get <app名>          # 看 Conditions 有没有 ComparisonError

现象:Health Status: Progressing 但构建已成功

这是正常的 ------ 同步成功返回时 Deployment 可能还在滚动更新。

复制代码
argocd app get <app名>                          # 稍后再看是否为 Healthy
kubectl -n <ns> get pod -w                      # 观察 Pod 状态
kubectl -n <ns> describe pod <pod> | tail -30   # 看 Events

17.3 Application 卡在 application is deleting

判断

复制代码
kubectl -n argo-cd get application <名字> \
  -o jsonpath='{.metadata.deletionTimestamp}{"\n"}{.metadata.finalizers}{"\n"}'
# 期望:第一行有时间戳(非空),第二行含 resources-finalizer.argocd.argoproj.io

kubectl -n argo-cd get applications    -o custom-columns=NAME:.metadata.name,DELETING:.metadata.deletionTimestamp
kubectl -n argo-cd get applicationsets -o custom-columns=NAME:.metadata.name,DELETING:.metadata.deletionTimestamp

常见原因

目标集群不可达 → 子 App 无法删除目标集群里的资源 → 整条级联删除链冻结。

级联链长这样:

复制代码
父 Application
  └─ ApplicationSet
       └─ 子 Application(要连目标集群删资源)  ← 卡在这里,上面全冻住

解除(自下而上摘 finalizer)

复制代码
# ① 子 Application
kubectl -n argo-cd patch application <子App名> --type=merge -p '{"metadata":{"finalizers":null}}'
# ② ApplicationSet
kubectl -n argo-cd patch applicationset <名>   --type=merge -p '{"metadata":{"finalizers":null}}'
# ③ 父 Application
kubectl -n argo-cd patch application <父App名> --type=merge -p '{"metadata":{"finalizers":null}}'

⚠️ 摘 finalizer 会跳过资源清理 ,目标集群里的资源变成"孤儿",需另行手工清理。 ⚠️ AppProject 不要删。

验证清空

复制代码
kubectl -n argo-cd get application,applicationset | grep <项目名>   # 期望无输出
kubectl -n argo-cd get appproject <项目名>                          # 期望有一行

实操经验 :摘掉最底层的 finalizer 后,上层会自动 走完删除。所以后面的命令可能报 NotFound ------ 那是好事,说明链条解开了。

预防

措施 说明
集群保持可达 目标集群挂了就必然卡删除
不在部署期间删 Application 删除会触发级联清理,风险高

17.4 域名访问不通

按顺序排查:

复制代码
# ① Ingress 是否存在、CLASS 是否有值(以 test 环境为例)
kubectl -n slp-vue-demo get ingress
kubectl -n slp-vue-demo describe ingress slp-vue-demo

# ② Service 是否有 endpoints(没有就会 503)
kubectl -n slp-vue-demo get endpoints slp-vue-demo

# ③ Pod 是否 Ready
kubectl -n slp-vue-demo get pod

# ④ 集群是否有默认 IngressClass
kubectl get ingressclass

# ⑤ 绕过 DNS,直接用 Host 头打入口(cq-test-01 的入口实测为 192.168.10.111)
curl -s -H "Host: slp-vue-demo-test.yunwdh.cn" http://192.168.10.111
现象 原因 处理
curl 立即返回空 DNS 解析失败 nslookup slp-vue-demo-test.yunwdh.cn;找网管加记录
curl 挂住(需 Ctrl-C) DNS 解析到了不通的地址 curl -v -m 5 slp-vue-demo-test.yunwdh.cn 看 Trying <IP>;修正 DNS
HTTP 404 Ingress 规则没匹配上 检查 Host 拼写;检查 CLASS
HTTP 503 Service 没有 endpoints 检查 Service 的 selector 与 Pod 标签是否一致
HTTP 502 后端端口不对 / 应用未监听 检查 Service targetPort 与容器 containerPort
用 Host 头打 IP 通、域名不通 纯 DNS 问题 加 DNS 记录(推荐泛解析,见 13.2)

实测参考(cq-test-01):

复制代码
kubectl -n slp-vue-demo get svc,pod,ingress
# service/slp-vue-demo   ClusterIP   10.96.3.171   80/TCP
# ingress  CLASS=nginx  HOSTS=slp-vue-demo-test.yunwdh.cn
kubectl -n slp-vue-demo get endpoints slp-vue-demo
# ENDPOINTS  100.102.164.248:80          ← 有值就对了
curl -s -H "Host: slp-vue-demo-test.yunwdh.cn" http://192.168.10.111 | grep app123
# <div id="app123"></div>                ← 成功

17.5 kustomize build / ComparisonError

kustomize 对 kustomization.yaml 启用了 DisallowUnknownFields() ------ 写了不存在的字段会直接解析失败。

常见错误:

复制代码
error: invalid Kustomization: json: unknown field "imagePullSecrets"

原因 :imagePullSecrets 不是 kustomization.yaml 的合法顶层字段,它属于 Deployment 的 spec.template.spec。

复制代码
# ✅ 对:放在 Deployment 里
spec:
  template:
    spec:
      imagePullSecrets:
        - name: harbor-secret
复制代码
# ❌ 错:放在 kustomization.yaml 顶层
kind: Kustomization
namespace: xxx
imagePullSecrets:          # ← 非法字段,会报错
  - name: harbor-secret

其它常见 kustomize 错误:

报错 原因
no such file or directory resources 里写的文件路径不存在
cycle detected overlay 与 base 互相引用
field imageTagNameEnv not found 占位符没被 sed 替换(模板占位符写错或 sed 漏了)

17.6 GitOps 仓库状态异常

复制代码
# 本地与线上比对
git ls-remote http://gitlab.yunwdh.cn/slp/gitops.git refs/heads/master

# 看目录结构
git -C <克隆目录> ls-tree -r --name-only HEAD
现象 说明 处理
每次构建后提交数 +1 完全正常(流水线自动提交) 无需处理
业务目录只剩 template/ 有人执行过 reset 下次构建会走「部署新服务」完整重建
template/ 不见了 严重:脚手架源头丢了 立刻从历史恢复(见下)
同一个应用出现两套 ApplicationSet 旧命名(三段)未清理 删掉旧的那份,避免两个 ApplicationSet 争抢同一 Application

恢复被误删的模板:

复制代码
git log --diff-filter=D --oneline -- template/     # 找到删除模板的提交
git checkout <删除前的提交> -- template/           # 恢复
git commit -m "revert: 恢复 template 目录"
git push

17.7 排查工具箱

复制代码
# ========== Jenkins / 构建集群 ==========
kubectl -n jenkins get pod                                        # Agent Pod 状态(名字形如 slp-vue-demo-47-xxxxx)
kubectl -n jenkins describe pod slp-vue-demo-47-xxxxx             # Pod 启动失败原因
kubectl -n jenkins logs slp-vue-demo-47-xxxxx -c ci-build         # 指定容器日志

# ========== GitOps 仓库 ==========
cd /tmp/gitops && git log --oneline -10
git diff HEAD~1 HEAD                                             # 上一次构建改了什么

# ========== ArgoCD(以 test / cq-test-01 为例)==========
argocd app list | grep slp-vue-demo
argocd app get slp-vue-demo-test-cq-test-01-slp-vue-demo          # Sync/Health、Conditions
argocd app diff slp-vue-demo-test-cq-test-01-slp-vue-demo         # 期望 vs 实际的差异 ★ 很好用
argocd app manifests slp-vue-demo-test-cq-test-01-slp-vue-demo    # 查看将 apply 的清单 ★ 很好用
argocd app history slp-vue-demo-test-cq-test-01-slp-vue-demo      # 部署历史
argocd cluster list                                               # 集群可达性

# ========== 目标集群(以 test / cq-test-01 为例)==========
kubectl -n slp-vue-demo get deploy,pod,svc,ingress,endpoints
kubectl -n slp-vue-demo describe pod slp-vue-demo-7fd9785c8d-k2wws | tail -30
kubectl -n slp-vue-demo logs deploy/slp-vue-demo --tail=50
kubectl -n slp-vue-demo get events --sort-by=.lastTimestamp | tail -20

# ========== Harbor ==========
curl -s http://bj-harbor.yunwdh.cn/v2/                            # 401 = 活着
curl -u admin:'<密码>' -s "http://bj-harbor.yunwdh.cn/api/v2.0/projects/prod/repositories"
docker manifest inspect sz-harbor.yunwdh.cn/prod/slp-vue-demo/slp-vue-demo:202609200731-46faaaa4-v47

18. 已知隐患与改进建议

按严重程度 排序。前 4 条建议优先处理。 标注 ✅ 的表示已修复。

18.1 隐患清单

# 严重度 位置 问题 影响 建议
1 🔴 高 DockerBuild.groovy goBuild / gccBuild / phpBuild / netBuild 完全没有实现,但流水线会调用它们 Go / gcc / php / 兜底项目必然抛 MissingMethodException 补齐方法,或改为报出明确错误;Go 项目暂用 customDockerfile
2 🟠 中 Pod-CICD-Base-Build-Env.yaml ci-build 容器 privileged: true + 挂载 docker.sock + init 容器 chmod 777 容器逃逸风险,等于把宿主机 root 交给构建任务 改用 Kaniko / BuildKit rootless / 独立 DinD;限制触发构建的人员范围
3 🟠 中 Pipeline_GitOps_Run.groovy 隐性依赖 :推送固定 bj-harbor,拉取按集群前缀路由(sz*→sz-harbor、cq*→cq-harbor) 这本身是合理的"中心推送 + 边缘拉取"架构 ,但前提是 bj → cq/sz 存在共享存储或复制规则 ,而该前提在代码/仓库中无任何声明。风险:新增集群静默不同步、prod 跨地域依赖 bj-harbor、排查时容易去错误的 Harbor 找镜像 保留架构 ,但把它显式化:① 确认是共享存储还是复制规则;② 对复制任务加监控告警;③ 新增 sz*/cq* 集群时纳入接入检查清单。仅当在 cq/sz 也部署 Jenkins 构建集群时才需改代码
4 🟠 中 Pipeline_GitOps_Run.groovy:46 awk -F'/' '{print $NR}' 取的是第一段而非最后一段 Job 名带 / 分组时 appNamespace 取错 → 命名空间/目录名全错 改成 awk -F'/' '{print $NF}';或强制 Job 建在根目录(当前做法)
5 🟠 中 GetParameters.groovy 环境列表 dev/release/master/other 四个分支返回完全相同的列表 级联"限制"形同虚设,任何分支都能选 prod 按分支返回不同列表,或明确注释"仅靠权限门控制"
6 🟠 中 GetParameters.groovy Fallback Script 里的集群名(cd-gx-az01-biz-priv-dev-01)与正式脚本(bj-dev-01)完全不一致 正式脚本异常时才暴露,排查困难 统一为一致的集群名
7 🟠 中 GetParameters.groovy Dest_Cluster_Name_Parameter 声明 PT_CHECKBOX(多选),却用报错强制单选 交互与校验矛盾 改为 PT_SINGLE_SELECT
8 🟠 中 SedYaml.groovy 推送段 git push http://${GIT_USERNAME}:${GIT_PASSWORD}@... 把凭证拼进 URL 会出现在进程参数 / git remote 里(Jenkins 会打码但仍是隐患) 改用凭据 helper 或 GIT_ASKPASS
9 🟡 低 Deploy.groovy sonApplicationServer != destCluster 比较的是集群 server URL 与集群名 ,恒为真 判断失效(当前行为恰好是想要的完整流程,无实际故障) 改为对比 URL,或直接去掉该条件
10 🟡 低 gitops applicationset-config/*/config.json 文件被生成,但 ApplicationSet 模板并未引用 冗余;易让人误以为改了它就生效 明确用途(如供外部平台读取)或移除生成逻辑
11 🟡 低 slp/vue: index.html 没有 <script> 标签 去加载 main.js App.vue / main.js 永远不执行,页面空白 补 <script type="module" src="/src/main.js"></script>
12 🟡 低 gitops template/kustomize/overlays/env/ingress-patch.yaml Ingress 没有 ingressClassName 也没有 annotations 依赖集群存在默认 IngressClass;集群没配就访问不通 显式补 ingressClassName: nginx(或多集群场景改用 patches)
13 🟡 低 共享库重试配置 SedYaml retry(8)、GitClone retry(6) 偏多 确定性错误下白等(DeployK8s 已从 10 次降到 2 次 × 10 秒) 按需下调
14 🟡 低 Common.groovy 大量变量(projectName、Release_Env_Type_Parameter、BUILD_USER_ID)依赖 Pipeline 全局绑定隐式传入 Groovy 类 耦合强、IDE 无法静态检查,改错变量名会静默失效 改为显式传参,或集中定义常量类
15 🟡 低 PreRunCheck.groovy allowProdWithoutUserIdentity 开关可绕过 prod 权限管控 误开启后任何人都能发生产 仅在内网确认无需认证时临时使用;启用认证后务必删除该行

18.2 已修复项(记录在案)

项 原问题 修复方式 提交
✅ A common-k8s-resource 的 overlay 模板把 namespace 写死为 common-k8s-resource(而非占位符 serviceNameEnv),导致 grep 空匹配 → sed: no input files,首次往已有项目加新环境必然失败 恢复占位符 serviceNameEnv(替换结果不变,不影响已有环境) gitops 7444ddf
✅ B template/kustomize/overlays/env/kustomization.yaml 未引用 ingress-patch.yaml → Ingress 从未被渲染创建 overlay 的 resources 增加 ingress-patch.yaml gitops 61ed55b
✅ C template/kustomize/base/ 缺少 Service 资源 → Ingress 的后端 Service 不存在 新增 base/service.yaml 并在 base/kustomization.yaml 引用 gitops 61ed55b
✅ D d44880f 曾把 imagePullSecrets 写在 kustomization.yaml 顶层 (非法字段,kustomize 会报 unknown field) 已改到 Deployment 的 spec.template.spec.imagePullSecrets gitops(后续提交)
✅ E Deploy.groovy 查询子 Application 时缺少集群段 ,导致每次构建固定刷出 PermissionDenied 补上 ${params.Dest_Cluster_Name_Parameter} 共享库 8f5ec95
✅ F 权限门在"无法识别构建用户"时打印 null(颜色 map 缺 yellow),用户看不到原因 补 yellow 颜色 + handleUnidentifiedUser() 统一处理并给出解决办法 共享库 04ec55e、d6dd630
✅ G 一个应用只能绑定一个集群(ApplicationSet 不按环境区分),换环境就失败 ApplicationSet 改为按「应用+环境+集群」命名 + 独立的兜底校验 共享库 04ec55e、7b924be
✅ H Agent retries 导致阶段失败时整条流水线重跑、阶段视图出现空列 移除 agent retries,DeployK8s 重试降为 2 次 × 10 秒 共享库 67e98df

18.3 关于「日志里出现 PermissionDenied」

判断方法 :看 argocd app get 后面的 app 名。

app 名形态 结论
<项目>-<环境>-<应用>(三段,缺集群段) 已知的历史残留查询,不影响构建,已修复(见 18.2 E)
<项目>-<环境>-<集群>-<应用>(完整四段) 真的有问题:检查 ArgoCD 账号权限,或该 app 是否正在删除

18.4 建议的处理优先级

优先级 处理项 说明
P0 功能正确性 #1 补齐构建方法、#12 显式 IngressClass #1 影响 Go/php 等项目能否构建;#12 影响域名能否访问
P1 可用性 #4 awk 修正、#5/#6/#7 参数脚本整理 不影响当前运行,但埋着坑
P2 安全加固 #2 Pod 特权降级、#8 凭证不进 URL、#15 权限门 安全相关,建议排期
P3 整洁性 #3 显式化 Harbor 依赖、#9~#11、#13~#14 可读性与可维护性


附录

附录 A · 术语表

术语 全称 / 英文 一句话解释
CI Continuous Integration 持续集成:代码提交后自动构建、测试、产出制品
CD Continuous Delivery / Deployment 持续交付/部署:把制品自动部署到环境
Jenkinsfile --- 放在业务仓库根目录的流水线入口脚本,声明 library(...) + map
共享库 Shared Library 把流水线的公共逻辑抽到独立 Git 仓库,供所有项目复用
vars/ --- 共享库目录,文件名 = Jenkins 全局方法名 (如 vars/MyStep.groovy → MyStep())
src/ --- 共享库目录,普通 Groovy 类,需 new org.devops.XXX() 使用
call(Map) --- 共享库的隐式调用入口,所以能写 Pipeline_GitOps_Run(map)
map --- 业务仓库传给共享库的参数集合,声明"差异"
properties() --- Pipeline 步骤,把参数定义持久化写入 Job 配置
podTemplate / kubernetes --- Jenkins agent 类型:在 K8s 上动态创建 Pod 跑构建
jnlp Jenkins Remoting Agent Pod 里回连 Jenkins 的容器,名称是插件写死的
emptyDir --- Pod 内容器共享的临时卷,Pod 删除即消失
hostPath --- 把宿主机上的文件/目录挂进容器
PVC PersistentVolumeClaim K8s 存储声明,这里用来存放预装的 Node 运行时
RWX ReadWriteMany 多节点同时读写,多节点并发构建的 PVC 必须用它
Kustomize --- K8s 原生的配置定制工具,用 base + overlay 避免 YAML 重复
kustomization.yaml --- Kustomize 的入口文件,会严格校验字段,写错字段直接报错
base --- Kustomize 公共部分(所有环境都用)
overlay --- Kustomize 环境差异部分(引用 base + 写差异)
images 替换器 --- Kustomize 功能:把 base 里的锚点名替换成真实镜像地址 + tag
GitOps --- 用 Git 仓库作为"集群期望状态"的单一事实来源,由工具自动对齐
Application ArgoCD Application 一条部署指令:仓库+路径 → 集群+命名空间
ApplicationSet ArgoCD ApplicationSet Application 生成器,靠集群标签批量生成
AppProject ArgoCD AppProject 权限边界:限定可用的仓库/集群/命名空间
cluster 生成器 clusters generator ApplicationSet 的一种生成器,按集群 Secret 的标签筛选集群
cluster Secret --- ArgoCD 里代表一个被管理集群的 Secret,argocd cluster add 会创建它
selfHeal --- ArgoCD 自动纠偏:集群里被手工改动会被改回 Git 里的状态
prune --- ArgoCD 自动清理:Git 里删掉的资源,同步时也从集群删掉
targetRevision: HEAD --- ArgoCD 跟踪仓库的最新提交
Harbor --- 私有 Docker 镜像仓库;本项目里"项目名 = 环境名"
Robot Account --- Harbor 机器人账号,可限定项目与权限、可轮换
insecure-registries --- Docker 配置项,允许用 HTTP 访问镜像仓库
harbor-secret --- K8s 里 kubernetes.io/dockerconfigjson 类型的 Secret,供 Pod 拉镜像
imagePullSecrets --- Deployment 字段,指定用哪个 Secret 拉私有仓库镜像
Ingress --- K8s 的七层入口规则:域名 + 路径 → Service
IngressClass --- 指定由哪个 Ingress 控制器处理;集群最好配一个默认的
ClusterIP --- Service 类型:只在集群内部可访问
NodePort --- Service 类型:在每个节点上开一个高位端口暴露服务
Endpoints --- Service 背后的真实 Pod 地址列表;为空说明 selector 没匹配上
commit8 --- 业务仓库 commit 哈希的前 8 位,写进镜像 tag 用于溯源

附录 B · 学习路径与动手实验

B.1 建议学习路径(约 1~2 天)

阶段 目标 看哪些章节 产出
第 1 步 建立整体印象 第 1 章(问题背景)→ 第 1.4 节(一张图) 能说出"Jenkins/ArgoCD/GitLab/Harbor 各管什么"
第 2 步 理解五个核心概念 第 2 章 能解释 GitOps、Kustomize、ApplicationSet 分别解决什么问题
第 3 步 跟着一次构建走一遍 第 3 章(对照真实构建日志) 能说出 6 个阶段各做什么
第 4 步 分清"谁在哪配" 第 9 章(配置归属) 知道哪些要手工、哪些是代码生成
第 5 步 读代码 第 6、7、8 章 能定位"改某个行为要去哪个文件"
第 6 步 动手 附录 B.2 实验 亲眼看到流水线的行为

B.2 动手实验(每个实验 5~15 分钟)

建议在 dev / bj-dev-01 环境做,不影响生产。

实验 1 · 观察"改代码 → 新镜像 → 新 Pod"的完整链路

复制代码
# ① 改业务仓库的 index.html(改 title 或 div 内容,肉眼可见)
# ② 提交推送
# ③ Jenkins 触发构建
# ④ 观察镜像 tag 变了(commit 段跟着变)
# ⑤ 观察 ArgoCD 子 App 的 Sync Revision 变了
# ⑥ 观察目标集群的 Pod 被重建

学会:镜像 tag 的三个组成(日期 / commit8 / BUILD_ID)、ArgoCD 如何跟着 GitOps 仓库走。

实验 2 · 用 argocd app diff / manifests 看"期望 vs 实际"

复制代码
argocd app diff <子App名>          # 差异
argocd app manifests <子App名>     # 将 apply 的完整清单(能看到 kustomize 渲染结果)

学会:Kustomize 的 base + overlay 是怎么合成最终 YAML 的。

实验 3 · 验证 selfHeal(自动纠偏)

复制代码
# 在目标集群手工改副本数(ArgoCD 会把 Deployment 改回去)
kubectl -n <ns> scale deploy <应用名> --replicas=3
kubectl -n <ns> get deploy <应用名> -w       # 观察副本数被改回 1

学会:GitOps 的"Git 是唯一事实来源"到底意味着什么。

实验 4 · 验证 CreateNamespace=true

复制代码
kubectl delete ns <某个测试命名空间>          # 删掉命名空间
argocd app sync <子App名>                     # 手工同步
kubectl get ns                                # 命名空间被自动重建

学会 :ApplicationSet 里的 syncOptions: [CreateNamespace=true] 的作用。

实验 5 · 亲手制造一个 kustomize 错误

复制代码
# 在 gitops 仓库某个 overlay 的 kustomization.yaml 顶层加一行:
imagePullSecrets:
  - name: harbor-secret
# 推送,然后在 ArgoCD UI / argocd app get 里看 ComparisonError

学会:kustomize 的严格字段校验;以及"错在 GitOps 仓库,报错在 ArgoCD"的排查路径。

实验 6 · 观察「三种生成场景」的日志差异

操作 会走哪个分支 日志
新建一个项目 + 首次构建 部署新服务 >>>部署新服务<<<
已有项目,换一个新环境构建 部署新环境 >>>部署新环境<<<
已有项目和环境,重新构建 服务更新 >>>已部署过服务或环境,执行服务更新镜像<<<

学会 :为什么改了 template/ 后已有环境不会自动更新。

实验 7 · 本地渲染 Kustomize

复制代码
# 需要本机有 kubectl 或 kustomize
kubectl kustomize /tmp/gitops/slp-vue-demo/slp-vue-demo/overlays/test

学会:ArgoCD 在"对比"阶段到底做了什么。

实验 8 · 读一次完整的构建日志

复制代码
# 从 Jenkins 打开一次成功的构建 → Console Output
# 对照第 3.2 节的时间线表,逐个标记 6 个阶段

学会:日志 → 阶段 → 代码位置的对应关系,这是排障的基本功。


附录 C · Jenkins 凭证与账号速查

地址、仓库、集群、域名等真实值统一见开头的 「本项目实例」 章节,此处不再重复;本附录只列 Jenkins 凭证。

C.1 Jenkins 凭证

凭证 ID 用户名 用途
gitlab-credentials slp 拉代码 + 推 GitOps 仓库 + 加载共享库
harbor-credentials admin Harbor 登录 / 推拉镜像
0ee5a2bf-ba0d-41c7-84af-d07475cb7eb2 admin ArgoCD 操作(代码里的默认值)

代码里三个凭证的兜底 UUID : gitCredId=a3dea81a-e05d-47db-827e-e4effb10cd6f|imgRegCredId=54b99cbd-eb0e-4602-bd72-eb70b9c28454|argocdCerdId=0ee5a2bf-ba0d-41c7-84af-d07475cb7eb2


附录 D · 部署前一页纸检查清单

复制代码
【K8s 构建集群】
[ ]  1. jenkins 命名空间存在
[ ]  2. PVC nodejs-package = Bound(RWX 20Gi)
[ ]  3. 构建节点有 /usr/local/bin/argocd、/usr/bin/docker、/var/run/docker.sock
[ ]  4. 构建节点有 /root/.m2、/root/go/pkg
[ ]  5. 构建节点 Docker 配了 insecure-registries(三个 harbor)

【Jenkins】
[ ]  6. 必需插件齐全(Kubernetes / Git Parameter / Active Choices / AnsiColor /
       Build User Vars / Docker Pipeline / Credentials Binding / Timestamper ...)
[ ]  7. Kubernetes 云名称 = kubernetes,Jenkins 地址 = http://jenkins.yunwdh.cn
[ ]  8. 凭证三个就绪:gitlab-credentials / harbor-credentials / ArgoCD 凭证
[ ]  9. 已启用安全认证(安全域 + 授权策略)
[ ] 10. 有管理员用户
[ ] 11. Job 建在根目录,名称 = 项目名
[ ] 12. Job 的 SCM 指向业务仓库,脚本路径 = Jenkinsfile,分支 = */master
[ ] 13. 勾选「不允许并发构建」

【GitLab】
[ ] 14. 三个仓库存在
[ ] 15. Jenkins 账号对 slp/gitops 有 Developer 及以上权限(需要 push)
[ ] 16. GitOps 仓库 template/ 下 10 个模板文件齐全
[ ] 17. (可选)Webhook 指向 http://jenkins.yunwdh.cn/project/slp-vue-demo

【Harbor】
[ ] 18. 三个 Harbor 域名可达(curl /v2/ 返回 401)
[ ] 19. dev / test / prod 三个项目已创建
[ ] 20. 推送账号有 push 权限(docker login + push 验证通过)
[ ] 21. 镜像同步方案已确定并生效(共享存储 / 复制规则 / 统一地址)

【ArgoCD】
[ ] 22. 安装在 argo-cd 命名空间
[ ] 23. 三个集群已添加
[ ] 24. 集群 Secret 上的 cluster-name 标签与集群名一致
[ ] 25. ArgoCD 能访问 http://gitlab.yunwdh.cn/slp/gitops.git

【目标集群】
[ ] 26. 每个命名空间有 harbor-secret(docker-registry 类型)
[ ] 27. 有默认 IngressClass + Ingress Controller 在跑

【DNS】
[ ] 28. gitlab / jenkins / argocd / {bj,cq,sz}-harbor .yunwdh.cn 全部可解析
[ ] 29. slp-vue-demo-<环境>.yunwdh.cn 解析到对应集群的 ingress 入口

附录 E · 变更操作手册

"我想改 X,该去哪改?"

我想改什么 去哪儿改 生效时机 影响面
编译命令、端口、Node 版本、产物目录 业务仓库 Jenkinsfile 的 map 下次构建 仅该项目
用自带/内联 Dockerfile 业务仓库 Jenkinsfile 加 dockerFile / customDockerfile 下次构建 仅该项目
环境 / 集群的可选项 共享库 GetParameters.groovy 下次构建 所有项目
分支与环境的级联规则 共享库 GetParameters.groovy 下次构建 所有项目
prod 权限门规则 共享库 PreRunCheck.groovy 下次构建 所有项目
镜像怎么打 / 基础镜像版本 共享库 DockerBuild.groovy 下次构建 所有项目
镜像推到哪个 Harbor 共享库 Pipeline_GitOps_Run.groovy 的 registryServer 下次构建 所有项目
新项目生成什么 YAML gitops template/(配合共享库 SedYaml.groovy) 仅对"新服务/新环境"生效;已有环境需直接改生成文件 新接入项目
已存在应用的集群里长什么样 gitops 仓库 里已生成的 base/ / overlays/ ArgoCD 自动同步(3 分钟内) 该应用的该环境
ArgoCD 怎么建/同步应用 共享库 Deploy.groovy 下次构建 所有项目
谁能发生产 / 怎么触发 / Job 名 Jenkins 页面 立即 该项目
集群注册与标签 ArgoCD(argocd cluster add / kubectl label secret) 立即 该集群
Harbor 项目与账号 Harbor 页面 立即 ---
各命名空间的拉取凭证 目标集群 kubectl create secret 立即 该命名空间
业务域名解析 DNS 服务 生效时间看 TTL 该域名

E.1 一条铁律

改 template/ ≠ 已有环境会变。 模板只在「部署新服务」/「部署新环境」两个分支被复制。 要让已有应用生效,必须直接改 gitops 里已生成的文件(这次修 Service / Ingress 就是这么做的)。

E.2 改完怎么验证

改动位置 验证方式
map / Jenkinsfile 触发一次构建,看日志里的 buildCmd is :
共享库 触发一次构建(共享库 @master 每次都会拉最新)
gitops template/ 新建一个测试项目走一次「部署新服务」验证
gitops 已生成文件 argocd app diff <子App名>;或 argocd app manifests <子App名>
Jenkins 页面 立即生效
ArgoCD 集群标签 kubectl -n argo-cd get secret -l argocd.argoproj.io/secret-type=cluster -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels

E.3 回滚怎么做

场景 操作
回滚业务代码 业务仓库 git revert → 重建 → 新镜像 tag
回滚部署配置 gitops 仓库 git revert <提交> → push → ArgoCD 自动同步
快速回滚镜像版本 直接改 gitops 里 overlays/<环境>/kustomization.yaml 的 newTag 为旧 tag → push
回滚 ArgoCD 对象 用 argocd app history <app> 查看历史,或 git revert GitOps 仓库

回滚的优势正是 GitOps 的核心价值 :所有变更都是 Git 提交,git revert 就等于回滚部署。



附录 F · 后续需要完善的功能与存在的不足

集中列出这套流水线还需要完善的功能 、存在的不足 ,以及本文档尚未覆盖的内容,便于后续排期。

F.1 本文档尚未覆盖的内容

# 缺口 说明 建议
1 「一个项目多个应用」的场景 当前 appNamespace = JOB_NAME 的第一段,所以 1 个 Jenkins Job = 1 个项目 = 1 个应用(同时也是 K8s 命名空间) 。若想让一个项目下挂多个应用(如 web + api 共用一个命名空间),需要改共享库(用 map 显式传 projectName)。另外 Job 若建在分组目录下会因 awk 的坑(隐患 #4)取错名字 补充一章"多应用共享项目"的设计方案
2 Jenkins/ArgoCD/Harbor 的从零安装步骤 本文档假定这三个组件已经就绪,只讲了"配置与对接" 单独写一份《基础组件安装手册》
3 构建日志的逐行注释版 第 3.2 节只给了"关键字 → 阶段"的对应,没有把一份完整日志贴出来逐行讲解 附一份成功构建的完整日志 + 行内注释
4 告警与监控方案 完全没有涉及(构建失败通知、Harbor 复制失败告警、ArgoCD 应用不健康告警) 接入 Jenkins 邮件/企微通知 + ArgoCD Notifications + Harbor 复制告警
5 Secret 管理的进阶方案 现在 harbor-secret 靠手工创建,Secret 无法安全入库 引入 Sealed Secrets / External Secrets Operator / SOPS
6 备份与灾备 未涉及 GitLab / Harbor / ArgoCD 的数据备份 明确备份周期与恢复演练
7 多租户与权限细化 未涉及"不同业务组看到不同 Job / 只能发自己的环境" GitLab 组权限 + Jenkins 视图 + ArgoCD Projects 三者配合
8 非前端/Java 技术栈的完整示例 只有 Vue 的端到端例子,Java 只讲了原理 补一个 Java(Maven)的完整示例

F.2 需要完善的功能与改进项

优先级 改进项 对应隐患 价值
高 补齐 goBuild / gccBuild / phpBuild / netBuild,或改为抛出明确错误 #1 让这些技术栈可用 / 报错可读
高 ingress-patch.yaml 显式写 ingressClassName #12 不依赖集群默认 IngressClass,换集群不踩坑
高 把 Harbor 的"中心推送 + 边缘拉取"依赖显式化(文档 + 复制监控 + 接入清单) #3 避免新增集群时静默失败;prod 不隐式依赖 bj-harbor
中 Pod 特权降级(Kaniko / BuildKit rootless / DinD) #2 消除容器逃逸风险
中 Git 推送不再把凭证拼进 URL(改用 GIT_ASKPASS) #8 消除凭证泄露面
中 整理 GetParameters.groovy(环境列表分分支、fallback 统一、改单选) #5 #6 #7 消除误导与矛盾
中 appNamespace 的 awk 改为取最后一段 #4 支持 Job 分组命名
中 补充 DockerBuild 的镜像/版本可配置化(不再硬编码阿里云地址) --- 便于内网化
低 构建失败 / 复制失败 / 应用不健康的告警接入 --- 缩短故障发现时间
低 Harbor 保留策略 + prod 不可变 tag 12.7 节省空间、防误删
低 共享库加静态检查(CI 里跑 groovy lint) #14 减少"改错变量名静默失效"
低 显式化 registryServer 的选择逻辑 #3 代码可读性

文档结束

相关推荐
wewowilling2 小时前
认识微服务
java·运维·微服务·springcloud
玖石书2 小时前
Ubuntu 24.04 UFW 全命令攻略:从入门到端口开放实战
linux·运维·ubuntu
weifont4 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
拂拉氏4 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
阿里云云原生4 小时前
从页面渲染到智能体:ARMS Node.js 探针解锁 SSR + AI 全链路可观测
云原生
Mikko75 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
光依旧5 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
yichengerp6 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦6 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
程序员老赵7 小时前
Docker 部署 Portainer CE:轻松搭建容器管理平台
docker·容器·开源