二丫讲梵 二丫讲梵
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

二丫讲梵

行者常至,为者常成
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Nexus系列文章

  • Jenkins系列文章

    • Jenkins入门系列笔记汇总整理
    • 前言与介绍
    • Jenkins初始部署与简单配置
    • Jenkins各配置选项介绍
    • Jenkins中一个项目的构建
    • Jenkins配置项目构建的钉钉通知
    • Jenkins忘记管理员密码怎么办
    • Jenkins与gitlab的交互探微
    • Jenkins根目录详解
    • Jenkins插件之显示构建时间
    • Jenkins插件之批量修改配置
    • Jenkins配置简单构建邮件推送
    • Jenkins复杂邮件推送配置详解
    • Jenkins配置复杂构建邮件推送
    • Jenkins构建安卓项目之前的一些唠叨
    • Jenkins构建安卓项目配置
    • Jenkins与Gitlab分支的交互
    • Jenkins构建nodejs项目
    • 使用docker部署Jenkins及初始配置
    • 配置gitlab提交代码Jenkins自动构建
    • Jenkins回滚方案探微
    • Jenkins角色控制(小黄锁)探微
    • Jenkins构建的应用配置问题解决探微
    • Jenkins构建中tag的应用
    • Jenkins插件之Ansicolor(神器)
    • 最基础核心的Jenkins功能部署一个java应用
    • Jenkins+sonar构建代码扫描
    • Jenkins+docker+gitlab将应用部署到docker
    • Jenkins参数化构建犀利插件Active-Choices-Plugin
    • 记一次将代码中参数外显到构建历史中的操作
    • Jenkins升级与迁移的经验分享
    • pipeline笔记之从一个简单的项目构建开始
    • Jenkinsfile声明式语法详解
    • 自动构建的原始配置以及pipeline中的用法
    • 多分支构建的实践与思考
    • 使用Jenkinsfile类前端项目的部署与回滚
    • 如何在Jenkinsfile中定义一个全局的时间戳变量
    • Jenkins中自由风格回滚方案的最佳实践
    • Jenkins中pipeline风格回滚方案的最佳实践
    • pipeline结合ansible剧本进行批量的部署与回滚配置
    • 最近配置安卓iOS打包本地化流程中一些值得记录的内容
    • pipeline中如何在environment环节声明一个含有通配符的变量
    • git-Parameter插件在pipeline共享库中的实践详解
    • jenkins作为ci检测代码是否合并的实践
    • 将Jenkins共享库的Jenkinsfile放到ci静态检测的实践
    • Jenkins的pipeline实践之GitSCM参数配置项详解
    • Jenkins中pipeline对接CMDB接口获取主机列表的发布实践
      • 1,前言
      • 2,参数
      • 3,渲染列表
      • 4,归纳
      • 5,参考
      • 6,补充
        • 1,参数优化
    • Jenkins有任务无法kill提示即将关闭
    • Jenkins基于Share Library共享库的最佳实践探索
    • Jenkins结合MySql Database插件的平台化实践思路
    • Jenkins-Groovy中三元表达式的用法
    • Jenkins-Groovy中Switch的高阶用法
    • Jenkins-pipeline之利用activity choice插件对接查询MySQL数据实现动态参数化的功能
    • CentOS通过yum快速安装Jenkins
    • Jenkins-pipeline语法之错误处理详解(文末有干货)
    • Jenkins常用插件汇总以及简单介绍
    • Jenkins所遇报错汇总及解决
    • Jenkins管理维护运维规范
  • ELK笔记

  • Kubernetes笔记

  • LLM专题

  • 系列专题
  • Jenkins系列文章
二丫讲梵
2021-11-07
目录

Jenkins中pipeline对接CMDB接口获取主机列表的发布实践

文章发布较早,内容可能过时,阅读注意甄别。

# 1,前言

发布平台统一项目中,二丫 (opens new window)将八百多个 job 全部基于 Jenkins Pipeline 实现,极大简化了项目配置与维护工作,自此之后再没有什么发布上面的难题。

当时宿主机发布的实现,全部基于参数化构建的特性处理集群里的 IP 列表(事实上在这之后,二丫 (opens new window)增减项目也就只是针对这个字段的调整了),结合 ansible 进行发布,现在随着各种基建的完善,打算改进一版,直接对接 CMDB 获取应用的接口拿到应用对应的主机列表来完成发布的实践,从而完成发布平台这最后一点工作量的覆盖,实现全通用,全自动,围绕以 CMDB 为中心的运维基建工作。

