Jenkins2 简介
DSL基于groovy实现,是jenkins核心组件;jenkinsfile -> 声明式流水线,其中jenkinsfile可使用源码版本控制
流水线即代码:脚本式流水线 -> 更高级 -> 声明式流水线(接近web界面构建的流水线)
Blue Ocean,展示流水线并提供点选式日志查看、可视化编辑
jenkins2新的任务类型,需要在初始化向导中选择安装系统推荐的插件
多分支流水线,multibranch pipeline,在有jenkinsfile的项目中,若创建一个新的分支,jenkins将自动基于这个新分支创建一个新任务
jinkens2流水线的特性是能容忍节点的重新启动,这也要求插件中对有状态的对象进行序列化
插件需要提供api供流水线调用
基础知识
脚本式语法
优点:
更少的代码段落和弱规范要求;更强大的程序代码能力;更像编写代码程序;传统的流水线即代码模型,兼容性强;更灵活的自定义代码操作;能构建复杂的工作流
缺点:
更高的编程水平;语法检查受限于groovy;和传统jenkins模型有很大差异;更复杂
声明式语法
优点:
更结构化,贴近传统web表单形式;更强大的声明内容能力,可读性高;可通过blue ocean图形化界面自动生成;段落可映射到常见的jenkins概念;更友好的语法检查和错误识别;能提升流水线的一致性
缺点:
对迭代逻辑支持较弱;部分功能缺乏支持;更难实现自定义流水线代码;对复杂流水线支持较弱
主节点
节点,任何可以执行jenkins任务的系统,包括主节点、代理节点、docker;node用于脚本式流水线
代理节点,(从节点);agent用于声明式流水线
jenkins dsl基于groovy语言实现
节点配置中多个标签以空格分隔;使用标签时,可使用逻辑运算符,如 || ,&&
节点 -> 阶段 -> 步骤step
step语法:
groovy允许跳过参数的圆括号
如果只有一个必选参数,同时只传递一个数值,可省略参数名
如果没有参数,默认参数是script对象, bat([script: 'echo hi']) -> bat 'echo hi'
代码片段生成器
代码片段生成器的内容基于流水线步骤的定义来生成和更新,这些定义来自于插件的支持
实际是由安装了哪些插件决定
提供基于web表单的界面,在其中填入需要的参数,自动生成对应的dsl代码段
git中的两个参数
polling,设置为false时,源码仓库中的变更不会自动检测并重新构建。(需要轮询功能打开)
changelog,设置为false时,不会获取变更记录,在输出中对应changes部分
构建流水线 Build Pipeline,插件,当前公司在使用的方式
stage view中,浅红色代表虽然执行成功,但下游某个阶段出现失败
回放 Replay (不是Rebuild)
不修改原始代码,临时修改进行验证,用于调试
命令行方式时行回放
java -jar ~/jenkins-cli.jar -s http://<jenkins-url> replay-pipeline "<Name>" < Jenkinsfile
1 2 3 4 5 6 7 8 |
node ('docker-1') { stage('Source'){ git 'https://github.com/brentlaster/gradle-greetings.git' } stage('AA'){ sh 'ls -al' } } |
流水线执行流程
触发任务
脚本式流水线,通过properties代码块(该属性会与web界面定义的属性合并处理,且web界面的优先级更高)
声明流水线,通过triggers指令定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 在其他项目后构建 properties([ pipelineTriggers([ upstream( threshold: hudson.model.Result.SUCCESS, upstreamProjects: 'Job1' ) ]) ]) # 在Job1成功后触发 多个任务可使用逗号分隔 指定任务的某个分支使用斜线 Job1/master # 周期性构建 ## 脚本式语法 properties([pipelineTriggers([cron('0 9 * * 1-5')])]) ## 声明式语法 triggers { cron(0 9 * * 1-5) } # 0和7代表周日 H可用于任何字段 根据项目名称计算出散列偏移量以错开相同的执行时间 还可以附加一个范围以指定可选取的时间区间 # 对固定的项目 H的值是确定的 H H(0,7) * * * * H(0-29)/10 * * * * 前半小时每隔10分钟 triggers { pollSCM(H(0,30) * * * *) } # 在每小时的前半个小时某个时间点触发一次SCM扫描 # SCM轮询 # 周期性扫描源码库变更 发现更新时 执行相应动作 properties([pipelineTriggers([pollSCM('*/30 * * * *')])]) triggers{pollSCM(*/30 * * * *)} |
脚本式流水线与参数
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 70 71 72 73 74 75 |
输入 input 'Continue to next stage' input步骤会导致资源被独占,即不会释放executor input message: '<msg text>',ok:'Yes' 替换Proceed标签 参数 布尔型 boolean def answer = input message: '<message>', parameters: [booleanParam (defaultValue:true, description: 'Prerelease setting',name: 'prerelease')] 选项型 choice 列表中的第一个值会作为默认值 使用换行符分隔各选项 def choice = input message: '<message>', parameters:[choice(choices:"choice1\nchoice2\nchoice3\n", description: 'Choose an option',name: 'Options')] 凭证 credential 可选的凭证包括:用户名和密码、docker主机证书验证、ssh用户名及私钥、机密文件、机密文本及证书 # SSH密钥 def creds = input message: '<message>', parameters:[[$class:'CredentialsParameterDefinition', credentialType:'com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey', defaultValue:'jenkins2-ssh',description:'SSH key for access', name:'SSH',required:true]] echo creds # 使用用户名和密码 def creds = input message: '',parameters:[[$class:'CredentialsParameterDefinition', credentialType:'com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl', defaultValue:'',description:'Enter username and password', name:'User And Pass',required:true]] 文件 file 允许用户选择一个文件给流水线使用 返回的是hudson.FilePath对象,FilePath关联的一些方法是jenkins中默认不允许执行,需要管理员流程批准 def selectedFile = input message:'<message>', parameters:[file(description:'Choose file to upload',name:'local')] 列出Subversion标签 list subversion tag def tag = input message:'message', parameters:[[$class:'ListSubversionTagsParameterDefinition', credentialsId:'jenkins2-ssh',defaultValue:'',maxTags:'', name:'LocalSVN',reverseByDate:false,reverseByName:false, tagsDir:'file:///svnrepos/gradle-demo',tagsFilter:'rel_*']] 多行字符串 multiline string 允许用户输入多行文本 def lines=input message:'<message>', parameters:[text(defaultValue:'''line1 line2 line3''',description:'',name:'Input Lines')] 密码 password def pw=input message:'<message>', parameters:[password(defaultValue:'',description:'Enter your password',name:'passwd')] 运行 run 从一个任务中选择一个特定的运行(已经执行过的构建) 常用于测试 默认的运行是最新一次的运行 def selection=input message:'<message>', parameters:[run(description:'Choose a run of the project', filter:'ALL',name:'RUN',projectName:'Oldproject/q')] echo "selection is ${selection}" 字符串 string def resp=input message:'<message>',parameters:[string(defaultValue:'', description:'Enter response',name:'Response')] // 多个输入参数的返回值 没有参数,返回值是null;多个参数,会返回一个map,可通过键获取值 def loginInfo=input message:'Login',parameters:[string(defaultValue:'',description:'Enter Userid',name:'userid'), password(defaultValue:'',description:'Enter Password',name:'passwd')] echo "Username="+loginInfo['userid'] echo "Password=${loginInfo['passwd']}" echo loginInfo.userid+" "+loginInfo.passwd |
声明式流水线与参数
创建本地变量存放input返回值不适用于声明式模型
使用parameters指令
用于声明参数,和agent代码块一起
也可以使用web表单中的this project is parameterized,在pipeline中可以引用这些参数,如params.<参数名称>
1 2 3 4 5 6 7 8 9 10 11 12 13 |
pipeline { agent any parameters{ string(name:'USERID',defaultValue:'',description:'Enter your userid') } stages{ stage('Login'){ steps{ echo "Active use is now ${params.USERID}" } } } } |
另一种方法对脚本式和声明式流水线都适用,在流水线开头把参数定义为属性 properties (和pipeline一个级别)
该方式不推荐在生产环境使用
声明式流水线可使用script代码块,代码块中允许使用非声明式语法,其作用域也仅限于此代码块,即在代码块外不能引用代码块中定义的变量
一个变通的方法是,把返回值存储在一个环境变量中 -> env.RESP1=input xxxx 在代码块外引用 ${env.RESP1}
使用input的一个问题是,若没有输入,会阻塞,这时,应当使用timeout来封闭输入调用
流程控制
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 |
超时 timeout 默认单位是分钟,达到超时时间后,该过程会抛出一个异常,若异常不被处理,流水线将中止 timeout(time:60,unit:'SECONDS'){代码块,该代码块中的过程被设置为超时} ### node { def response stage('input'){ timeout(time:10,unit:'SECONDS'){ response=input message:'User', parameters:[string(defaultValue:'user1', description:'Enter Userid:',name:'userid')] } echo "Username="+response } } --- node { def response stage('input'){ try{ timeout(time:10,unit:'SECONDS'){ response=input message:'User', parameters:[string(defaultValue:'user1', description:'Enter Userid:',name:'userid')] } } catch (err){ response='user1' } } } 重试 retry 当该闭包中有异常发生时,可以重试n次 retry(<n>){//代码过程} 睡眠 sleep 默认单位是秒s sleep time:5,unit:'MINUTES' 等待直到 waitUntil 判定时间间隔 0.25 0.3 0.36 0.43... (1.2倍) waitUntil{//返回true或false的过程} timeout(time:15,unit:'SECONDS'){ waitUntil{ def ret=sh returnStatus:true, script:'test -e /home/jenkins2/marker.txt' return (ret==0) } } --- timeout(time:120,unit:'SECONDS'){ waitUntil{ try{ sh "docker exec ${xxx} curl --silent http://127.0.0.1:8080/info 1>output.txt" return true } catch(exception){ return false } } } |
并发控制
使用lock对资源加锁
阻止多个构建在同一时间试图访问同样的资源
可锁定资源插件 Lockable Resources plugin
lock('worker_node1){//一些步骤}
lock(label:'docker-node',quantity:3){//...} 仅锁定该标签资源的三个实例
使用milestone控制并发
解决资源竞争,一旦一个构建已经通过某个点milestone,就阻止其他构建继续越过该点
后达到的构建在达到milestone时,会自动被取消
注意,如果一个较旧的构建通过了某个milestone,那些还没有通过该milestore的较新的构建不会被中止(启动顺序)
# 示例
sh "'${gradleLoc}/bin/gradle' clean build" }
milestone label:'After build',ordinal:1
stage("NotifyOnFailure"){...}
在多分支流水线中限制并发
限制多分支流水线每次只构建一个分支,排队
1 2 3 4 5 6 |
# 脚本式 properties([disableConcurrentBuilds()]) # 声明式 options{ disableConcurrentBuilds() } |
并行运行任务
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 |
# 传统并行语法 node('worker_node'){ stage('Parallel Demo'){ // 存储步骤的映射 def stepsToRun=[:] for (int i=1;i<5;i++){ stepsToRun["Step${i}"]={node{ echo "start" sleep 5 echo "done" }} } // 开始并行 没有指定节点,这些任务会在任何可用的节点上执行 parallel stepsToRun } } --- stage('Test'){ // 并行执行单元测试 parallel( master:{node('master'){ sh 'gradle -D test.single=TestExample1 test' }}, worker1:{node('worker1'){ sh 'gradle -D test.single=TestExample2 test' }}, ) } # 注意,并行运行时,新节点没有权限访问工作空间,实际上还需要对制品进行归档或复制或其他特殊步骤才可以执行成功 |
stash和unstash
在流水线的节点间或阶段间保存和获取文件 暂存文件以便在节点间共享
stash name:"<name>" [includes:"<pattern>" excludes:"<pattern>"]
unstash "<name>"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
stages{ stage('Source'){ git branch:'test',url:'git@xxx.xxx/xxx' stash name:'test-source',includes:'build.gradle,src/test/' } ... stage('Test'){ parallel( master:{node('master'){ unstash 'test-source' sh 'gradle -D test.single=TestExample1 test' }}, worker1:{node('worker1'){ unstash 'test-source' sh 'gradle -D test.single=TestExample2 test' }}, ) } } |
声明式流水线中新的并行语法
不需要建立映射,同时并行操作的输出也是分开的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
stage('Unit Test'){ parallel{ stage('Unit unit tests'){ agent{label 'worker_node2} steps{ cleanWs() unstash 'ws-src' gbuild4 ':util:test' } } stage('Api unit test1){ agent{label 'worker_node2} steps{ cleanWs() unstash 'ws-src' gbuild4 '-D test.single=TestExample1* :api:test' } } stage('Api unit test2){...} } } |
parallel与fialFast选项
并行的代码块中,任意一个步骤失败,退出所有并行的步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
pipeline{ agent any stages{ stage('Parallel'){ steps{ parallel( 'group1':{ timestamps{ catchError{ sleep 10 echo 'done' } } }, 'group2':{ sleep 5 error 'Error' }, failFast: true ) } } } } |
条件语句
条件性构建步骤插件 Conditional BuildStep plugin
测试某些条件,并基于执行结果执行一个或多个构建步骤
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 |
# 脚本式 node('worker_node1'){ def responses=null stage('selection'){ responses=input message:'Enter branch and select build type', parameters:[string(defaultValue:'',description:'', name:'BRANCH_NAME'),choice(choices:'DEBUG\nRELEASE\nTest', description:'',name:'BUILD_TYPE')] } stage('process'){ if ((responses.BRANCH_NAME=='master')&& (responses.BUILD_TYPE=='RELEASE')){ ... } } } # 声明式 pipeline{ agent any parameters{ string(defaultValue:'',description:'',name:'BRANCH_NAME') choice(choices:'DEBUG\nRELEASE\nTest',description:'',name:'BUILD_TYPE') } stages{ stage('process'){ when{ allOf{ //均为true时 expression{params.BRANCH_NAME=="master"} //返回true或false expression{params.BUILD_TYPE=="RELEASE"} } } steps{ ... } } } } |
构建后动作
脚本式流水线构建后处理
依靠 try-catch-finally 机制
无论构建结果如何,总是执行某些动作
1 2 3 4 5 6 7 8 9 10 11 12 |
def err=null try{ //流水线代码 } catch (err){ currentBuild.result="FAILURE" } finally{ (currentBuild.result != "ABORTED"){ // 如发邮件等 } } |
catchError
探测异常以及改变整个构建状态,还能继续执行流水线进程
若代码块抛出异常,构建会被标记为失败,但流水线从catchError代码块往后的语句可以继续执行
1 2 3 4 5 6 7 8 9 |
node('node1'){ catchError{ stage('xxx'){ ... } } ... // 发送邮件等 } |
声明式流水线构建后处理
post可放在阶段结尾或流水线结尾
1 2 3 4 5 6 7 8 9 10 |
... post{ always{ ... } failure{ echo "Build failed" mail body:'build failed',subject:'Build failed!',to:'admin@xxx.com' } } |
转载请注明:轻风博客 » Jenkins2权威指南1-基础知识与流水线执行流程