Jenkins pipeline腳本編寫實踐分享(一)上篇
持續集成的實踐應用已經在很多公司里推行,其中主流的持續集成工具就是Jenkins,作為過去兩年多來一直致力於公司的持續集成部署的我,回身看到踩過的不少坑,感慨不已,所以決定將這些做個記錄。
剛開始在公司部署Jenkins服務,使用的是插件流的方式部署,部署一個job要關聯到十幾個插件(插件流的方式就不在這裡贅述了,網上也有很多資料),兩個月後我把部署方式改成了Jenkins推薦的pipeline腳本的方式,這也是響應Jenkins2.0的精髓Pipeline as Code。
Jenkins的pipeline有Declarative Pipeline(在Pipeline 2.5中引入,結構化方式)和Scripted Pipeline兩種方式編寫,我選擇的是Declarative Pipeline的寫法,入門簡單。
好了話不多說,直接上實例:
#!/usr/bin/env groovy
pipeline {
//確認使用主機/節點機
agent any /*{
node { label master}
}*/
// 聲明參數
parameters{
//SVN代碼路徑
string(name:repoUrl, defaultValue: http://svn.com/svn/server/job, description: SVN代碼路徑)
// 部署內容的相對路徑
string(name:deployLocation, defaultValue: target/*.jar,target/alternateLocation/*.*,+target/classes/*.*,target/classes/i18n/*.*,target/classes/rawSQL/*.*,+target/classes/rawSQL/mapper/*.*,target/classes/rawSQL/mysql/*.*,+target/classes/rawSQL/sqlserver/*.*, description: 部署內容的相對路徑 )
//伺服器參數採用了組合方式,避免多次選擇
string(name:dev_server, defaultValue: IP,Port,Name,Passwd, description: 開發伺服器(IP,Port,Name,Passwd))
string(name:ZHtest_server, defaultValue: IP,Port,Name,Passwd, description: 中文測試伺服器(IP,Port,Name,Passwd))
string(name:alT19_server, defaultValue: IP,Port,Name,Passwd, description: 生產伺服器T1(IP,Port,Name,Passwd))
string(name:alT20_server, defaultValue: IP,Port,Name,Passwd, description: 生產伺服器T2(IP,Port,Name,Passwd))
}
// 聲明使用的工具
tools {
maven maven
jdk jdk1.8
}
//常量參數,初始確定後一般不需更改
environment{
// SVN服務全系統只讀賬號cred_id【參數值對外隱藏】
CRED_ID=CRED_ID
//項目經理郵箱地址
PM_EMAIL=PM
// Jenkins負責人
JM_EMAIL=QA
//測試人員郵箱地址【參數值對外隱藏】
TEST_EMAIL=Tester
}
triggers {
pollSCM(H/5 * * * 1-5)
}
//pipeline運行結果通知給觸發者
post{
//執行後清理workspace
always{
echo "clear workspace......"
deleteDir()
}
failure{
script {
emailext body: ${JELLY_SCRIPT,template="static-analysis"},
recipientProviders: [[$class: RequesterRecipientProvider],[$class: DevelopersRecipientProvider]],
subject: ${JOB_NAME}- Build # ${BUILD_NUMBER} - Failure!
}
}
}
stages {
stage(清理本地倉庫) {
steps{
sh "/home/jenkins/del_lastUpdated.sh"
}
}
stage(Checkout) {
steps {
script {
//從SVN拉取代碼
def scmVars = checkout ([$class: SubversionSCM,
additionalCredentials: [],
excludedCommitMessages: ,
excludedRegions: ,
excludedRevprop: ,
excludedUsers: ,
filterChangelog: false,
ignoreDirPropChanges: false,
includedRegions: ,
locations: [[credentialsId: CRED_ID,
depthOption: infinity,
ignoreExternalsOption: true,
local: .,
remote: params.repoUrl]],
workspaceUpdater: [$class: UpdateUpdater]])
svnversion = scmVars.SVN_REVISION
}
//sh "echo ${svnversion}"
}
}
// 編譯構建代碼
stage(構建) {
steps{
// maven構建
sh "mvn -Dmaven.test.failure.ignore clean install"
}
}
stage(靜態檢查) {
steps {
echo "starting codeAnalyze with SonarQube......"
//sonar:sonar.QualityGate should pass
withSonarQubeEnv(Sonar-6.4) {
//固定使用項目根目錄${basedir}下的pom.xml進行代碼檢查
//sh "mvn -f pom.xml clean compile sonar:sonar"
sh "mvn sonar:sonar "+
"-Dsonar.sourceEncoding=UTF-8 "//+
//"-Dsonar.language=java,groovy,xml"+
//"-Dsonar.projectVersion=${v} "+
//"-Dsonar.projectKey=${JOB_NAME} "+
//"-Dsonar.projectName=${JOB_NAME}"
}
script {
// 未通過代碼檢查,中斷
timeout(10) {
//利用sonar webhook功能通知pipeline代碼檢測結果,未通過質量閾,pipeline將會fail
def qg = waitForQualityGate()
if (qg.status != OK) {
error "未通過Sonarqube的代碼質量閾檢查,請及時修改!failure: ${qg.status}"
}
}
}
}
}
stage(歸檔) {
steps {
// 歸檔文件
/*archiveArtifacts artifacts: target/*.jar,target/alternateLocation/*.*,+target/classes/*.*,target/classes/i18n/*.*,target/classes/rawSQL/*.*,+target/classes/rawSQL/mapper/*.*,target/classes/rawSQL/mysql/*.*,+target/classes/rawSQL/sqlserver/*.*,fingerprint: true*/
archiveArtifacts params.deployLocation
}
}
stage(部署到開發環境 ) {
steps {
//根據param.server分割獲取參數,包括IP,jettyPort,username,password
script {
def dev_split=params.dev_server.split(",")
dev_serverIP=dev_split[0]
dev_serverPort=dev_split[1]
dev_serverName=dev_split[2]
dev_serverPasswd=dev_split[3]
}
echo Deploying to dev_server
//清理清理舊程序
sh "/home/jenkins/del_158_client.sh bas"
// 部署到開發環境
sh "scp -r target/*.jar ${dev_serverName}@${dev_serverIP}:/jenkins/datacenter/bas/"
sh "scp -r target/alternateLocation ${dev_serverName}@${dev_serverIP}:/jenkins/datacenter/bas/"
sh "rsync -av target/classes/ --exclude=com ${dev_serverName}@${dev_serverIP}:/jenkins/datacenter/bas/"
// 重啟服務
sh "/home/jenkins/kill_158_client.sh bas-job bas"
}
}
stage(開發環境介面自動化測試) {
agent{
label Slave_Linux_69_2
}
steps{
sh "sleep 60s"
echo "starting interfaceTest......"
/*echo 節點是: ${env.NODE_NAME}
echo 節點是: ${env.NODE_LABELS}
echo ${currentBuild}
echo ${env}
echo " 當前BuildId: ${env.BUILD_ID}"*/
dir(/home/jenkins/pm_test)
{
sh (source /etc/profile;newman -c APD201test_bas.postman_collection.json)
}
}
}
stage(對當前版本代碼打tag) {
steps{
timeout(5) {
script {
input message: 需要打tag嘛?
}
}
//sh "echo ${params.repoUrl}"
//sh "echo ${svnversion}"
sh "/home/jenkins/del_crea_tag.sh bas-job ${params.repoUrl} ${svnversion}"
}
}
stage(確認是否部署到測試環境) {
steps{
timeout(5) {
script {
mail to: "${JM_EMAIL} ${PM_EMAIL}",
subject: "PineLine ${JOB_NAME} (${BUILD_NUMBER})人工驗收通知",
body: "提交的PineLine ${JOB_NAME} (${BUILD_NUMBER})進入人工驗收環節
請及時前往${env.BUILD_URL}進行測試驗收"
input message:部署到測試環境?//,submitter:"${PM_EMAIL}"
// 中文環境
def ZHtest_split=params.ZHtest_server.split(",")
ZHtest_serverIP=ZHtest_split[0]
ZHtest_serverPort=ZHtest_split[1]
ZHtest_serverName=ZHtest_split[2]
ZHtest_serverPasswd=ZHtest_split[3]
}
}
//中文測試環境
//清理清理舊程序
sh "/home/jenkins/del_32_client.sh bas"
// 部署到中文測試環境
sh "scp -r target/*.jar ${ZHtest_serverName}@${ZHtest_serverIP}:/jenkins/datacenter/bas/"
sh "scp -r target/alternateLocation ${ZHtest_serverName}@${ZHtest_serverIP}:/jenkins/datacenter/bas/"
sh "rsync -av target/classes/ --exclude=com ${ZHtest_serverName}@${ZHtest_serverIP}:/jenkins/datacenter/bas/"
}
}
stage(部署到測試環境) {
steps{
echo Deploying to ZHtest_server
// 重啟服務
sh "/home/jenkins/kill_32_client.sh bas-job bas"
}
}
stage(測試環境介面自動化測試) {
agent{
label Slave_Linux_69_2
}
steps{
//sh "sleep 60s"
echo "starting interfaceTest......"
dir(/home/jenkins/pm_test)
{
sh (source /etc/profile;newman -c API_test.json)
}
}
}
stage(是否發布到生產環境?) {
steps{
timeout(10) {
script {
mail to: "${JM_EMAIL} ${PM_EMAIL}",
subject: "PineLine ${JOB_NAME} (${BUILD_NUMBER})發布生產環境通知",
body: "提交的PineLine ${JOB_NAME} (${BUILD_NUMBER})進入生產環境部署
請及時前往${env.BUILD_URL}進行確認"
input message:部署到生產環境?,submitter:"${PM_EMAIL}"
// 中文環境
def ZHtest_split=params.ZHtest_server.split(",")
ZHtest_serverIP=ZHtest_split[0]
ZHtest_serverPort=ZHtest_split[1]
ZHtest_serverName=ZHtest_split[2]
ZHtest_serverPasswd=ZHtest_split[3]
// 英文環境
def UStest_split=params.UStest_server.split(",")
UStest_serverIP=UStest_split[0]
UStest_serverPort=UStest_split[1]
UStest_serverName=UStest_split[2]
UStest_serverPasswd=UStest_split[3]
}
}
//文件服務環境
//清理清理舊程序
sh "/home/jenkins/del_file_client.sh bas"
sh "scp -r target/*.jar ${file_serverName}@${file_serverIP}:/data/datacenter/bas/"
sh "scp -r target/alternateLocation ${file_serverName}@${file_serverIP}:/data/datacenter/bas/"
sh "rsync -av target/classes/ --exclude=com ${file_serverName}@${file_serverIP}:/data/datacenter/bas/"
}
}
stage(部署生產環境) {
parallel {
stage(中文環境) {
steps{
//sh "sleep 60s"
echo "starting Deploy Chinese_Server......"
}
}
stage(英文環境) {
steps{
//sh "sleep 60s"
echo "starting Deploy English_Server......"
}
}
}
}
}
}
一段段的來解釋
//確認使用主機/節點機
agent any /*{ node { label master} }*/
就如注釋所寫的,這裡是選擇要使用的Jenkins伺服器,開始之初配置了兩台,1台作為主機master,另一台作為從節點69_2,而any表示的是所有節點中任何一台,這是會根據Jenkins內部的分配機制分配的,如果指定是哪個節點執行腳本就是用注釋里的方法,當然還有其他方式也可以指定。
// 聲明參數
parameters{ //SVN代碼路徑 string(name:repoUrl, defaultValue: Job Board - SVN International Corp., description: SVN代碼路徑) // 部署內容的相對路徑 string(name:deployLocation, defaultValue: target/*.jar,target/alternateLocation/*.*,+target/classes/*.*,target/classes/i18n/*.*,target/classes/rawSQL/*.*,+target/classes/rawSQL/mapper/*.*,target/classes/rawSQL/mysql/*.*,+target/classes/rawSQL/sqlserver/*.*, description: 部署內容的相對路徑 )//伺服器參數採用了組合方式,避免多次選擇
string(name:dev_server, defaultValue: IP,Port,Name,Passwd, description: 開發伺服器(IP,Port,Name,Passwd)) string(name:ZHtest_server, defaultValue: IP,Port,Name,Passwd, description: 中文測試伺服器(IP,Port,Name,Passwd)) string(name:alT19_server, defaultValue: IP,Port,Name,Passwd, description: 生產伺服器T1(IP,Port,Name,Passwd)) string(name:alT20_server, defaultValue: IP,Port,Name,Passwd, description: 生產伺服器T2(IP,Port,Name,Passwd)) }
接下去這段是參數聲明,格式都是以string形式來聲明string(name:參數名, defaultValue: 默認值, description: 備註),當然參數也有其他形式,這裡只用到這個。這個腳本里聲明了SVN地址、歸檔的部署的文件、部署伺服器地址參數。
// 聲明使用的工具
tools { maven maven jdk jdk1.8}
這段是說的當前job里會用到的工具,因為當前腳本是編譯maven工程的,所以使用了JDK和maven,引號中的名字是Jenkins全局工具配置里設定好的