开始正题之前,先看一眼之前已经运行半年左右的实践:

这几个参数满足了二丫 (opens new window)在宿主机发布应用的需求,二丫 (opens new window)基于共享库抽取出 Jenkinsfile 引导文件,又从 Jenkinsfile 里抽取出这四个变量,几个都是常规的变量,作用也显而易见,因此不做过多介绍。实现它们的伪代码(实际应用中需要将如下代码中的部分变量进行提取)如下:

parameters {
    string(name: 'BRANCH', defaultValue: 'master', description: '请输入将要构建的代码分支')
    choice(name: 'REMOTE_HOST', choices: 'ALL\n10.3.7.7\n10.3.7.8', description: '请选择发布主机,默认ALL')
    choice(name: 'MODE', choices: ['DEPLOY','ROLLBACK'], description: '请选择发布或者回滚?')
    extendedChoice(description: '回滚版本选择,倒序排序,只保留最近十次版本;如果选择发布则忽略此项', multiSelectDelimiter: ',', name: 'ROLLBACK_VERSION', propertyFile: env.JOB_BASE_NAME, propertyKey: env.JOB_BASE_NAME, quoteValue: false, saveJSONParameterToFile: false, type: 'PT_SINGLE_SELECT', visibleItemCount: 10)
}
1
2
3
4
5
6

单说 REMOTE_HOST,当二丫 (opens new window)把choices: 'ALL\n10.3.7.7\n10.3.7.8'提取成一个参数,那么每个项目的不同主机就可以通过参数传递,然后利用如下代码渲染出 ansible 需要的主机发布列表:

