关于回滚的那些事儿

代码发布到线上,突然就出问题了,一时半会儿还找不到原因,开发看了自己这次提交的代码,思来想去也无法理解出现的故障,这个时候,回滚一定是第一要务,这个平日里不大被人提起的小功能,关键时刻,非常重要,,今天,就聊聊关于回滚的一些方案思路,以及一些疑惑的交流。

本次交流大纲如下:

  • 1,回滚方案及演示
    • 1,脚本灵活定义法
    • 2,Git回滚法
    • 3,tag方案回滚
  • 2,一些疑问交流
    • 1,如何能够基于每次构建,直接rebuild当次代码?
    • 2,回滚的最佳实践有哪些,真诚交流彻底解决!
2赞

参与下周三九点线上分享方式,关注[jenkins]公众号,回复“社区活动”即可进群获取线上分享地址。

@eryajf 欢迎👏来分享!!! 我们会有录屏,届时也会把上传后的视频地址贴在这里。

代码发布到线上,突然就出问题了,一时半会儿还找不到原因,开发看了自己这次提交的代码,思来想去也无法理解出现的故障,这个时候,回滚一定是第一要务,这个平日里不大被人提起的小功能,关键时刻,非常重要,,今天,就聊聊关于回滚的一些方案思路,以及一些疑惑的交流。

本次交流大纲如下:

  • 1,回滚方案及演示
    • 1,脚本灵活定义法
    • 2,Git回滚法
    • 3,tag方案回滚
  • 2,一些疑问交流
    • 1,如何能够基于每次构建,直接rebuild当次代码?
    • 2,回滚的最佳实践有哪些,真诚交流彻底解决!

1,脚本本灵活定义

脚本灵活定义,主要还是借助于Jenkins的参数化构建框架特性,全面采用脚本进行构建与回滚的操作。

这里主要侧重思路的分享,所以找了一个Java的hello world进行验证。

脚本灵活定义方案的明显特性,有如下几个:

  • 优点:
    • 对于需要编译类的项目,回滚速度最快,不必重新编译
  • 缺点:
    • 目前来看,只支持回滚到上一次版本,如果想要回滚之前好几个版本,则不太方便
  • 扩展:
    • 当然也可以添加一个参数,使用build_id作为bak的后缀,回滚的时候输入这个参数即可。这一点是自己曾经梦想着的一个方案,也是打算进行探讨的方案,竟然在做笔记的过程里,思索到了一个。

脚本内容:

#!/bin/bash

##set color##
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
##set color##

source /etc/profile
project="maven-demo"
remote_port="22"
remote_user="root"
remote_ip="192.168.3.65"
remote_dir=/usr/local/$project

if [ $mode == "deploy" ];then
	echoGreen "开始编译代码!"
	cd $WORKSPACE && mvn  install && mv $WORKSPACE/target/gs-maven-0.1.0.jar $WORKSPACE/target/$project.jar
    ssh -p $remote_port $remote_user@$remote_ip "[ ! -d $remote_dir ] && mkdir -p $remote_dir"
    echoGreen "开始备份上次包!"
    ssh -p $remote_port $remote_user@$remote_ip "[ -f $remote_dir/$project.jar.bak ] && rm -f $remote_dir/$project.jar.bak"
    ssh -p $remote_port $remote_user@$remote_ip "[ -f $remote_dir/$project.jar ] && mv $remote_dir/$project.jar $remote_dir/$project.jar.bak"
    scp -P $remote_port $WORKSPACE/target/$project.jar $remote_user@$remote_ip:$remote_dir/$project.jar
    echoYellow "=====调用部署!====="
    ssh -p $remote_port $remote_user@$remote_ip "/usr/local/jdk1.8.0_192/bin/java -jar $remote_dir/$project.jar"
else
    ssh -p $remote_port $remote_user@$remote_ip "[ -f $remote_dir/$project.jar ] && rm -f $remote_dir/$project.jar"
    ssh -p $remote_port $remote_user@$remote_ip "[ -f $remote_dir/$project.jar.bak ] && mv $remote_dir/$project.jar.bak $remote_dir/$project.jar"
    ssh -p $remote_port $remote_user@$remote_ip "/usr/local/jdk1.8.0_192/bin/java -jar $remote_dir/$project.jar"
fi

2,Git方式回滚

Git的回滚其实是最原始的,也最彻底的,比较简单的就是开发同学可以直接回滚代码,然后重新发布即可,但是这种方法在测试环境或许可以,线上生产则不推荐,毕竟将主线代码进行往后回退,容易出现不可预知问题。

不过这里可以使用如下方案,把回滚的工作配置到Jenkins服务器的项目工作目录当中。

核心思路:每次构建时保存本次的commit id,需要回滚的时候,取之前的ID进行回滚即可。

同样创建自由风格项目,利用参数化构建,核心仍旧在脚本:

#!/bin/bash
set -e
source /etc/profile

##set color##
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
##set color##

project="test-h5"
remote_port="22"
remote_user="root"
remote_ip="192.168.3.65"

