一、為什么是jenkinsfile
上文我們說了pipeline,已為本文鋪路不少,接下里就是將之串聯(lián)起來。
先想說下,為什么是jenkinsfile, 因?yàn)閖enkins job還支持pipeline方式。
這種方式,不建議實(shí)際使用,僅限于測試或調(diào)試groovy代碼。
下面貼出來,我們的使用方式。好處是:采用分布式的思想,改動git上的jenkinsfile,就可以讓所有的job更新。
二、jenkinsfile的參數(shù)
這也有兩種方式,我反而又不建議你寫在jenkinsfile里。
- Job (建議方式,因?yàn)槊總€工程的參數(shù)不一樣,需要持久化保存)
- jenkinsfile
對于java語言來說, 我梳理了以下幾個必須參數(shù):
- jarName
- port
- projectName(消息通知)
- codeUrl
- branch
為了一些額外需求,我們還定義了幾個擴(kuò)展參數(shù):
- packageType(打包方式,mvn或者gradle。默認(rèn)為mvn)
- robotKey(消息通知的機(jī)器人robot,默認(rèn)為空)
- childModule(子模塊, 用于一個多module的項(xiàng)目打包,指定某模塊以發(fā)布。默認(rèn)為空)
三、groovy編碼的幾點(diǎn)建議
1、變量一定要使用trim()方法
舉例,shell命令讀取程序版本號,返回值如果沒有使用trim(),會額外多一個換行符。-- 后期在使用該變量拼接的時候,很容易就把明明一行執(zhí)行的,硬生生地被拆成了兩行執(zhí)行。
// 讀取java應(yīng)用的版本號
def getAppVersion(packagePath) {
def appVersion = sh returnStdout: true,
script: """
grep 'git.build.version' ${packagePath}/classes/git.properties | cut -d'=' -f2 | sed 's/\\r//'
"""
// trim()
return appVersion.trim()
}
// 鏡像版本號
def dockerImageVersion = appVersion + "-001"
docker build -f ${dockerfileName} -t ${repoProject}/${appName}:${dockerImageVersion} .
// 如果你沒有使用trim()的話, 上面的這行代碼,就會變成:
docker build -f ${dockerfileName} -t ${repoProject}/${appName}:${dockerImageVersion}
// 我還在納悶,怎么最后的.被誰吃掉了呢。。。
// 當(dāng)你期望的是
docker build -f /opt/Dockerfile -t xxx/devops-service:1.0.7-001 .
// 實(shí)際卻是:
docker build -f /opt/Dockerfile -t xxx/devops-service:1.0.7
2、引入pipeline library
這里的名稱,對應(yīng)前文配置中的name
為了避免jenkinsfile的代碼量過于復(fù)雜,我們往往會抽取出公共的方法。
@Library('jenkinslib') _
def tools = new com.xhtech.devops.tools()
tools.PrintMes("pull code!!!", "green")
3、讀取參數(shù)
這里的參數(shù)分為兩種,一是自定義,一是系統(tǒng)自帶。
- 字符串類型的變量,后面都緊跟著trim()方法。
// JOB_NAME是系統(tǒng)自帶
String jobName = "${env.JOB_NAME}".trim()
// jarName是我們在job的參數(shù)化構(gòu)建中配置
String jarName = "${env.jarName}".trim()
4、運(yùn)行shell命令
// 正確的寫法,必須由script括起來
script {
sh "mvn clean package -Dmaven.test.skip=true -U"
}
5、切換目錄
dir("${env.WORKSPACE}")
6、指定容器
默認(rèn)容器是jnlp, 如果你的Pod配置了多個容器,docker image的相關(guān)操作就必須在docker容器里執(zhí)行。
- container(‘docker’),這里的docker就是對應(yīng)PodTemplate中的容器名稱。
container('docker') {
dir("${env.WORKSPACE}") {
docker.buildAndPushImage(jarName, port, dockerImageVersion, packagePath, dockerfileName)
}
}
7、歸檔archiveArtifacts
把版本號寫入到文件,并且歸檔
dir("${env.WORKSPACE}") {
sh "echo ${appVersion} > version_${appVersion}.txt"
archiveArtifacts artifacts: 'version_*.txt', followSymlinks: false
}
8、設(shè)置操作的超時時間
timeout(time: 1, unit: "MINUTES") {
// 具體操作,預(yù)計(jì)在1分鐘內(nèi)完成
}
9、指定工作空間
我們把工作空間持久化到某個目錄,而不是放在Pod里。這樣的好處是:起到了緩存的作用,不會隨著Pod的銷毀而需重建。
// 我們會對/opt/.m2進(jìn)行持久化操作
String sharefile = "/opt/.m2"
String jobName = "${env.JOB_NAME}".trim()
String PROJECT_WORKSPACE = "$sharefile/java-workspaces/$jobName"
agent {
kubernetes {
inheritFrom 'jnlp-maven'
customWorkspace "${PROJECT_WORKSPACE}"
}
}
10、清理空間
安裝jenkins plugin: Workspace Cleanup
// 文檔地址: https://plugins.jenkins.io/ws-cleanup/
stage('Clean Workspace') {
steps {
cleanWs()
}
}
四、完整的java-k8s.jenkinsfile
#!groovy
@Library('jenkinslib') _
String sharefile = "/opt/.m2"
String dockerfileName = sharefile + "/" + "Dockerfile"
// java代碼倉庫gitlab的ssh密鑰
String gitlabCredentialsId = "ffcbbd41-1e6a-49ec-8277-87a1299606dd"
def tools = new com.xxtech.devops.tools()
def http = new com.xxtech.devops.http()
def docker = new com.xxtech.devops.docker()
def helm = new com.xxtech.devops.helm()
// OA:項(xiàng)目管理--研發(fā)項(xiàng)目--產(chǎn)品列表中的英文名稱
String projectName = "${env.projectName}".trim()
// jenkins job
String jobName = "${env.JOB_NAME}".trim()
// jar包的名稱
String jarName = "${env.jarName}".trim()
// java應(yīng)用的端口
int port = "${env.port}"
// java代碼的git倉庫地址
String codeUrl = "${env.codeUrl}".trim()
// java代碼的git分支
String branch = "${env.branch}".trim()
// 消息通知的機(jī)器人robot
String robotKey = "${env.robotKey}".trim()
// 打包方式,mvn或者gradle
String packageType = "${env.packageType}".trim()
// 構(gòu)建者
String buildUser = ""
// 版本號, 由jenkins去讀取
String appVersion = ""
//docker image version
String dockerImageVersion = ""
// 子模塊, 用于一個多module的項(xiàng)目打包,指定某模塊以發(fā)布
String childModule = "${env.childModule}".trim()
String packagePath = "target"
if (childModule && childModule != "null" && childModule.trim() != "") {
tools.PrintMes("build package for childModule: " + childModule, "green")
packagePath = childModule + "/target"
}
// 判斷是否為gradle打包
boolean gradlePackage = packageType.equalsIgnoreCase("gradle")
if (gradlePackage) {
packagePath = "build/libs"
}
String gradleUserHome = "$sharefile/java-gradle-homes"
String gradleProjectCacheDir = "$gradleUserHome/$jobName"
// workspace
String PROJECT_WORKSPACE = "$sharefile/java-workspaces/$jobName"
// 校驗(yàn)不能為空
if (!projectName) {
tools.PrintMes("projectName is null", "red")
return
}
if (!jarName) {
tools.PrintMes("jarName is null", "red")
return
}
if (!codeUrl) {
tools.PrintMes("codeUrl is null", "red")
return
}
if (!branch) {
tools.PrintMes("branch is null", "red")
return
}
//根據(jù)jobName讀取環(huán)境區(qū)分
String[] jobInfoArray = jobName.split("_")
// 環(huán)境
String buildEnv = jobInfoArray[0]
pipeline {
agent {
kubernetes {
inheritFrom 'jnlp-maven'
customWorkspace "${PROJECT_WORKSPACE}"
}
}
options {
timestamps() //日志會有時間
skipDefaultCheckout() //刪除隱式checkout scm語句
disableConcurrentBuilds() //禁止并行
timeout(time: 1, unit: 'HOURS') //流水線超時設(shè)置1h
}
stages {
stage('Get UserInfo') {
steps {
wrap([$class: 'BuildUser']) {
script {
def BUILD_USER = "${env.BUILD_USER}"
def BUILD_USER_ID = "${env.BUILD_USER_ID}"
buildUser = BUILD_USER + "(" + BUILD_USER_ID + ")"
}
}
}
}
stage('Pull Code') {
steps {
script {
tools.PrintMes("pull code!!!", "green")
// 拉取代碼:git協(xié)議
git branch: "$branch", credentialsId: "$gitlabCredentialsId", url: "$codeUrl"
}
}
post {
failure {
//當(dāng)此Pipeline失敗時打印消息
script {
tools.PrintMes("pull code failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "pull code failure", branch, buildUser, robotKey)
}
}
}
}
stage('Compile') {
steps {
script {
tools.PrintMes("compile!!!", "green")
container('maven') {
dir("${env.WORKSPACE}") {
if (gradlePackage) {
sh "$sharefile/gradle-7.6/bin/gradle build -x test --build-cache -g $gradleUserHome --no-daemon --project-cache-dir $gradleProjectCacheDir"
} else {
sh "mvn clean package -Dmaven.test.skip=true -U -s $sharefile/settings.xml"
}
}
}
}
}
post {
failure {
//當(dāng)此Pipeline失敗時打印消息
script {
tools.PrintMes("compile failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "compile failure", branch, buildUser, robotKey)
}
}
}
}
stage('Get Version') {
steps {
script {
tools.PrintMes("Get Version!!!", "green")
appVersion = tools.getAppVersion(packagePath)
// 輸出程序的版本號
tools.PrintMes("get version: ${appVersion}", "green")
}
}
post {
failure {
//當(dāng)此Pipeline失敗時打印消息
script {
tools.PrintMes("Get Version failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "Get Version failure", branch, buildUser, robotKey)
}
}
}
}
stage('Push Docker Image') {
steps {
script {
tools.PrintMes("Push Docker Image!!!", "green")
def currentTime = sh returnStdout: true,
script: """
date +%m%d%H%M%S
"""
// 注意:這里的分隔符不能使用冒號, trim()
dockerImageVersion = appVersion + "-" + currentTime.trim()
tools.PrintMes("Docker Image Version: ${dockerImageVersion}", "green")
container('docker') {
dir("${env.WORKSPACE}") {
docker.buildAndPushImage(jarName, port, dockerImageVersion, packagePath, dockerfileName)
}
}
}
}
post {
failure {
//當(dāng)此Pipeline失敗時打印消息
script {
tools.PrintMes("Push Docker Image failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "Push Docker Image failure", branch, buildUser, robotKey)
}
}
}
}
stage('Clean Workspace') {
steps {
script {
tools.PrintMes("Clean Workspace!!!", "green")
cleanWs()
}
}
post {
failure {
script {
//當(dāng)此Pipeline失敗時打印消息
tools.PrintMes("Clean Workspace failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "Clean Workspace failure", branch, buildUser, robotKey)
}
}
}
}
stage('Update Helm Yaml') {
steps {
script {
tools.PrintMes("Update Helm Yaml!!!", "green")
helm.updateYaml(jarName, dockerImageVersion)
}
}
post {
failure {
script {
//當(dāng)此Pipeline失敗時打印消息
tools.PrintMes("Update Helm Yaml failure!!!", "red")
http.imNotify(projectName, "FAIL", buildEnv, "Update Helm Yaml failure", branch, buildUser, robotKey)
}
}
}
}
}
post {
success {
//當(dāng)此Pipeline成功時打印消息
timeout(time: 1, unit: "MINUTES") {
script {
tools.PrintMes("archive jar, send the message of build successfully to user", "green")
container('maven') {
dir("${env.WORKSPACE}") {
sh "echo ${appVersion} > version_${appVersion}.txt"
archiveArtifacts artifacts: 'version_*.txt', followSymlinks: false
}
http.imNotify(projectName, "SUCCESS", buildEnv, "OK", branch, buildUser, robotKey)
}
}
}
}
}
}
五、消息通知
- TODO: 我們對于構(gòu)建成功的消息內(nèi)容,增加程序版本號。
具體消息通知的實(shí)現(xiàn),就不在本文的范疇。文章來源:http://www.zghlxwxcb.cn/news/detail-537070.html
寫在末尾的話
后期,我們有空,就Jenkins集群、插件(包括二次開發(fā)插件)等話題,聊一聊我們的實(shí)際使用情況。文章來源地址http://www.zghlxwxcb.cn/news/detail-537070.html
到了這里,關(guān)于Devops系列六(CI篇之jenkinsfile)jenkins將gitlab helm yaml和argocd 串聯(lián),自動部署到K8S的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!