stage('定义部署主机列表'){
    steps{
        script{
            try{
                sh '''
                    OLD=${IFS}
                    IFS='\n'
                        if [ $REMOTE_HOST == "ALL" ];then
                            echo "[remote]" > ${ANSIBLE_HOSTS}
                            for i in ${HOSTS};do echo "$i ansible_port=22" >> ${ANSIBLE_HOSTS};done
                            sed -i '/ALL/d' ${ANSIBLE_HOSTS}
                        else
                            echo "[remote]" > ${ANSIBLE_HOSTS}
                            echo "$REMOTE_HOST ansible_port=34222" >> ${ANSIBLE_HOSTS}
                        fi
                    IFS=${OLD}
                '''
            }catch(exc) {
                env.Reason = "定义主机列表出错"
                throw(exc)
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

那么,现如今想要对接 CMDB 发布服务,则只需要解决两个事情即可:

  1. 参数化构建处通过 CMDB 接口展示主机列表。
  2. 定义部署主机列表步骤改造成通过接口获取的方式渲染。

# 2,参数

当二丫 (opens new window)有了明确的目标以及大概的思路之后,凭借着以往网上冲浪时大概看到过有人通过 groovy 实现通过接口渲染参数的模糊印象,开始了漫长地研究之路。

本文如下代码可能需要你根据报错情况添加插件,具体情况具体分析,这里不多赘述。目前简单记得至少需要如下插件:

  • Active Choices Plugin (opens new window):支持灵活定义的参数化插件。
  • http request plugin (opens new window):支持创建 http 请求的插件。
  • Pipeline Utility Steps (opens new window):提供 pipeline 流水线中一些读写文件之类的操作。

找到以往模糊印象中的文章,确定了这里需要使用Active Choices Plugin,通过一些检索,很快确定了基本的发起 http request 的代码,后边一直困住自己的,是在这个步骤获取项目名字的需求,二丫 (opens new window)需要与 CMDB 一起约定一个东西作为项目的唯一 key,不用说,直接用项目名一定是最好的,这个项目名从 gitlab 仓库中的命名,到 Jenkins 中 job 的命名,到 CMDB 中应用的命名,乃至整个生命周期中,都应该是一致的,唯一的。

接下来直接上干货。

为了模仿 CMDB 接口返回应用对应的数据信息,二丫 (opens new window)这里直接在 Nginx 中添加如下配置:

location /user-api {
    default_type application/json;
    return 200 '{"name":"user-api","host":["ALL","10.0.0.1","10.0.0.2","10.0.0.3","10.0.0.4"]}';
}
1
2
3
4

当二丫 (opens new window)请求服务对应的 /user-api 接口时,将会拿到应用对应的集群主机 IP 列表:

然后在 Jenkins 中,创建一个名叫 user-api 的流水线项目,流水线代码如下:

properties([
  parameters([
    [$class: 'ChoiceParameter', filterLength: 1, filterable: true, randomName: 'choice-parameter-18463792817640626',
    name: 'HOST', choiceType: 'PT_SINGLE_SELECT', description: '选择要部署的主机',
    script:[
        $class: 'GroovyScript',
        fallbackScript: [classpath: [], sandbox: false, script: 'return[\'error\']'],
        script: [ classpath: [], sandbox: false,
        script:'''import hudson.model.*
import groovy.json.JsonSlurperClassic
def getHostList(jobName) {
    def url = new URL("http://10.6.6.66:66/${jobName}")
    def parsedJSON = parseJSON(url.getText())
    return parsedJSON.host
}
def parseJSON(json) {
    return new groovy.json.JsonSlurperClassic().parseText(json)
}
return getHostList(Thread.currentThread().toString().split('job')[-1].replace('/',''))
''']]]])])

pipeline{
    agent any
    stages{
        stage('Example'){
            steps{
                script{
                    sh """echo ${env.HOST}"""
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

关于 active choice 插件的流水线语法,这里不多介绍,可参考官方文档,也可在你的 Jenkins 流水线语法中自行生成理解。

说明:

  • 因为 Jenkins 官方对流水线支持的参数化定义仅支持:booleanParam,choice,text,password,file 等几种常用参数,并不支持 active choice 这类复杂的选项定义,因此这里借助 properties的方法来实例化任一需要的对象,同样,该用法可以在 Jenkins 中的流水线语法参考详细了解。示例如下:

  • 使用代码定义 active choice 参数时需要注意一个坑是:如果你在一个项目中定义多个 active choice 参数,那么请确保不同参数的 randomName是唯一的,否则可能会无法正常使用。

  • 接着进入到 groovy 脚本区域,在写这里的代码时,参考网络上的写法各种各样,先说引包问题。

    同一个方法,有人导包有人不导,后来查了下这块儿:Groovy 提供一些默认的导入。Groovy 默认导入的包有参考 (opens new window):

    import java.lang.*
    import java.util.*
    import java.io.*
    import java.net.*
    import groovy.lang.*
    import groovy.util.*
    import java.math.BigInteger
    import java.math.BigDecimal
    
    1
    2
    3
    4
    5
    6
    7
    8

    但二丫 (opens new window)上边的 import groovy.json.JsonSlurperClassic也可以省略。

  • 接着是借助 URL 模块儿,传递唯一参数应用名,拿到数据,return 出来,当然这里如果用于生产,还应该再好好设计一下,这里暂时按下不表,在后文再详述。

  • 最后困难的是如果在这个阶段拿到项目名,成了困住二丫 (opens new window)的一个地方,二丫 (opens new window)找遍了网上的资料,试了又试,败了又败,人家说:文章不厌百回改,二丫 (opens new window)说,code 不厌百回试。虽然在 active choice 官方文档处有说明提供了两个变量获取 Jenkins 构建的系统信息:

    也见到有人使用这两个变量来获取的,如下:

    import hudson.model.*
    def jobName = this.binding.jenkinsProject.name
    
    1
    2

    但经自己的测试,却始终都没拿到。最后还是在 Stack Overflow 中看到一个答案:

    def jobName = (Thread.currentThread().toString() =~ /job\/(.*?)\//)[0][1]
    
    1

    如果你所有项目都是单层目录的场景,则用上边的方案没有问题,只不过需要注意的是转义符会报语法错误,可多加一层转义处理:

    def jobName = (Thread.currentThread().toString() =~ /job\\/(.*?)\\//)[0][1]
    
    1

    不过因为二丫 (opens new window)测试场景是多目录的情况,发现如上方法拿多层级 job 的时候会有问题,因此个人改造此方法如下:

    def jobName = Thread.currentThread().toString().split('job')[-1].replace('/','')
    
    1

现在来到 Jenkins 当中,在任意目录中创建个项目,命名为 user-api,然后将 Jenkinsfile 文件丢进去,运行一下,发现能够满足需求:

到这里,参数基于 CMDB 接口实时拿主机列表的需求已经满足,接下来就是实现通过接口将数据解析下来的功能了。

# 3,渲染列表

渲染列表也是这次改造工作的重中之重,一开始考虑用 shell 实现,但是觉得接口拿到的 json 经由 shell 处理实在不理想,于是打算用 go 做个脚本,但是丢个 go 的二进制放在流水线中,又违背了操作指令应当显式呈现在定义流程的 code 这一理念,于是暂时放弃 go 的实现方案,打算转用与流水线更亲和的 groovy 来实现这个需求,不必觉得难,事实上在实现如下需求之前,二丫 (opens new window)对 groovy 的编程经验也是零,因此如下的代码实现也会以运维视角来实现,而非纯编程方式,对于将发布流水线写的像编程一个项目那样,二丫 (opens new window)并不喜欢。

但在这里先说一个二丫 (opens new window)后来才知道的事情:在声明式的流水线中,一些场景中对原生的 groovy 代码会有各种各样的奇怪限制,比如二丫 (opens new window)在实验的过程中就遇到过不让用 URL 模块儿,不让用 write 模块儿的,大概报错日志如下:

Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getText java.net.URL
1

如下:

Method definition not expected here. Please define the method at an appropriate place or perhaps try using a block/Closure instead
1

后来才了解到,针对 groovy 原生语法的一些常见用法,事实上 Jenkins pipeline 语法中已经提供了相对应的方法进行支持,比如原生的url方法,在 pipeline 中,应该改用 httpRequest方法,原生的json解析方法,可以改用 readJSON方法,原生的 writer.writeLine方法,可以改用 writeFile方法,凡此种种,不一而足。

所以当二丫 (opens new window)用原生 groovy 在本地调试成功之后,屁颠屁颠跑到 Jenkins 上运行时,发现各个步骤都会卡自己一道,最后不得不用 Jenkins 给定的方法进行了重构改造,不过这里还是放一下 groovy 原生的处理方式,仅当留念自己奋斗的成果:

import groovy.json.JsonSlurperClassic
def getHostList(jobName) {
    def url = new URL("http://10.6.6.66:66/${jobName}")
    def parsedJSON = parseJSON(url.getText())
    return parsedJSON.host
}
def parseJSON(json) {
    return new groovy.json.JsonSlurperClassic().parseText(json)
}

def newFile = new File(${ANSIBLE_HOSTS})
if (!newFile.exists()) {
    newFile.createNewFile()
} else {
    newFile.withWriter('utf-8') { writer ->
        writer.writeLine '[remote]'
    }
    host = getHostList(${env.JOB_BASE_NAME})
    host -= "ALL"
    host.each{
        println(it)
        newFile.append(it + " ansible_port=22\n")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

这段代码在本地直接通过 groovy 解析是没毛病的,放到 Jenkins 中就不行了。

于是再次借助 Jenkins 中流水线语法参考,对各个功能点逐个击破,最后完成脚本内容如下:

properties([
  parameters([
    [$class: 'ChoiceParameter', filterLength: 1, filterable: true, randomName: 'choice-parameter-18463792817640626',
    name: 'HOST', choiceType: 'PT_SINGLE_SELECT', description: '选择要部署的主机',
    script:[
        $class: 'GroovyScript',
        fallbackScript: [classpath: [], sandbox: false, script: 'return[\'error\']'],
        script: [ classpath: [], sandbox: false,
        script:'''import hudson.model.*
def getHostList(jobName) {
    def url = new URL("http://10.6.6.66:66/${jobName}")
    def parsedJSON = parseJSON(url.getText())
    return parsedJSON.host
}
def parseJSON(json) {
    return new groovy.json.JsonSlurperClassic().parseText(json)
}
return getHostList(Thread.currentThread().toString().split('job')[-1].replace('/',''))
''']]]])])

pipeline{
    agent any
    environment {
        ANSIBLE_PORT="22" // 定义远程主机ssh端口,一般不需要更改
        ANSIBLE_USER="root" // 定义远程主机ssh用户,一般不需要更改
        // 定义主机hosts文件,一般不用更改
        ANSIBLE_HOSTS="${WORKSPACE}/${env.JOB_BASE_NAME}_hosts"
    }
    stages{
        stage('定义主机列表'){
            steps{
                script{
                    try {
                        if (HOST == 'ALL') {
                            def response = httpRequest \
                                httpMode: "GET",
                                ignoreSslErrors: true,
                                contentType: 'APPLICATION_JSON',
                                validResponseCodes: '200',
                                // requestBody: groovy.json.JsonOutput.toJson(["k1":"v1","k2":"v2"]),
                                url: "http://10.6.6.66:66/${env.JOB_BASE_NAME}"
                            println response.content
                            def props = readJSON text: response.content
                            props.host -= "ALL"
                            writeFile file: env.ANSIBLE_HOSTS, text: '[remote]\n'
                            props.host.each{
                                appendFile(env.ANSIBLE_HOSTS,it + " ansible_port=${env.ANSIBLE_PORT} ansible_user=${env.ANSIBLE_USER}")
                            }
                        } else {
                            writeFile file: env.ANSIBLE_HOSTS, text: '[remote]\n'
                            appendFile(env.ANSIBLE_HOSTS,HOST + " ansible_port=${env.ANSIBLE_PORT} ansible_user=${env.ANSIBLE_USER}")
                        }
                    }catch(exc) {
                        env.REASON = "定义主机列表出错"
                        throw(exc)
                    }
                }
            }
        }
    }
}

// 该方法实现了往文件内追加内容的功能
def appendFile(String fileFullPath, String line) {
    if (fileExists(fileFullPath)) {
        current = readFile fileFullPath
    }
    writeFile file: fileFullPath, text: current + line + "\n"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

说明:这里不一一介绍代码了,捡一些干货聊聊,有话则长,无话则短。

  • httpRequest在流水线语法中有非常详细的设置项,感兴趣的同学可以去了解查看,这里特地预留了 requestBody,是为了二丫 (opens new window)通过该参数与 CMDB 交互时,能够提供更加灵活的方式,从而让代码更具普适性。
  • readJSON的用法非常简单,让二丫 (opens new window)能够直接解析 json 串。
  • -=是 groovy 语法的一种,能够直接删除掉列表中二丫 (opens new window)不需要的元素。
  • .each是一个遍历方法,它会返回一个 it 对象表示 value。
  • writeFile方法默认是覆盖式写文件,通过一个外挂方法,实现追加功能。

# 4,归纳

经过以上思路的实现之后,二丫 (opens new window)可以来做一些归纳,将一些规范化的东西约定出来,以便于通过同样的配置代码,完成不同的项目发布需求。

  1. 唯一 key 串联

    CMDB 平台建设期间,应用的模型至少应该有如下定义:

    # http://10.6.6.66:66/project?project=test-user-api-runner&env=test
    
    ProjectName  全局唯一
    	ProjectEnv  应用要有环境的属性
    		test
    		pre
    		prod
    	HostList  主机列表,其中主机列表应该通过请求参数进行区分
    
    1
    2
    3
    4
    5
    6
    7
    8

    唯一 key 保障了全局交互的统一,环境标识为应用的不同环境提供不同数据,在 Jenkins 配置发布时,就会有如下三种环境:test-user-api,pre-user-api,prod-user-api。然后将 job 名字拆分,构造如下请求:

    A = 'test-user-api-runner'
    def B = A.tokenize('-')
    def projectEnv = B[0]
    println projectEnv
    B.remove(0)
    def projectName = B.join('-')
    println projectName
    
    1
    2
    3
    4
    5
    6
    7

    通过一些处理从 Jenkins 项目名中拿到环境以及项目名的参数,实现一套代码,发布多套环境的需求。

  2. 保留冗余字段

    除了上边提到的三种环境字段,还应该结合自己的实际保留一定的冗余字段,比如因为经常搞活动而需要扩缩容,这个时候就应该再多个场景expand-user-api,自动扩容过程中,通过运维平台购买下来的主机通过这个字段返回,然后复制一个项目将代码同步到扩容的主机上去。

  3. 提高 CMDB 稳定性

    当 CMDB 的应用接口暴漏给发布接入之后,同时对 CMDB 所在服务的稳定性提高了要求,原来通过单台提供服务的,现在就需要配置成两台高可用起来。

  4. 以 CMDB 为中心

    这一举措彻底将发布中心从原来的手动维护转到以 CMDB 为中心的方案中来,那么如果保障 CMDB 中的数据可持续性稳定,准确,就是需要从购买机器,扩缩容流程,等逐个变动的场景中来细细规划。

  5. 封装一层

    如上策略全局推广之后,所有应用的信息全部维护在 CMDB 中,那么发布就只是一个透明的管道,只需要将应用的参数传递给管道即可触发构建,这个时候,就是适合在 Jenkins 上封装一层,完全集成到运维平台的时候了。有很多公司一上来就要给 Jenkins 封装,最后发现要么无法封装成熟,要么就封装出了个新的 Jenkins。

本文从想法的萌发,到思路的梳理,到逐步的实现,都做了详细介绍以及过程中的心理描写,运维标准化规范化建设之路非常漫长,需要不断地深耕建设,更需要无数这样的点汇聚起来,从而才能形成一片堂堂汪洋。

本文研究完毕之后,我曾在朋友圈发表感慨一段,也摘录与此:

《技术的乐趣》

你在冲破一个新知识点的过程中,难道不充满曲折,困厄,迷惘,反复么!

你知道自己将要达到何方

你知道自己将要面对多少凶险

你几乎靠近最终答案

你经常与正确答案擦肩

你在被自己的怯弱劝退

你在被自己的勇气鼓励

你终于,会收获那最终的正确答案

而这个答案,足够你开心灿烂好久好久

同时摘录了南宋诗人杨万里的一首诗表意:

《桂源铺》- 杨万里

万山不许一溪奔,

拦得溪声日夜喧。

到得前头山脚尽,

堂堂溪水出前村。

# 5,参考

  • How to get the job name on a groovy dynamic parameter? (opens new window)
  • A real example of Jenkins active choices and reactive parameter (opens new window)
  • 实战 Groovy: for each 剖析 (opens new window)
  • 在 Java / Groovy 中将数组转换为字符串 (opens new window)
  • 如何将文本追加到 jenkinsfile 中的文件 (opens new window)
  • Jenkins Pipeline 实现 http 请求并解析响应 (opens new window)
  • Groovy: Method definition not expected here (opens new window)
  • 文件操作 (opens new window)
  • Groovy 教程 (opens new window)

# 6,补充

# 1,参数优化

上边举例中,应用返回的主机列表里还有一个 ALL 字段,这是应用对应的主机列表所不存在的值,通常应用应该只返回主机列表,所以下边对此处做了简单的改造,利用列表的plus参数,将 ALL 字段添加上去。

properties([
  parameters([
    [$class: 'ChoiceParameter', filterLength: 1, filterable: true, randomName: 'choice-parameter-18463792817640626',
    name: 'HOST', choiceType: 'PT_SINGLE_SELECT', description: '选择要部署的主机',
    script:[
        $class: 'GroovyScript',
        fallbackScript: [classpath: [], sandbox: false, script: 'return[\'error\']'],
        script: [ classpath: [], sandbox: false,
        script:'''import hudson.model.*
def getHostList(jobName,jobEnv) {
    def url = new URL("http://10.6.6.66:66/${jobName}/${jobEnv}?token=abcdefg")
    def parsedJSON = parseJSON(url.getText())
    return parsedJSON.host
}
def parseJSON(json) {
    return new groovy.json.JsonSlurperClassic().parseText(json)
}
def getProjetName(name) {
    def B = name.tokenize('-')
    B.remove(0)
    return B.join('-')
}

def jobName = Thread.currentThread().toString().split('job')[-1].replace('/','')
def htmp = ["ALL"]
return htmp.plus(getHostList(getProjetName(jobName),jobName.tokenize('-')[0]))
''']]]])])

pipeline{
    agent any
    environment {
        // 定义主机hosts文件,一般不用更改
        ANSIBLE_HOSTS="${WORKSPACE}/${env.JOB_BASE_NAME}_hosts"
    }
    stages{
        stage('初始化主机列表'){
            steps{
                script{
                    println env.HOST
                }
            }
        }
    }
}

// 该方法实现了往文件内追加内容的功能
def appendFile(String fileFullPath, String line) {
    if (fileExists(fileFullPath)) {
        current = readFile fileFullPath
    }
    writeFile file: fileFullPath, text: current + line + "\n"
}

def getProjetName(String name) {
    def B = name.tokenize('-')
    B.remove(0)
    return B.join('-')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
微信 支付宝
#jenkins#pipeline#cmdb
上次更新: 2024/07/04, 22:40:37
Jenkins的pipeline实践之GitSCM参数配置项详解
Jenkins有任务无法kill提示即将关闭

← Jenkins的pipeline实践之GitSCM参数配置项详解 Jenkins有任务无法kill提示即将关闭→

最近更新
01
学习周刊-总第211期-2025年第20周
05-15
02
记录二五年五一之短暂回归家庭
05-09
03
学习周刊-总第210期-2025年第19周
05-09
更多文章>
Theme by Vdoing | Copyright © 2017-2025 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式