前言
最近一周,我的工作任务都围绕着Jenkins展开,从泛泛了解到实际应用,中间的沟需要填平,坎需要铲平。这中间踩了一些坑,但也积累了一些实践经验。完成了从门外汉到入门的进阶,感觉学习Jenkins的一个障碍,就是权限不够的话,许多配置菜单看不到,看不到菜单的话就无从学习了解。这次有幸管理员给我充分赋权,让我实现了配置和调试的自由,所以学的比较快。今天就把这一周感觉有收获的知识点给各位掘友分享一下。
1.如何安装插件?
许多功能,都需要安装插件。可是邪门之处在于,一些插件,在插件管理-->可得到的插件面板搜不到,比如说http_request插件,

可是这个插件在Jenkins官方插件市场是存在的,这种情况经常发生,让新手感到很懵。
这时就需要手动下载,手动下载的链接在一个不起眼的地方,我第一次找了几分钟才找到。
切换到高级设置面板,进行部署。部署之后,需要重启才能生效。
手动安装后,重启才能生效。在浏览器端安全重启的方式是在浏览器中输入http://your-jenkins-domain/safeRestart
2.如何创建凭据?
所有的登录认证,都需要凭据。如登录Gitlab仓库,登录Docker镜像仓库,k8s部署登录等。新手创建凭据时。大概率会遇到的第一个障碍是,找不到创建凭据的菜单入口。因为创建凭据的菜单按钮不是一个很显眼的按钮。而是鼠标停留在全局链接时,会出现下拉箭头,点击下拉箭头之后,才会出现添加凭据菜单入口。

第二个障碍就是凭据是分类型的,经常出现的问题是你创建了凭据,使用的时候却在下拉列表不展示。如创建了一个jenkins-gitlab的凭据,

却在配置git仓库的凭据时找不到,这是因为创建的凭据类型不匹配。
另外就是创建凭据的时候, 对ID参数填什么感到很困惑。ID是个可选参数,可以不填。

3.如何在Jenkins流水线中进行登录认证?
这个功能需要安装Credentials Plugin 和Credentials Binding Plugin插件。它们各自的用途是:
- Credentials Plugin :管理凭据(
REGISTRY
这个credentialsId
就是保存到 Jenkins 凭据里的) - Credentials Binding Plugin :在 Pipeline 脚本中安全地把凭据绑定为环境变量(比如你的
REGISTRY_USERNAME
、REGISTRY_PASSWORD
)
场景举例,比如说流水线构建有一个步骤是登录docker,执行镜像构建,再把构建之后的Docker镜像推送到远程仓库。就需要用到登录认证功能。
js
steps {
script {
try {
updateGitlabCommitStatus(name: '生成docker镜像', state: 'pending')
withCredentials([
usernamePassword(
credentialsId: 'REGISTRY',
usernameVariable: 'REGISTRY_USERNAME',
passwordVariable: 'REGISTRY_PASSWORD'
)
]) {
sh """
echo "\$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "\$REGISTRY_USERNAME" --password-stdin
docker build --build-arg TAR_FILE="$TAG_FILE_NAME" -t "$IMAGE_TAG" .
docker push "$IMAGE_TAG"
"""
}
updateGitlabCommitStatus(name: '生成docker镜像', state: 'success')
} catch (Exception e) {
updateGitlabCommitStatus(name: '生成docker镜像', state: 'failed')
throw e
}
}
}
4.如何自定义Jenkins构建侧边栏展示信息?
Jenkins默认的构建侧边栏展示信息比较有限,只有启动者信息,如何自定义展示更多的信息呢。
答案是修改currentBuild.description
的值,添加自定义内容。不过有长度约束,超过4行多余部分就会显示省略号。
js
stage('SetPanelInfo') {
steps {
def starter = env.gitlabUserName ?: env.BUILD_USER
def shortSha = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
def branch = "${env.gitlabBranch ?: params.BranchName}-${shortSha}"
currentBuild.description = "自动部署: ${env.DEPLOY_ENV}, 分支: ${branch}, 启动者: ${starter}"
}
}
这是定制之后的效果:

5.如何采集代码提交信息?
场景是这样的,需要采集手动构建时的代码提交消息,如Jenkins构建地址,提交地址,分支,提交ID,提交信息,提交作者,ESLint 总错误数,ESLint 总警告数。获取这些信息的方式参见下面的Groove脚本。有两处需要注意
- 跨stage使用的变量,要定义到环境变量中。
- 单个Stage执行时间过长的话,会引起Jenkins超时报错退出。对于比较耗时的操作,比如说ESLint全局校验,需要放在
timeout(time: 10, unit: 'MINUTES')
中执行。
js
stage('Report Info') {
steps {
script {
env.branchName = params.BRANCH_NAME
echo "当前选择的分支: ${env.branchName}"
// 直接拿 HEAD,不需要 fetch
env.commitHash = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// 拿到 commit message
env.commitMessage =sh(script: "git log -1 --pretty=format:%s ${commitHash}", returnStdout: true).trim()
// 拿到 commit 作者
env.commitAuthorName = sh(script: "git log -1 --pretty=format:'%an' ${commitHash}", returnStdout: true).trim()
// GitLab 项目基础地址
def gitlabProjectUrl = 'https://git.xxx.com/research-and-development/general/exchange/exchange-portal-web'
// commitHash 是上面已经拿到的
env.commitUrl = "${gitlabProjectUrl}/-/commit/${commitHash}"
// 运行 ESLint 并生成 JSON 报告
timeout(time: 10, unit: 'MINUTES') {
sh '''
set -e
bash -c "
source ~/.bashrc
nvm use 18.20.6
npx eslint 'src/**/*.{ts,vue}' --cache --format json --output-file eslint-report.json || true
"
'''
// 读取 ESLint 报告文件
def reportFile = readFile file: 'eslint-report.json'
// // 解析 JSON 报告
def report = readJSON text: reportFile
// 初始化计数器
int totalErrors = 0
int totalWarnings = 0
// 遍历报告中的每个文件
report.each { fileReport ->
fileReport.messages.each { message ->
if (message.severity == 2) {
totalErrors++
} else if (message.severity == 1) {
totalWarnings++
}
}
}
env.totalErrors = totalErrors
env.totalWarnings = totalWarnings
// 输出统计结果
echo "Jenkins构建地址: ${env.BUILD_URL}"
echo "提交地址: ${env.commitUrl}"
echo "分支: ${env.branchName}"
echo "提交ID: ${env.commitHash}"
echo "提交信息: ${env.commitMessage}"
echo "提交作者: ${env.commitAuthorName}"
echo "ESLint 总错误数: ${env.totalErrors}"
echo "ESLint 总警告数: ${env.totalWarnings}"
}
}
}
}
6.如何发送邮件?
需要安装Email Extension Plugin
插件,才能使用emailext
发送邮件,emailext
支持 HTML格式(mimeType: 'text/html'
),附件、条件发送、抄送/密送,非常灵活。比如说有这样一个场景,无论构建成功还是失败,要将每次构建的代码版本信息和质量检查统计结果邮件发送给产研负责人,限定只发送test和canary环境的构建操作。因为每个环境都发的话,收件箱会爆满。这个功能的实现脚本如下:
js
post {
always {
script {
if (params.DEPLOY_ENV in ['test', 'canary']) {
emailext (
subject: "项目:${env.JOB_NAME}--环境:${params.DEPLOY_ENV} -- 构建结果:${currentBuild.currentResult}",
body: """
<p>项目:${env.JOB_NAME}</p>
<p>状态:<strong>${currentBuild.currentResult}</strong></p>
<p>详情:<a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
<p>提交地址: <a href="${env.commitUrl}">${env.commitUrl}</a></p>
<p>分支: ${env.branchName}</p>
<p>提交ID: ${env.commitHash}</p>
<p>提交作者: ${env.commitAuthorName}</p>
<p>提交信息: ${env.commitMessage}</p>
<p>ESLint 总错误数: ${env.totalErrors}</p>
<p>ESLint 总警告数: ${env.totalWarnings}</p>
""",
to: '[email protected], [email protected]',
from: '[email protected]',
mimeType: 'text/html'
)
} else {
echo "分支 ${env.branchName} 不在允许发送邮件的列表中,跳过发送邮件。"
}
}
}
success {
// 构建成功后的操作,例如发送通知
echo 'Build and package succeeded!'
}
failure {
// 构建失败后的操作,例如发送通知
echo 'Build or package failed!'
}
}
7.什么时候应该使用Pipeline Script?
流水线的获取方式有两种:
- 一种是Pipeline script, 这种方式使用的是在线Jenkins脚本,多用于调试
- Pipeline script from SCM是从指定的代码仓库与分支获取Jenkins脚本,适用于Jenkins脚本存档。
如果用的不恰当的话,会带来不少麻烦。比如说调试Jenkins脚本,使用 Pipeline script from SCM,每次改点东西都要推送到远程仓库才能生效,调试比较低效。而调试通过之后,不对脚本进行存档,可能会发生篡改或者无意清空在线Jenkins脚本的情形。