cd $WORKSPACE

if [ $mode == "deploy" ];then
    git checkout $branch
    [ $? -ne 0 ] && echoRed "Failed to checkout the branch" && exit 1
    git pull
    [ $? -ne 0 ] && echoRed "Failed to pull the branch" && exit 1
    git rev-parse HEAD >> /media/version.log
    rsync -avz --progress -e ssh -p $remote_port --delete $WORKSPACE/* $remote_user@$remote_ip:/usr/local/$project
    [ $? -ne 0 ] && echoRed "Failed to send project" && exit 1
    echoGreen "部署成功!"
else
    if [ $HEAD == "0" ];then
        Head=`tail -n 2 $WORKSPACE/version.log | head -n 1`
        git reset --hard $Head
        rsync -avz --progress -e ssh -p $remote_port --delete $WORKSPACE/* $remote_user@$remote_ip:/usr/local/$project
    else
        git reset --hard $HEAD
        rsync -avz --progress -e ssh -p $remote_port --delete $WORKSPACE/* $remote_user@$remote_ip:/usr/local/$project
    fi
fi

3,tag方案

tag是日常构建中参数化构建的一种,这里是结合了Jenkins构建过程中,可以在 构建后操作当中将当次代码打成tag到gitlab当中,基于这个特性,通过多添加一个参数,实现灵活的回滚方案。

因为这种方案便捷性不是那么好,所以只做思路分享,不详细做实验讲解了。

4,pipeline中的一种回滚思路

pipeline通过代码既配置的方式,让我们可以像开发那样对Jenkins项目进行管理,这里做一个简单的示例,采用了另外一种回滚的思路,来跟大家做一个分享,下边的代码未经过测试,现场配置演示,以及思路讲解。

  • 1

    • walle
      • Remote 1
  • 2

    • walle
      • Remote 2
  • 3

    • walle
      • Remote 3

第四次构建可以直接输入第一次构建的BUILD_ID,直接回滚到第一次构建,这是理想的方案,目前我还没有把方案具体落实,下边是这个方案的初始阶段,各位可以作为参考理解这种思路。

pipeline {
    agent any
    environment {
    	// 定义项目名称方便全局引用
        project     = "test-php"
        // 远程主机地址,这里只演示了一台,如果是多台,可以空格继续写,下边的部署嵌套个for循环即可
        remote_ip   = "192.168.3.65"
        remote_dir  = "/data/www/test-php"
    }
    options {
        timestamps()
        disableConcurrentBuilds()
        timeout(time: 10, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }
    triggers{
        gitlab( triggerOnPush: true,
                triggerOnMergeRequest: true,
                branchFilterType: 'All',
                secretToken: "${env.git_token}")
    }
    parameters {
        choice(name: 'MODE', choices: ['deploy','rollback'], description: '请选择发布或者回滚?')
    }
    stages {
        stage('部署') {
            when {
                environment name: 'MODE',value: 'deploy'
            }
            steps {
                script {
                    try {
                        sh '''
                            ssh -p 22 root@$remote_ip "mkdir -p /media/${project}"
                            rsync -avz -e 'ssh -p 22' --exclude='Jenkinsfile' --delete ${WORKSPACE}/  root@$remote_ip:/media/${project}/${BUILD_ID}
                            ssh -p 22 root@$remote_ip "rm -rf $remote_dir && ln -s /media/${project}/${BUILD_ID} $remote_dir"
                        '''
                    } catch(err) {
                        echo "${err}"
                    }
                }
            }
        }
        stage('回滚') {
            when {
                environment name: 'MODE',value: 'rollback'
            }
            steps {
                script {
                    try {
                        sh '''
                            last_success=$(ssh -p 22 root@$remote_ip "ls -lt /media/${project} | sed -n '3p'" | awk '{print \$9}')
                            ssh -p 22 root@$remote_ip "rm -rf $remote_dir && ln -s /media/${project}/${last_success} $remote_dir"

                        '''
                    } catch(err) {
                        echo "${err}"
                    }
                }
            }
        }
        stage('清理工作空间') {
            steps {
                echo '清理工作空间'
                cleanWs()
            }
        }
    }
    post {
        success {
            dingTalk accessToken:'https://oapi.dingtalk.com/robot/send?access_token=换成自己的',
            imageUrl:'https://ae01.alicdn.com/kf/Hdfe28d2621434a1eb821ac6327b768e79.png',
            jenkinsUrl: "${env.JENKINS_URL}",
            message:'构建成功 ✅',
            notifyPeople:'李启龙'
        }
        failure {
            dingTalk accessToken:'https://oapi.dingtalk.com/robot/send?access_token=换成自己的',
            imageUrl:'https://ae01.alicdn.com/kf/Hdfe28d2621434a1eb821ac6327b768e79.png',
            jenkinsUrl: "${env.JENKINS_URL}",
            message:'构建失败 ❌',
            notifyPeople:'李启龙'
        }
    }
}

2赞

视频已经上传 https://www.bilibili.com/video/av78401814/

1赞