jenkins中基于Shared Libraries和Active Choices插件的容器化项目回滚方案

公众号:yunops

一、背景描述

二、案例场景:

三、方案概述:

四、操作流程

4.1 准入条件

这些比较基础、网上样例也多,我们不展开详细说明;

4.2 关键配置、代码展示

  1. jenkins 部署主机上添加/data/scripts/getImageTag.sh 脚本,作用是用来加载 golang 程序拉取的镜像版本列表(这里有优化空间,应该没必要串两层:golang->shell->groovy,经测试 grooxy 直接调用 golang 程序获取返回值在 Active Choices 插件不能正常显示,所以就这样写了),脚本内容:
/data/scripts/getImageTag -namespace=demo-service -name=$1
cat /tmp/rollback_tag && truncate -s 0 /tmp/rollback_tag
  1. getImageTag 为 golang 脚本编译生成的可执行程序,代码为:
package main

import (
	"encoding/json"
	"flag"

	//"fmt"
	"io/ioutil"
	"strings"

	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	//"github.com/emre/golist"
)

func main() {
	type Tag struct {
		TagName string `json:"tag"`
	}

	type ImageTag struct {
		TagList []Tag `json:"tags"`
	}

	type ImageData struct {
		TagData ImageTag `json:"data"`
	}

	repoNamespace := flag.String("namespace", "demo-service", "repo namespace")
	repoName := flag.String("name", "demo-microservice001", "repo name")
	flag.Parse()

	client, err := sdk.NewClientWithAccessKey("cn-hangzhou", "<AccessKey ID>", "<AccessKey Secret>")
	if err != nil {
		panic(err)
	}

	request := requests.NewCommonRequest()
	request.Method = "GET"
	request.Scheme = "https" // https | http
	request.Domain = "cr.cn-hangzhou.aliyuncs.com"
	request.Version = "2016-06-07"
	// request.PathPattern = "/repos/demo-service/demo-microservice001/tags"
	request.PathPattern = strings.Join([]string{"/repos", *repoNamespace, *repoName, "tags"}, "/")
	request.Headers["Content-Type"] = "application/json"
	request.QueryParams["Page"] = "1"
	request.QueryParams["PageSize"] = "10"
	body := `{}`
	request.Content = []byte(body)

	response, err := client.ProcessCommonRequest(request)
	if err != nil {
		panic(err)
	}

	imagetags := &ImageData{}
	json.Unmarshal(response.GetHttpContentBytes(), imagetags)

	//my_list := golist.New()
	var tagListStr string = ""
	for _, v := range imagetags.TagData.TagList {
		//my_list.Append(v.TagName)
		tagListStr = strings.Join([]string{tagListStr, v.TagName}, ",")
		//fmt.Println(v.TagName)

	}

	//fmt.Println(tagListStr[1:])
	write_err := ioutil.WriteFile("/tmp/rollback_tag", []byte(tagListStr[1:]), 0644)
	if write_err != nil {
		panic(write_err)
	}
}

  1. 共享库 demo-shared-lib 的 vars 目录中添加 javaRollback.groovy(主要内容为一个接收 map 类型参数的 call 函数)内容如下:
#!groovy
def call(Map map) {
    properties([
        parameters([
            [$class: 'ChoiceParameter',
                choiceType: 'PT_SINGLE_SELECT',
                filterLength: 1,
                filterable: true,
                name: 'servername',
                script: [
                    $class: 'GroovyScript',
                    fallbackScript: [
                        classpath: [],
                        sandbox: false,
                        script:''
                    ],
                    script: [
                        classpath: [],
                        sandbox: false,
                        script:
                            'return["demo-aaa","demo-bbb","demo-ccc","demo-ddd"]'
                    ]
                ]
            ],
            [$class: 'CascadeChoiceParameter',
                choiceType: 'PT_RADIO',
                filterLength: 1,
                filterable: true,
                name: 'tagname',
                referencedParameters: 'servername',
                script: [
                    $class: 'GroovyScript',
                    fallbackScript: [
                        classpath: [],
                        sandbox: false,
                        script:''
                    ],
                    script: [
                        classpath: [],
                        sandbox: false,
                        script:
                            '''
def sout = new StringBuffer()
def serr = new StringBuffer()
def proc = ["/data/scripts/getImageTag.sh", servername].execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
return sout.tokenize(",")
                            '''
                    ]
                ]
            ]
        ])
    ])
// def call(Map map) {
    pipeline {
        agent {
            label 'slave002'
        }

        environment {
            // map变量传递
            namespace="${map.namespace}"

            // 自定义变量
            IMAGE_HUB="registry-vpc.cn-hangzhou.aliyuncs.com/demo-service"
        }

        options {
            timestamps()
            disableConcurrentBuilds()
            timeout(time: 10, unit: 'MINUTES')
            buildDiscarder(logRotator(numToKeepStr: '20'))
            ansiColor('xterm')
        }

        stages {
            stage ("") {
                steps {
                    // echo "${params.tagname}"
                    script {
                        if ("${tagname}") {
                            try {
                                configFileProvider([configFile(fileId: "k8s_auth_docker", targetLocation: "docker.kubeconfig")]){
                                    sh '''
                                        kubectl --kubeconfig docker.kubeconfig -n ${namespace} set image deployment/${servername} ${servername}=${IMAGE_HUB}/${servername}:${tagname}
                                    '''
                                }
                            } catch(exc) {
                                    config.err "应用回退出错!"
                                    throw(exc)
                            }
                        } else {
                            config.err "镜像版本有误!回退失败"
                        }
                    }
                }
            }
        }
        post {
            success {
                script{
                    wrap([$class: 'BuildUser']){
                        buildName "#${BUILD_NUMBER}-${BUILD_USER}"
                        buildDescription "Service:${servername} - Version: ${tagname}"
                    }
                }
            }
        }
    }
}
  1. jenkins 控制台创建 pipeline 类型的 job,因为非生产环境使用了 namespace 来分环境,这里给 map 传一个 namespace 的参数,实现一个环境一个 job 回滚所有任务的功能(当然也可以把这个参数到构建选项中,这样隔离性较差,根据自己的需求可以自行调整),Pipeline script 填入代码如下:
#!groovy
library "demo-shared-lib"
    def map = [:]
    map.put('namespace','demo-qa')
javaRollback(map)
  1. 发布即可,选择服务后,下面会自动加载倒序排列的最新的 10 个可选版本,示例图: jenkins-rollback-config