本文目录导读:

在Jenkins中实现自动化回滚,通常通过保留多个历史版本的构建产物、动态选择回滚目标以及执行反向部署操作来完成,以下是几种常见的实现方案:
基于版本号回滚(最常用)
Pipeline脚本示例
pipeline {
agent any
parameters {
choice(
name: 'DEPLOY_ACTION',
choices: ['deploy', 'rollback'],
description: '选择部署或回滚'
)
string(
name: 'ROLLBACK_VERSION',
defaultValue: '',
description: '回滚到的版本号(如v1.2.0),留空则回滚到上一版本'
)
}
environment {
// 应用名称
APP_NAME = 'my-app'
// 部署目录(保留多个版本)
DEPLOY_DIR = "/opt/deploy/${APP_NAME}"
// 当前版本链接
CURRENT_LINK = "${DEPLOY_DIR}/current"
}
stages {
stage('Check Parameters') {
when {
expression { params.DEPLOY_ACTION == 'rollback' }
}
steps {
script {
// 检查是否有可回滚的版本
def versions = getAvailableVersions()
if (versions.isEmpty()) {
error "没有可回滚的版本"
}
if (params.ROLLBACK_VERSION && !(params.ROLLBACK_VERSION in versions)) {
error "指定版本 ${params.ROLLBACK_VERSION} 不存在,可用版本: ${versions.join(', ')}"
}
}
}
}
stage('Build Application') {
when {
expression { params.DEPLOY_ACTION == 'deploy' }
}
steps {
script {
// 构建应用,生成带版本号的包
def version = "v${BUILD_NUMBER}"
sh "mvn clean package -DskipTests"
sh "cp target/*.jar ${DEPLOY_DIR}/${version}.jar"
// 更新版本记录
updateVersionRecord(version)
}
}
}
stage('Rollback') {
when {
expression { params.DEPLOY_ACTION == 'rollback' }
}
steps {
script {
def rollbackVersion = params.ROLLBACK_VERSION ?: getPreviousVersion()
echo "正在回滚到版本: ${rollbackVersion}"
// 停止当前应用
sh "systemctl stop ${APP_NAME} || true"
// 备份当前版本(可选)
sh "cp -r ${CURRENT_LINK} ${DEPLOY_DIR}/backup-${BUILD_NUMBER}"
// 切换软链接到目标版本
sh "ln -sfn ${DEPLOY_DIR}/${rollbackVersion}.jar ${CURRENT_LINK}"
// 启动应用
sh "systemctl start ${APP_NAME}"
// 健康检查
sleep 10
sh "curl -f http://localhost:8080/health || error '应用启动失败'"
}
}
}
}
post {
always {
// 清理旧版本(保留最近5个版本)
cleanOldVersions(5)
}
}
}
// 辅助方法
def getAvailableVersions() {
def result = sh(script: "ls ${DEPLOY_DIR}/*.jar | xargs -n1 basename | sed 's/.jar//'", returnStdout: true).trim()
return result ? result.split('\n') : []
}
def getPreviousVersion() {
def versions = getAvailableVersions()
def current = sh(script: "readlink -f ${CURRENT_LINK}", returnStdout: true).trim()
def currentVersion = current.split('/').last().replace('.jar', '')
// 返回当前版本的前一个版本
def index = versions.indexOf(currentVersion)
return index > 0 ? versions[index - 1] : null
}
def updateVersionRecord(version) {
// 将版本信息写入文件或数据库
writeFile file: "${DEPLOY_DIR}/version_history.txt", text: "${version}\n", encoding: 'UTF-8'
}
def cleanOldVersions(keepCount) {
// 清理旧版本JAR包
sh """
cd ${DEPLOY_DIR}
ls -t *.jar | tail -n +${keepCount + 1} | xargs -r rm -f
"""
}
基于Docker镜像回滚
pipeline {
agent any
parameters {
choice(
name: 'ACTION',
choices: ['deploy', 'rollback']
)
string(
name: 'IMAGE_TAG',
defaultValue: '',
description: '回滚到的镜像标签(如v1.2.0)'
)
}
environment {
REGISTRY = 'registry.example.com'
IMAGE_NAME = "${REGISTRY}/my-app"
}
stages {
stage('Build & Push') {
when {
expression { params.ACTION == 'deploy' }
}
steps {
script {
def tag = "v${BUILD_NUMBER}"
docker.build("${IMAGE_NAME}:${tag}")
docker.withRegistry("https://${REGISTRY}", 'docker-credentials') {
docker.image("${IMAGE_NAME}:${tag}").push()
}
}
}
}
stage('Deploy/Rollback') {
steps {
script {
def tag = params.ACTION == 'deploy' ? "v${BUILD_NUMBER}" : params.IMAGE_TAG
// 更新Kubernetes或Docker Compose
sh """
docker pull ${IMAGE_NAME}:${tag}
docker-compose -f /opt/docker-compose.yml up -d --no-deps my-app
"""
}
}
}
}
}
通用回滚函数封装
// 通用回滚函数,可在多个Job中复用
def rollbackToVersion(version, deployDir, appName) {
script {
// 1. 版本验证
def versions = sh(
script: "ls ${deployDir}/*.jar | xargs -n1 basename",
returnStdout: true
).trim().split('\n')
if (!(version in versions)) {
error "版本 ${version} 不存在"
}
// 2. 备份当前运行版本
def current = sh(
script: "readlink -f ${deployDir}/current",
returnStdout: true
).trim()
if (current) {
sh "cp ${current} ${deployDir}/backup-${BUILD_NUMBER}.jar"
}
// 3. 执行回滚
sh """
systemctl stop ${appName} || true
ln -sfn ${deployDir}/${version}.jar ${deployDir}/current
systemctl start ${appName}
"""
// 4. 健康检查
sleep 5
sh "timeout 30 curl -sf http://localhost:8080/actuator/health || error '健康检查失败'"
echo "成功回滚到版本: ${version}"
}
}
// 使用示例
pipeline {
stages {
stage('Rollback') {
steps {
rollbackToVersion(
version: params.ROLLBACK_VERSION,
deployDir: '/opt/deploy/my-app',
appName: 'my-app'
)
}
}
}
}
关键设计原则
-
版本管理:
- 使用构建号或语义化版本号(如
v1.0.0) - 保留至少N个历史版本
- 使用构建号或语义化版本号(如
-
部署策略:
- 推荐使用软链接方式,保留多个版本
- 每次部署创建新目录,更新current链接
-
回滚验证:
- 健康检查(HTTP、进程、端口)
- 数据库迁移回滚(如果涉及)
- 回滚后自动化测试
-
安全性:
- 限制回滚次数
- 保留回滚操作日志
- 生产环境需要审批
高级功能扩展
数据库回滚集成
stage('Database Rollback') {
when {
expression { params.ROLLBACK_DB == true }
}
steps {
script {
// 使用Flyway/Liquibase回滚
sh """
flyway -url=jdbc:postgresql://db:5432/mydb \
-user=admin -password=secret \
-target=${params.DB_VERSION} \
migrate
"""
}
}
}
灰度回滚
stage('Canary Rollback') {
steps {
script {
// 先回滚10%流量
sh "kubectl set image deployment/my-app-canary my-app=${IMAGE_NAME}:${params.ROLLBACK_TAG}"
// 等待观察
input message: 'Canary回滚完成,是否继续?', ok: '继续全量回滚'
// 全量回滚
sh "kubectl set image deployment/my-app my-app=${IMAGE_NAME}:${params.ROLLBACK_TAG}"
}
}
}
最佳实践
- 自动化版本仓库:使用Git标签或数据库记录版本历史
- 一键回滚:提供独立的回滚Job或pipeline参数
- 权限控制:回滚操作需要特定角色审批
- 监控告警:回滚后立即启动监控检查
- 回滚测试:定期演练回滚流程
通过以上方案,你可以根据具体的技术栈(传统部署、Docker、Kubernetes)选择合适的回滚实现方式,生产环境建议参考 “版本保留 + 软链接切换 + 健康检查 + 审批” 的完整方案。