如果真的发生脚本被别人无意清空,运维给我传授了一个窍门,可以使用回放功能,找回丢失的构建脚本。

另外,配置页面的这两个按钮使用也有讲究。点击应用之后,新改的脚本会立即生效,但不会引起配置页面跳转。特别适合打开两个tab页,一个页面用于调试修改脚本,另外一个页面用于构建查看结果,很方便。

8.如何手动切换git仓库分支?
需要安装Git Plugin
插件, 这个功能的用途是Jenkins使用的构建脚本在master分支,而要构建的业务功能是Gitlab Webhook推送过来的动态分支,这个时候,就需要手动切换到需要构建的动态分支。刚开始把脚本获取分支和构建分支分不清,发现手动构建时正常,自动构建时构建的产物内容不对,后来发现是这个问题引起的。
js
stage('Checkout') {
steps {
script {
try {
sh 'env'
// 手动检出代码
updateGitlabCommitStatus(name: '拉取代码', state: 'pending')
echo "最新hash值 ${env.gitlabMergeRequestLastCommit} "
if (env.gitlabMergeRequestLastCommit || params.BranchName) {
checkout([
$class: 'GitSCM',
branches: [[name: "${env.gitlabMergeRequestLastCommit ?: 'dev'}"]],
// extensions: [
// [$class: 'WipeWorkspace'],
// [$class: 'CleanBeforeCheckout'],
// [$class: 'PruneStaleBranch'],
// [$class: 'CloneOption', noTags: false, shallow: false, depth: 0, reference: '', timeout: 10]
// ],
userRemoteConfigs: [[
url: env.gitlabSourceRepoHttpUrl ,
credentialsId: 'jenkins-robot'
]]
])
}
updateGitlabCommitStatus(name: '拉取代码', state: 'success')
} catch (Exception e) {
updateGitlabCommitStatus(name: '拉取代码', state: 'failed')
throw e
}
}
}
}
最后
这一周的工作经历,让人颇有获得感。现学现用的感觉真好。如果没有实践反馈,人不知道自己掌握的知识,能否经得起实践的考验。原因有三:
- 工具在不断迭代更新,你学到的可能是老版本的使用方法,而项目使用的却是工具的新版本。
- 看的文章作者可能省略了一些关键步骤,一步一步跟着做,跟到某一步会突然卡住跟不上
- 只有看到真实场景的反映,才了解一些专有名词所表达的实际含义,用陌生名词理解陌生名词,用生涩定义推理生涩定义,会让人云里雾里,大脑一片茫然。
本文所列举的知识点,都能经得起实践的检验,如你将来如遇类似问题,可直接套用现成方案。另外,手头还有两个Jenkins任务没做完,等做完后再添加上。