我们组之前的发版流程是这样的:
- 开发本地
mvn package- SCP上传jar到服务器
- SSH登录服务器
kill -9旧进程nohup java -jar启动新版本- 看日志确认启动成功
这个过程有4个致命问题:
| 问题 | 后果 |
|---|---|
| 人工操作 | 忘记一个步骤就出事 |
| kill -9 | 正在处理的请求直接断掉 |
| 无法回滚 | 出问题只能紧急手动回 |
| 没有审批 | 谁都能随时部署到生产 |
因此自动化部署迫在眉睫,从0搭建一套完整的CI/CD流水线:GitLab → Jenkins → Docker → K8s,实现提交代码后自动测试、构建、部署,生产环境一键发版+审批+回滚。
一、CI/CD流水线全景图
CI/CD流水线全流程
开发提交代码 → GitLab
【CI阶段 - 持续集成】
代码拉取
单元测试 + 代码质量检查
Maven构建打包
Docker镜像构建
推送到私有镜像仓库
【CD阶段 - 持续部署】
自动部署到开发环境(dev)
自动部署到测试环境(test)
人工审批 → 部署到预发环境(staging)
人工审批 → 部署到生产环境(prod)
【监控 + 回滚】
自动健康检查
异常自动回滚
正常则标记版本
二、环境准备
| 组件 | 版本 | 用途 |
|---|---|---|
| GitLab | 16.x | 代码仓库 + Webhook触发 |
| Jenkins | 2.426 | CI/CD引擎 |
| Harbor | 2.10 | 私有Docker镜像仓库 |
| K8s | 1.28 | 运行环境 |
Jenkins插件安装
必须安装的插件:
- Pipeline:流水线核心
- Git:拉取代码
- Docker Pipeline:构建镜像
- Kubernetes CLI:部署到K8s
- Pipeline Stage View:流水线可视化
- Generic Webhook Trigger:GitLab触发构建
- Promoted Builds:审批部署
三、Jenkinsfile完整流水线
这是本文最核心的内容,线上用了1年多的流水线模板:
groovy
// Jenkinsfile
pipeline {
agent any
environment {
// 项目配置
APP_NAME = 'easy-platform'
DOCKER_REGISTRY = 'registry.your-company.com'
IMAGE_NAME = "${DOCKER_REGISTRY}/${APP_NAME}"
// Git配置
GIT_REPO = 'git@gitlab.your-company.com:devops/easy-platform.git'
GIT_CREDENTIALS = 'gitlab-ssh-key'
// K8s配置
K8S_CREDENTIALS = 'k8s-config'
K8S_NAMESPACE_DEV = 'easy-platform-dev'
K8S_NAMESPACE_TEST= 'easy-platform-test'
K8S_NAMESPACE_PROD= 'easy-platform'
// 镜像Tag:分支名-构建号-提交哈希前7位
IMAGE_TAG = "${env.BRANCH_NAME ?: 'main'}-${env.BUILD_NUMBER}-${sh(script:'git rev-parse --short HEAD', returnStdout:true).trim()}"
}
tools {
maven 'Maven-3.8'
jdk 'JDK-1.8'
}
stages {
// ========== CI阶段 ==========
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "*/${env.BRANCH_NAME ?: 'main'}"]],
userRemoteConfigs: [[
url: env.GIT_REPO,
credentialsId: env.GIT_CREDENTIALS
]]
])
}
}
stage('代码质量检查') {
steps {
sh 'mvn checkstyle:check -DskipTests'
}
}
stage('单元测试') {
steps {
sh 'mvn test'
}
post {
always {
junit '*/target/surefire-reports/*.xml'
}
}
}
stage('Maven构建') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('构建Docker镜像') {
steps {
script {
docker.withRegistry("https://${env.DOCKER_REGISTRY}", 'harbor-credentials') {
def appImage = docker.build(
"${env.IMAGE_NAME}:${env.IMAGE_TAG}",
'--build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) .'
)
appImage.push()
// 同时打上latest标签
appImage.push('latest')
}
}
}
}
// ========== CD阶段 - 开发环境 ==========
stage('部署-开发环境') {
when {
branch 'develop'
}
steps {
script {
deployToK8s(env.K8S_NAMESPACE_DEV, env.IMAGE_TAG)
}
}
}
// ========== CD阶段 - 测试环境 ==========
stage('部署-测试环境') {
when {
branch 'release/*'
}
steps {
script {
deployToK8s(env.K8S_NAMESPACE_TEST, env.IMAGE_TAG)
}
}
}
// ========== CD阶段 - 预发环境(需审批)==========
stage('审批-预发环境') {
when {
branch 'main'
}
steps {
input message: '确认部署到预发环境?', ok: '确认部署',
submitter: 'tech-lead,devops-lead'
}
}
stage('部署-预发环境') {
when {
branch 'main'
}
steps {
script {
deployToK8s("${env.K8S_NAMESPACE_PROD}-staging", env.IMAGE_TAG)
}
}
}
// ========== CD阶段 - 生产环境(需审批)==========
stage('审批-生产环境') {
when {
branch 'main'
}
steps {
input message: '''确认部署到生产环境?
请确认:
1. 预发环境验证通过
2. 已通知相关团队
3. 回滚方案已确认''',
ok: '确认上线',
submitter: 'tech-lead,cto'
}
}
stage('部署-生产环境') {
when {
branch 'main'
}
steps {
script {
deployToK8s(env.K8S_NAMESPACE_PROD, env.IMAGE_TAG)
}
}
}
// ========== 健康检查 ==========
stage('健康检查') {
when {
anyOf {
branch 'main'
branch 'release/*'
}
}
steps {
script {
def namespace = env.BRANCH_NAME == 'main' ? env.K8S_NAMESPACE_PROD : env.K8S_NAMESPACE_TEST
echo '等待Pod就绪...'
sh """
kubectl rollout status deployment/${env.APP_NAME} \
-n ${namespace} \
--timeout=300s
"""
// 检查应用健康
sh """
sleep 30
kubectl exec deployment/${env.APP_NAME} -n ${namespace} -- \
curl -sf http://localhost:8080/actuator/health || exit 1
"""
}
}
}
}
// ========== 后置处理 ==========
post {
success {
echo '✅ 流水线执行成功!'
sendNotification('SUCCESS')
}
failure {
echo '❌ 流水线执行失败!'
sendNotification('FAILURE')
// 生产环境失败自动回滚
script {
if (env.BRANCH_NAME == 'main') {
echo '生产环境部署失败,自动回滚...'
sh """
kubectl rollout undo deployment/${env.APP_NAME} \
-n ${env.K8S_NAMESPACE_PROD}
"""
}
}
}
always {
// 清理工作空间
cleanWs()
}
}
}
// ========== 工具函数 ==========
def deployToK8s(String namespace, String imageTag) {
sh """
kubectl set image deployment/${env.APP_NAME} \
${env.APP_NAME}=${env.IMAGE_NAME}:${imageTag} \
-n ${namespace} \
--record
kubectl rollout status deployment/${env.APP_NAME} \
-n ${namespace} \
--timeout=180s
"""
}
def sendNotification(String status) {
def color = status == 'SUCCESS' ? '#00CC00' : '#FF0000'
def emoji = status == 'SUCCESS' ? '✅' : '❌'
// 钉钉通知
dingtalk(
robot: 'jenkins-dingtalk',
type: 'MARKDOWN',
title: "${emoji} 构建通知",
text: [
"### ${emoji} 构建通知",
"- **项目**: ${env.APP_NAME}",
"- **分支**: ${env.BRANCH_NAME}",
"- **版本**: ${env.IMAGE_TAG}",
"- **状态**: ${status}",
"- **构建人**: ${env.BUILD_USER ?: '自动触发'}",
"- **耗时**: ${currentBuild.durationString}",
"- [查看详情](${env.BUILD_URL})"
]
)
}
四、GitLab Webhook自动触发
配置Webhook
GitLab → 项目 → Settings → Webhooks:
| 配置项 | 值 |
|---|---|
| URL | http://jenkins.your-company.com/generic-webhook-trigger/invoke?token=easy-platform |
| Trigger - Push | ✅ 勾选 |
| Trigger - Merge Request | ✅ 勾选(merged时触发) |
| Secret Token | your-webhook-secret |
触发规则
| 分支 | 触发条件 | 自动部署到 |
|---|---|---|
develop |
Push/MR合并 | 开发环境 |
release/* |
Push/MR合并 | 测试环境 |
main |
MR合并(保护分支) | 预发 → 审批 → 生产 |
五、多环境配置管理
不同环境使用不同的配置,通过K8s ConfigMap管理:
yaml
# config-dev.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: easy-platform-dev
data:
SPRING_PROFILES_ACTIVE: "development"
JAVA_OPTS: "-Xms256m -Xmx256m"
---
# config-test.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: easy-platform-test
data:
SPRING_PROFILES_ACTIVE: "test"
JAVA_OPTS: "-Xms512m -Xmx512m"
---
# config-prod.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: easy-platform
data:
SPRING_PROFILES_ACTIVE: "product"
JAVA_OPTS: "-Xms1g -Xmx1g -XX:+UseG1GC"
不同环境资源配置对比
| 环境 | 副本数 | 内存限制 | CPU限制 | 数据库 |
|---|---|---|---|---|
| 开发 | 1 | 512Mi | 250m | H2/内嵌MySQL |
| 测试 | 2 | 1Gi | 500m | 独立MySQL实例 |
| 预发 | 2 | 2Gi | 1000m | 与生产同配置 |
| 生产 | 3+ | 2Gi | 1000m | 云RDS + 读写分离 |
六、回滚方案
方案1:K8s回滚(秒级)
bash
# 查看部署历史
kubectl rollout history deployment/easy-platform -n easy-platform
# 回滚到上一版本
kubectl rollout undo deployment/easy-platform -n easy-platform
# 回滚到指定版本
kubectl rollout undo deployment/easy-platform --to-revision=3 -n easy-platform
方案2:重新部署历史镜像
bash
# 查看Harbor镜像历史
# 重新部署指定版本
kubectl set image deployment/easy-platform \
easy-platform=registry.your-company.com/easy-platform:main-156-a3f8b2c \
-n easy-platform
方案3:蓝绿部署
yaml
# blue-green-deployment.yaml
# 同时运行两个版本,切换Service指向
apiVersion: v1
kind: Service
metadata:
name: easy-platform
namespace: easy-platform
spec:
selector:
app: easy-platform
version: blue # 切换为 green 即可完成切换
ports:
- port: 8080
targetPort: 8080
七、流水线踩过的5个坑
坑1:Jenkins构建时Maven下载依赖太慢
修复:配置阿里云Maven镜像 + 本地仓库缓存
groovy
// Jenkinsfile中挂载本地仓库
docker.image('maven:3.8-openjdk-8').inside('-v /data/maven-repo:/root/.m2/repository') {
sh 'mvn clean package -DskipTests'
}
坑2:Docker镜像构建时找不到Dockerfile
修复:Jenkins配置Docker socket挂载
groovy
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: docker
image: docker:24
securityContext:
privileged: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
'''
}
}
}
坑3:K8s部署后Pod启动失败,Jenkins还在等rollout status
修复 :设置合理的 --timeout
bash
kubectl rollout status deployment/easy-platform \
-n easy-platform \
--timeout=300s # 5分钟超时
坑4:流水线敏感信息泄露
修复:Jenkins Credentials管理,不要在Jenkinsfile中硬编码
groovy
// ❌ 错误
K8S_TOKEN = 'eyJhbGciOiJSUzI1NiIs...'
// ✅ 正确
withCredentials([string(credentialsId: 'k8s-token', variable: 'TOKEN')]) {
sh "kubectl --token=${TOKEN} apply -f ..."
}
坑5:生产环境审批人不在,流水线卡住
修复:设置超时 + 自动降级
groovy
stage('审批-生产环境') {
steps {
timeout(time: 2, unit: 'HOURS') {
input message: '确认部署到生产环境?',
submitter: 'tech-lead,cto'
}
}
}
八、发版SOP
1️⃣ 开发完成 → 合并到 develop 分支
→ 自动构建 + 部署到开发环境
2️⃣ 开发自测通过 → 创建 release 分支
→ 自动构建 + 部署到测试环境
3️⃣ QA测试通过 → 合并到 main 分支
→ 自动构建 + 部署到预发环境(需审批)
4️⃣ 预发验证通过 → 发起生产部署(需CTO审批)
→ 滚动更新到生产环境
→ 健康检查 + 监控观察15分钟
5️⃣ 异常 → 自动回滚 / 手动回滚
正常 → 标记版本 + 发送通知
面试速答
面试官:说一下你们项目的CI/CD流程?
答:我们用GitLab + Jenkins + Harbor + K8s搭建了完整的CI/CD流水线。开发提交代码后,通过Webhook自动触发Jenkins构建。CI阶段包括代码质量检查、单元测试、Maven构建打包、Docker镜像构建并推送到Harbor私有仓库。CD阶段按分支策略自动部署:develop分支自动部署到开发环境,release分支自动部署到测试环境,main分支合并后需要技术负责人审批才部署到预发环境,CTO审批后才部署到生产。部署采用K8s滚动更新实现零停机,部署后自动做健康检查,失败则自动回滚到上一版本。每次构建的镜像Tag包含分支名、构建号和Git提交哈希,确保可追溯。