一、为什么是jenkinsfile

上文我们说了pipeline,已为本文铺路不少,接下里就是将之串联起来。 先想说下,为什么是jenkinsfile, 因为jenkins job还支持pipeline方式。 这种方式,不建议实际使用,仅限于测试或调试groovy代码。

下面贴出来,我们的使用方式。好处是:采用分布式的思想,改动git上的jenkinsfile,就可以让所有的job更新。

二、jenkinsfile的参数

这也有两种方式,我反而又不建议你写在jenkinsfile里。

Job (建议方式,因为每个工程的参数不一样,需要持久化保存)jenkinsfile

对于java语言来说, 我梳理了以下几个必须参数:

jarNameportprojectName(消息通知)codeUrlbranch

为了一些额外需求,我们还定义了几个扩展参数:

packageType(打包方式,mvn或者gradle。默认为mvn)robotKey(消息通知的机器人robot,默认为空)childModule(子模块, 用于一个多module的项目打包,指定某模块以发布。默认为空)

三、groovy编码的几点建议

1、变量一定要使用trim()方法

举例,shell命令读取程序版本号,返回值如果没有使用trim(),会额外多一个换行符。-- 后期在使用该变量拼接的时候,很容易就把明明一行执行的,硬生生地被拆成了两行执行。

// 读取java应用的版本号

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}

// 我还在纳闷,怎么最后的.被谁吃掉了呢。。。

// 当你期望的是

docker build -f /opt/Dockerfile -t xxx/devops-service:1.0.7-001 .

// 实际却是:

docker build -f /opt/Dockerfile -t xxx/devops-service:1.0.7

2、引入pipeline library

这里的名称,对应前文配置中的name

为了避免jenkinsfile的代码量过于复杂,我们往往会抽取出公共的方法。

@Library('jenkinslib') _

def tools = new com.xhtech.devops.tools()

tools.PrintMes("pull code!!!", "green")

3、读取参数

这里的参数分为两种,一是自定义,一是系统自带。

字符串类型的变量,后面都紧跟着trim()方法。

// JOB_NAME是系统自带

String jobName = "${env.JOB_NAME}".trim()

// jarName是我们在job的参数化构建中配置

String jarName = "${env.jarName}".trim()

4、运行shell命令

// 正确的写法,必须由script括起来

script {

sh "mvn clean package -Dmaven.test.skip=true -U"

}

5、切换目录

dir("${env.WORKSPACE}")

6、指定容器

默认容器是jnlp, 如果你的Pod配置了多个容器,docker image的相关操作就必须在docker容器里执行。

container(‘docker’),这里的docker就是对应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、设置操作的超时时间

timeout(time: 1, unit: "MINUTES") {

// 具体操作,预计在1分钟内完成

}

9、指定工作空间

我们把工作空间持久化到某个目录,而不是放在Pod里。这样的好处是:起到了缓存的作用,不会随着Pod的销毁而需重建。

// 我们会对/opt/.m2进行持久化操作

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:项目管理--研发项目--产品列表中的英文名称

String projectName = "${env.projectName}".trim()

// jenkins job

String jobName = "${env.JOB_NAME}".trim()

// jar包的名称

String jarName = "${env.jarName}".trim()

// java应用的端口

int port = "${env.port}"

// java代码的git仓库地址

String codeUrl = "${env.codeUrl}".trim()

// java代码的git分支

String branch = "${env.branch}".trim()

// 消息通知的机器人robot

String robotKey = "${env.robotKey}".trim()

// 打包方式,mvn或者gradle

String packageType = "${env.packageType}".trim()

// 构建者

String buildUser = ""

// 版本号, 由jenkins去读取

String appVersion = ""

//docker image version

String dockerImageVersion = ""

// 子模块, 用于一个多module的项目打包,指定某模块以发布

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"

// 校验不能为空

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

}

//根据jobName读取环境区分

String[] jobInfoArray = jobName.split("_")

// 环境

String buildEnv = jobInfoArray[0]

pipeline {

agent {

kubernetes {

inheritFrom 'jnlp-maven'

customWorkspace "${PROJECT_WORKSPACE}"

}

}

options {

timestamps() //日志会有时间

skipDefaultCheckout() //删除隐式checkout scm语句

disableConcurrentBuilds() //禁止并行

timeout(time: 1, unit: 'HOURS') //流水线超时设置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协议

git branch: "$branch", credentialsId: "$gitlabCredentialsId", url: "$codeUrl"

}

}

post {

failure {

//当此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 {

//当此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 {

//当此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 {

//当此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 {

//当此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 {

//当此Pipeline失败时打印消息

tools.PrintMes("Update Helm Yaml failure!!!", "red")

http.imNotify(projectName, "FAIL", buildEnv, "Update Helm Yaml failure", branch, buildUser, robotKey)

}

}

}

}

}

post {

success {

//当此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: 我们对于构建成功的消息内容,增加程序版本号。

具体消息通知的实现,就不在本文的范畴。

写在末尾的话

后期,我们有空,就Jenkins集群、插件(包括二次开发插件)等话题,聊一聊我们的实际使用情况。

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: