第 3 章 · CI/CD 流水线实战
本章目标:搭建 Gitea → Jenkins → Registry → 集群 企业级流水线;理解多阶段门禁、制品追溯与 staging 自动部署 + prod 人工审批;让 demo-mall 每次 push 可重复、可审计。
学时建议:2.5~3 小时(含 2 小时跟练)
前置:PaaS ch7、ch11;ops-deploy ch02 环境矩阵;git-collab ch03~ch05 分支与 PR。
3.1 场景说明
| 项目 | 内容 |
|---|---|
| 背景 | demo-mall API 代码在 Gitea;团队希望 push 后自动测试、构建镜像、部署 staging |
| 痛点 | 手工 docker build tag 混乱;prod 曾部署 latest 无法追溯 |
| 你的角色 | DevOps,维护 Jenkins Multibranch Pipeline |
| 本章任务 | 完成一次 push 触发全流程;Registry 可见 SHA tag;staging 镜像与构建一致 |
| 约束 | prod 阶段必须 人工 input;HIGH 漏洞 staging warn、prod 阻断(逐步收紧) |
3.2 学完你能
| 能力 | 验收标准 |
|---|---|
| 描述 CI/CD 架构 | 画出 webhook → Jenkins → Registry → kubectl 全链路 |
| 编写/读懂 Jenkinsfile | 解释 Test、Build、Deploy 各 stage |
| 配置质量门禁 | 说出 staging vs prod 四项门禁差异 |
| 分支策略 | feature/develop/release/main 各触发什么 |
| 制品追溯 | 填写一行 build 号 ↔ git SHA ↔ 镜像 tag |
| 排错 | docker push 失败、kubectl 权限、旧镜像三问题定位 |
3.3 目标架构
git push ──► Gitea webhook ──► Jenkins Multibranch Pipeline
│
┌───────────────┼───────────────┐
▼ ▼ ▼
单元测试 镜像构建+扫描 SBOM(可选)
│ │
└───────┬───────┘
▼
Registry (tag=git SHA)
▼
staging 自动部署 ──► 人工审批 ──► prod
| 组件 | Namespace | 小紫 PaaS 商店 |
|---|---|---|
| Gitea | devops | gitea |
| Jenkins | devops | jenkins |
| Registry | devops | docker-registry |
你应该看到:PaaS 商店 中三组件 Running;Jenkins 能访问 Gitea 与 Registry 内网地址。
3.4 逐步跟练 · 第一步:准备仓库与 Webhook(约 30 分钟)
步骤 1 — 确认 demo-api 仓库
- Gitea 中仓库
demo-mall/demo-api - 根目录含
Dockerfile、tests/、下文 Jenkinsfile
步骤 2 — 创建 Multibranch Pipeline
- Jenkins → 新建 Item → Multibranch Pipeline
- Branch Sources:Gitea,扫描
feature/、develop、release/、main
步骤 3 — Gitea Webhook
- 仓库 设置 → Webhook → Push 事件 → Jenkins 回调 URL
你应该看到:Webhook 测试返回 200;Jenkins Scan Repository Now 发现分支。
3.5 逐步跟练 · 第二步:Jenkinsfile 完整示例(约 45 分钟)
将以下 Jenkinsfile 放入仓库根目录,逐 stage 理解注释:
pipeline {
agent { kubernetes { yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: docker
image: docker:24-dind
securityContext: { privileged: true }
volumeMounts: [{ name: dind, mountPath: /var/lib/docker }]
volumes: [{ name: dind, emptyDir: {} }]
''' } }
environment {
REGISTRY = 'registry.devops.svc.cluster.local:5000'
IMAGE = "${REGISTRY}/demo-api"
GIT_SHA = "${env.GIT_COMMIT?.take(7)}"
TAG = "${env.BUILD_NUMBER}-${GIT_SHA}"
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Test') {
steps {
sh 'python -m pytest tests/ -q --junitxml=report.xml'
}
post { always { junit 'report.xml' } }
}
stage('Lint') {
steps { sh 'flake8 app/ || true' } // 逐步改为阻断
}
stage('Build & Push') {
steps {
sh """
docker build -t ${IMAGE}:${TAG} -t ${IMAGE}:latest .
docker push ${IMAGE}:${TAG}
"""
}
}
stage('Scan') {
steps {
sh 'trivy image --severity HIGH,CRITICAL --exit-code 1 ${IMAGE}:${TAG} || true'
}
}
stage('Deploy Staging') {
steps {
sh """
kubectl set image deployment/demo-api \\
demo-api=${IMAGE}:${TAG} -n staging-app
kubectl rollout status deployment/demo-api -n staging-app --timeout=300s
"""
}
}
stage('Deploy Prod') {
when { branch 'main' }
steps {
input message: '发布到生产?', ok: '确认'
sh """
kubectl set image deployment/demo-api \\
demo-api=${IMAGE}:${TAG} -n prod-app
"""
}
}
}
post {
success { echo "Deployed ${TAG}" }