前言
Jenkins 是目前使用非常广泛的自动化流程的执行工具, 我们目前的一些自动化编译, 自动化测试都允许在 Jenkins 上面. 在 Jenkins 的术语里面, 一些自动化工作联合起来称之为流水线, 比如拉取代码, 编译, 运行自动化测试等.
本文的主要目的是引导你快速熟悉 Jenkinsfile 结构和语义, 让你读完之后可以自己写出一个 Jenkinsfile.
Jenkins 流水线分为脚本式的和声明式的. 脚本式流水线比较灵活, 可以嵌入一些 groovy 语言编程,功能比较强大, 但是对于新手而言上手比较困难, 有一定的学习门槛. 声明式流水线更具结构化, 简单直白, 减少了对 groovy 语言的依赖, 对新用户更友好.
Jenkinsfile 是什么
在 Jenkins 2 中, 流水线配置可以从 Jenkins 中分离出来. Jenkinsfile 允许你将配置文件和执行步骤以代码的形式保存, 这样就可以做到像管理 源代码一样管理 Jenkins 任务, 支持历史追溯, 差异对比等.
Jenkins2 推荐使用名为 'Jenkinsfile'的文件保存任务配置和流水线信息, 不同的项目和分支都有自己 的 Jenkinsfile, 其内容各不相同.
Jenkinsfile 基本功能
让我们先从一个简单的 Jenkinsfile 开始, 逐步了解 Jenkinsfile 的功能和用法.
Jenkinsfile Hello world
jenkinsfile
pipeline {
agent any
stages {
stage('Source') {
steps {
echo 'hello world'
}
}
}
}
- 第 1 行声明这是一个流水线.
- 第 2 行指定了执行该流水线的
agent
,agent
可以理解为一个执行环境, 可以是docker
,k8s
, 也可以是当前机器环境 - 第 4 行指定了执行该流水线所需要的步骤,
stages
里面可以保护多个stage
- 第 5 行指定了一个名字叫做
'Source'
的stage
- 第 6 行指定了该
stage
内需要执行的steps
- 第 7 行调用
echo
内置函数打印了一句'hello world'
执行 shell 脚本
在 steps 块中使用sh
来执行. 多行命令可以用"""
(三个双引号) 或者 "'
(三个单引号). 区别在于双引号里面引用 的变量会被计算展开. 例如:
groovy
steps {
// 单行shell命令
sh 'echo "Hello world"'
// 多行shell命令, 以连续的三个'或者"包围
sh '''#!/bin/bash
# multi line shell script
cd project
mkdir build && cd build
cmake .. && cmake --build .
'''
}
指定工作目录
dir
指令的功能是设定命令执行时的工作目录, 如果该目录不存在则创建它.
groovy
steps {
dir('project/build') {
sh "echo `pwd`"
}
}
拉取 GitHub/Bitbucket 代码
声明式流水线的 git 检出代码非常简单直白. 如下:
groovy
steps {
dir('code_repo') {
git credentialsId: "${BITBUCKET_CREDENTIALS_ID}",
url: 'ssh://git@git.company.com/code_repo.git',
branch: 'master'
}
}
需要说明几点:
git
指令会检出到当前的目录. 如果你需要检出到子目录则需要使用dir
指令.credentialsId
这块引用了一个环境变量, 该变量保存着仓库服务器的credential
. 定义环境变量的方法见后.branch
指定使用该仓库的哪个分支, 如果是master
那可以省略.
定义环境变量
环境变量在 pipeline 的environment
块中定义.
groovy
pipeline {
agent none
environment {
// credentials for other service, you can find it at
BITBUCKET_CREDENTIALS_ID = 'bitbucket.company.com'
REGISTRY_CREDENTIALS_ID = 'jenkins-harbor'
}
}
使用 docker 容器
这里有两种办法.
docker 插件提供的方法
在 agent 中指定 docker 环境. 这种方法适合安装了 docker 的 Jenkins.
groovy
stage ('SetupEnv') {
agent {
docker {
alwaysPull true
image 'hub.company.com/jenkins/ubuntu:24_04'
registryCredentialsId "${REGISTRY_CREDENTIALS_ID}"
registryUrl 'https://hub.company.com'
reuseNode true
}
}
steps {
// 这里的代码运行在上面的docker container环境
sh '''#!/bin/bash
set -xeu
cd code_repo
./setup_env.sh 64
'''
}
}
此时 steps
块中的命令都是在 docker 环境中执行的.
Docker Template
本方法适用于在 k8s 集群上面部署的 Jenkins, 并且已经设置了全局的 Docker Template 文件. 如果想使用 自己设定的 yaml 文件, 请参考下一节. label
是指定 k8s 有相应标签属性的 pod, 而container
则是指定选取特定的 docker images.
groovy
pipeline {
agent {
kubernetes {
label 'jenkins-streaming'
}
}
stages {
stage('stage1') {
steps{
container('ubuntu'){
sh 'lsb_release -a'
}
}
}
}
}
指定自己的 k8s yaml 文件
本方法适用于 k8s 环境下部署的 Jenkins. 特点在于可以由使用者配置 Docker Template.
groovy
pipeline {
agent {
kubernetes {
yaml libraryResource('co/company/project_pod.yaml')
}
}
stages {
stage('show') {
steps{
container('ubuntu'){
sh 'lsb_release -a'
}
}
}
}
}
yaml 文件如果在本地仓库, 则可以使用
groovy
yamlFile 'path/to/local/pod/file.yaml'
指定 agent
一个 pipeline 可以指定一个或者多个agent
. 指定一个全局通用 agent 的方法是在 pipeline 开始的地方指定.
groovy
pipeline {
agent any
}
pipeline {
agent {
docker {
// docker images settings
}
}
}
pipeline {
agent {
kubernetes {
// k8s settings
}
}
}
如果需要在不同的 stage 使用不同的 agent, 则在开始处指定为none
, 然后在各个 stage 分别指定.
groovy
pipeline {
agent none
stages {
stage('Source') {
agent any
}
stage('Compile') {
agent {
image: 'hub.company.com/jenkins/ubuntu:2404'
}
}
}
}
Artifactory 上传下载
中间产物文件需要上传到 Artifactory 的, 可以参考下面步骤:
groovy
stage ('Upload Files to Artifactory') {
steps {
rtUpload (
serverId: "${ARTIFACTORY_SERVER_ID}",
spec: """
{
"files": [
{
"pattern": "/build/library.a",
"target": "your_repo",
"props": "gcc_version=4.8;branch=your_branch_name"
},
{
"pattern": "/build/executable.a",
"target": "your_repo",
"props": "gcc_version=4.8;branch=your_branch_name"
}
]
}
"""
)
}
}
关于 spec
的详细信息可以参考: Upload
SonarQube 集成
SonarQube 提供了代码审计, 漏洞检查的功能. 目前可以集成 clang-tidy, cppcheck 静态检查结果, 以及 gcov 的覆盖率分析结果.
同时能提供新增代码的质量检查, 确保新 merge 的代码符合质量要求.
groovy
stage('Analysis') {
steps {
withSonarQubeEnv('SonarQubeServerID') {
container("sonar-scanner-image") {
sh '''#!/bin/bash
set -eu
cd your/project
sonar-scanner -X \
-Dsonar.projectKey=ProjectName
'''
}
}
}
}
一般而言, 我们会在执行 sonar-scanner 的目录里面存放一个名为sonar-project.properties
的文件, 用以指定更多的 sonar 配置. 示例的配置如下:
properties
sonar.projectVersion=3.0.0
sonar.language=c++
# 指定源文件目录
sonar.sources=.
sonar.sourceEncoding=UTF-8
# 指定排除的文件/目录
sonar.exclusions=\
build/** \
doc/** \
script/** \
libs/** \
test/**
# 指定排除覆盖率的文件
sonar.coverage.exclusions=**/*_test.cpp
# cppcheck 报告文件
sonar.cxx.cppcheck.reportPath=./cppcheck_report.xml
# clang tidy报告文件
sonar.cxx.clangtidy.reportPath=./clang-tidy-report.txt
sonar.cxx.coverage.reportPath=./bullseye_report.xml
总结
在这篇文章中, 我们深入探讨了 Jenkinsfile 的基础与高级用法, 从最简单的"Hello World"示例到复杂的 Docker 容器集成, Kubernetes 部署, Artifactory 上传下载以及 SonarQube 代码质量检查. 通过这些内容的学习, 可以帮你掌握如何创建一个基本的流水线, 并且了解了如何扩展这个基础以适应更复杂的需求.