Jenkins脚本如何实现自动化回滚

wen 实用脚本 4

本文目录导读:

Jenkins脚本如何实现自动化回滚

  1. 基于版本号回滚(最常用)
  2. 基于Docker镜像回滚
  3. 通用回滚函数封装
  4. 关键设计原则
  5. 高级功能扩展
  6. 最佳实践

在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'
                )
            }
        }
    }
}

关键设计原则

  1. 版本管理

    • 使用构建号或语义化版本号(如 v1.0.0
    • 保留至少N个历史版本
  2. 部署策略

    • 推荐使用软链接方式,保留多个版本
    • 每次部署创建新目录,更新current链接
  3. 回滚验证

    • 健康检查(HTTP、进程、端口)
    • 数据库迁移回滚(如果涉及)
    • 回滚后自动化测试
  4. 安全性

    • 限制回滚次数
    • 保留回滚操作日志
    • 生产环境需要审批

高级功能扩展

数据库回滚集成

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}"
        }
    }
}

最佳实践

  1. 自动化版本仓库:使用Git标签或数据库记录版本历史
  2. 一键回滚:提供独立的回滚Job或pipeline参数
  3. 权限控制:回滚操作需要特定角色审批
  4. 监控告警:回滚后立即启动监控检查
  5. 回滚测试:定期演练回滚流程

通过以上方案,你可以根据具体的技术栈(传统部署、Docker、Kubernetes)选择合适的回滚实现方式,生产环境建议参考 “版本保留 + 软链接切换 + 健康检查 + 审批” 的完整方案。

抱歉,评论功能暂时关闭!