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 46faaaa4slp/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 pushSedYaml 阶段)
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 accessibleapplication 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,解析 Jenkinsfilemap 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 SucceededSuccessfully tagged ...
7 SedYaml 克隆 GitOps 仓库 → 改 YAML → 提交推送 >>>部署新服务<<< / >>>部署新环境<<< / >>>已部署过服务或环境,执行服务更新镜像<<<
8 DeployK8s 登录 ArgoCD → 建 Project/App → 同步 'admin:login' logged in successfullyPhase: 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 的 syncPolicyautomated: {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. buildCmdenv_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
}

按目标集群名前缀选 Harborsz-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.jsonapp.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/apptry_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) CascadeChoiceParameterGroovyScript 后两个参数不显示
AnsiColor ansiColor('xterm') { ... } 彩色日志失效
Build User Vars BUILD_USER_IDBUILD_USERwrap([$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 一直 Pendingis 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/prodpush + 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) 同上 Jenkinsfilelibrary(...) 里引用
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-managerkube-system 命名空间)

  • ClusterRole / ClusterRoleBinding argocd-manager-rolecluster-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.groovydestClusterNameGroovyScript 的取值完全一致 , 否则: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 按集群名前缀:szsz-harborcqcq-harborbj/其他→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 SucceededSuccessfully builtdigest: sha256:...
SedYaml >>>部署新服务<<<xxxx..xxxx master -> master
DeployK8s 'admin:login' logged in successfullySync 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.groovyreleaseEnvTypeGroovyScript 。 当前环境列表写死了 ["dev","test","prod"],如果要加 sit / uat必须改共享库代码 ,并把新集群加进 destClusterNameGroovyScript

16.3 新增一个集群

步骤 操作
1 在 ArgoCD 添加集群:argocd cluster add <context> --name <集群名>
2 给集群 Secret 打标签cluster-name: <集群名>
3 在共享库 GetParameters.groovydestClusterNameGroovyScript 里加上这个集群名
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 Succeededdenied: 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 / 兜底项目的构建方法未实现 改用 dockerFilecustomDockerfile
ImageBuild COPY failed: file not found ... dist compOutputPath 写错或有 ./ 前缀 改成 dist(不带 ./
SedYaml sed: no input files 模板里占位符被写死,grep 匹配为空 恢复模板里的占位符(见 7.2 的警告)
SedYaml git push ... 403 gitlab-credentialsslp/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.cnTrying <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 大量变量(projectNameRelease_Env_Type_ParameterBUILD_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() 统一处理并给出解决办法 共享库 04ec55ed6dd630
✅ G 一个应用只能绑定一个集群(ApplicationSet 不按环境区分),换环境就失败 ApplicationSet 改为按「应用+环境+集群」命名 + 独立的兜底校验 共享库 04ec55e7b924be
✅ H Agent retries 导致阶段失败时整条流水线重跑、阶段视图出现空列 移除 agent retriesDeployK8s 重试降为 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.groovyMyStep()
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 操作(代码里的默认值)

代码里三个凭证的兜底 UUIDgitCredId=a3dea81a-e05d-47db-827e-e4effb10cd6fimgRegCredId=54b99cbd-eb0e-4602-bd72-eb70b9c28454argocdCerdId=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 版本、产物目录 业务仓库 Jenkinsfilemap 下次构建 仅该项目
用自带/内联 Dockerfile 业务仓库 JenkinsfiledockerFile / customDockerfile 下次构建 仅该项目
环境 / 集群的可选项 共享库 GetParameters.groovy 下次构建 所有项目
分支与环境的级联规则 共享库 GetParameters.groovy 下次构建 所有项目
prod 权限门规则 共享库 PreRunCheck.groovy 下次构建 所有项目
镜像怎么打 / 基础镜像版本 共享库 DockerBuild.groovy 下次构建 所有项目
镜像推到哪个 Harbor 共享库 Pipeline_GitOps_Run.groovyregistryServer 下次构建 所有项目
新项目生成什么 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.yamlnewTag 为旧 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 消除误导与矛盾
appNamespaceawk 改为取最后一段 #4 支持 Job 分组命名
补充 DockerBuild 的镜像/版本可配置化(不再硬编码阿里云地址) --- 便于内网化
构建失败 / 复制失败 / 应用不健康的告警接入 --- 缩短故障发现时间
Harbor 保留策略 + prod 不可变 tag 12.7 节省空间、防误删
共享库加静态检查(CI 里跑 groovy lint) #14 减少"改错变量名静默失效"
显式化 registryServer 的选择逻辑 #3 代码可读性

文档结束

相关推荐
金融小师妹2 小时前
AI金融能力评估:18个主流模型金融问题平均错误率达57%
人工智能·云计算·逻辑回归·深度优先
whyutianict_vv2 小时前
云计算运维培训课程技术栈拆解:从 Linux 网络到 AI 推理的五层能力分层
linux·云计算
ChaITSimpleLove2 小时前
云原生性能对决:JDK21虚拟线程+GraalVM vs .NET10/11 技术栈深度对比
微服务·云原生·serverless·graalvm native·native aot·runtime async·.net11/java21
pt10432 小时前
面向AI的Cisco UCS X-Series设计-2:Cisco UCS AI规格选型
运维·人工智能
元岳数字人小元3 小时前
无惧网络受限:数字人私有化部署打造离线全域服务能力
运维·人工智能·开源·人机交互·交互
2601_962063233 小时前
从使用场景覆盖到品牌长期建设的内容解读
运维
此冬歌咏3 小时前
K8s etcd 备份与恢复实战:误删命名空间后 37 秒恢复,5 个对象 UID 完全一致
运维
牢姐与蒯3 小时前
Linux进程间通信(三).基于匿名管道的进程池的实现
linux·运维·服务器·ubuntu
景煜拾年3 小时前
RHCA-Docker知识点整理
运维·笔记