前言
Jenkins Pipeline 是 Jenkins 提供的一套强大的工作流框架,它允许开发者以代码的形式定义整个软件交付过程,从而实现持续集成和持续部署(CI/CD)。通过 Pipeline,原本独立运行于单个或多个节点的任务可以被连接起来,形成一个完整的、可视化的工作流。以下是对 Pipeline 部署的一个综合记录与总结,涵盖了常用语法的概述以及实践应用的展示。
语法
Pipeline 的语法主要基于 Groovy,并分为声明式(Declarative)和脚本式(Scripted)两种。
声明式 Pipeline
声明式 Pipeline 是 Jenkins Pipeline 的推荐方式,它更简洁、更易于理解。其最简结构如下:
groovy
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
主要组成部分
- pipeline:整个流水线的根元素,包含流水线的全部逻辑。
- agent:指定流水线的执行节点(Jenkins agent)。可以是 any、none、label、node 或 docker。
- stages:包含多个 stage 的容器。
- stage:代表流水线的阶段,每个阶段必须有名称。
- steps:代表阶段中的一个或多个具体步骤(step)的容器。
- post:可选部分,包含整个 pipeline 或阶段完成后的一些附加步骤。根据完成状态分为 always、success、failure、unstable、changed、fixed、aborted 和 cleanup 等条件块。
常用指令
- environment:设置环境变量,可定义在 stage 或 pipeline 部分。
groovy
pipeline {
agent any
environment {
CC = 'clang'
}
stages {
stage('Example') {
environment {
AN_ACCESS_KEY = credentials('my-predefined-secret-text')
}
steps {
sh 'printenv'
sh 'echo ${CC}'
}
}
}
}
- tools:定义自动安装并自动放入 PATH 里面的工具集合。
groovy
pipeline {
agent any
tools {
maven 'apache-maven-3.0.1'
}
stages {
stage('Example') {
steps {
sh 'mvn --version'
}
}
}
}
- input:在 stage 部分使用,会暂停 pipeline,提示输入内容。
groovy
stage('Input Example') {
input 'Should we continue?'
}
- options:配置 pipeline 本身的选项,如 timeout、retry、buildDiscarder、disableConcurrentBuilds 等。
groovy
pipeline {
agent any
options {
timeout(time: 10, unit: 'SECONDS')
buildDiscarder(logRotator(numToKeepStr: '2'))
retry(5)
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
- parameters:定义 pipeline 的专有参数列表,支持 string、text、booleanParam、choice、password 等数据类型。
groovy
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: 'Jenkins', description: '输入的文本参数')
choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
}
}
}
}
- triggers:定义 pipeline 的触发器,如 cron 和 pollSCM。
groovy
pipeline {
agent any
triggers {
cron('H 4/* 0 0 1-5')
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
- parallel:声明式 Parallel 的代码块中可以嵌套多个 stage,从而让多个 stage 任务并行执行。
groovy
pipeline {
agent any
stages {
stage('Parallel Example') {
steps {
script {
def tasks = [:]
tasks['Task 1'] = {
echo 'Running Task 1'
// 其他步骤
}
tasks['Task 2'] = {
echo 'Running Task 2'
// 其他步骤
}
parallel tasks
}
}
}
}
}
常用的内置步骤
文件目录相关步骤:deleteDir、dir、fileExists、pwd、writeFile、readFile。
制品相关步骤:stash、unstash。
命令相关步骤:sh。
其他步骤:error、tool、timeout、waitUntil、retry、sleep。
脚本式 Pipeline
groovy
node {
stage('Build') {
echo 'Building..'
sh 'make'
}
stage('Test') {
echo 'Testing..'
sh 'make check'
}
stage('Deploy') {
echo 'Deploying....'
sh 'make publish'
}
}
- 使用node作为根元素,表示Pipeline将在某个节点上执行。
- stage块是可选的,但在这里使用它可以清晰地展示Jenkins UI中的每个stage的任务子集。
- 每个stage中同样定义了要执行的步骤,但这里没有显式地指定steps块,因为步骤是直接写在stage块中的。
对比分析
声明式 Pipeline 以其简洁的语法结构和固定的组织要求,更适合小型项目或快速构建需求,且对无编程经验的用户友好;如果需要更复杂的逻辑,可以通过嵌套script块来实现。而脚本式 Pipeline 则基于Groovy 脚本,提供了更高的灵活性和几乎无限制的功能,适用于大型项目或需要复杂逻辑和高度定制化的场景,但要求用户具备一定的Groovy编程基础。
应用实例
网页部署
- Jenkins 首页 < New Item > 新建 < Pipeline >,并进行配置。
- 绑定 gerrit project,用于 gerrit 代码提交时触发 Pipeline 执行。
- 在部署 Pipeline 时,可以先直接在网页配置 Pipeline script 进行测试,等 Pipeline script 调试通过后再移植到代码仓库进行版本管理。
- 网页的 Pipeline script 调试通过后,可以将脚本内容复制到代码仓库 .jenkinsfile 文件上(比如我这里的 chirpstack.jenkinsfile),方便后续进行版本管理。
Pipeline script
下面是一个使用 docker 构建 yocto openstlinux 镜像的 pipeline script 实例,主要流程有:
- 指定 pipeline 使用的 docker 镜像环境。(agent)
- 拉取项目代码并检出到对应分支。(Checkout)
- 从 aws 私人仓库下载相关软件加速包(避免在编译过程中下载)。(Download)
- 配置编译环境,构建镜像。(Build)
- 将构建的镜像上传到 aws。(Upload)
- 构建成功时将镜像下载链接附属到网页。(post )
关于如何构建 openstlinux 编译环境的 docker 镜像(chirpstack-dev:latest),可以参考 Docker 基础入门
shell
pipeline {
agent {
docker {
label 'Embedded'
image 'chirpstack-dev:latest'
args '-v ${HOME}/.gitconfig:/home/ubuntu/.gitconfig -v ${HOME}/.ssh:/home/ubuntu/.ssh -v ${HOME}/.aws:/home/ubuntu/.aws'
}
}
environment {
JOB_PATH = "${env.WORKSPACE}/lorawan/yocto_ws"
}
stages {
stage('Checkout') {
steps {
sh '''#!/bin/bash
set -xe
whoami
mkdir -p "$JOB_PATH" && cd $JOB_PATH
init_and_update() {
local max_retries=3
local retries=0
local success=false
west init -m ssh://gerrit.robotics.cn:29419/iot_device/lorawan/gateway/cloud_walking_shoes
while [ $retries -lt $max_retries ]; do
retries=$((retries + 1))
local result=$(west update 2>&1)
local status=$?
if echo "$result" | grep -q "ERROR"; then
echo "Attempt $retries failed to initialize job $JOB_PATH. Retrying..."
else
success=true
echo "Initialization and update completed successfully for job $JOB_PATH on attempt $retries."
break
fi
sleep 3
done
if [ $success = false ]; then
echo "Error initializing job $JOB_PATH after $max_retries attempts:"
echo "$result"
exit 1
fi
}
init_and_update
'''
}
}
stage('Download') {
steps {
sh '''#!/bin/bash
set -xe
EXPECTED_MD5="04fca21f1061fd33a831ffc3660722b2"
CACHE_DIR=$JOB_PATH/../build
mkdir -p "$CACHE_DIR"
echo "Starting download process ..."
aws s3 cp s3://embedded/yocto/chirpstack/download/downloads.zip $CACHE_DIR/
if [ $? -ne 0 ]; then
echo "Error: Failed to download files from S3."
exit 1
fi
echo "Download process completed successfully."
# ACTUAL_MD5=$(md5sum "$CACHE_DIR/downloads.zip" | awk '{print $1}')
# if [ "$ACTUAL_MD5" != "$EXPECTED_MD5" ]; then
# echo "Error: MD5 checksum mismatch. Expected: $EXPECTED_MD5, Actual: $ACTUAL_MD5"
# exit 1
# fi
# echo "Download process completed successfully and MD5 checksum verified."
unzip -o $CACHE_DIR/downloads.zip -d $CACHE_DIR
rm $CACHE_DIR/downloads.zip
'''
}
}
stage('Build') {
steps {
sh '''#!/bin/bash
set -xe
cd ${JOB_PATH}/../
source ${JOB_PATH}/poky/oe-init-build-env
pwd
sed -i 's/^MACHINE ??=.*/MACHINE ??= \"stm32mp15-loar-gateway\"/' conf/local.conf
sed -i 's/^DISTRO ?=.*/DISTRO=\"openstlinux-weston\"/' conf/local.conf
cat conf/local.conf | grep -E "MACHINE|DISTRO"
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-oe
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-python
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-networking
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-gnome
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-multimedia
bitbake-layers add-layer ../yocto_ws/meta-openembedded/meta-webserver
bitbake-layers add-layer ../yocto_ws/meta-loar-gateway
bitbake-layers add-layer ../yocto_ws/meta-st-stm32mp
bitbake-layers add-layer ../yocto_ws/meta-st-openstlinux
bitbake-layers add-layer ../yocto_ws/meta-st-stm32mpu-app-lorawan
cat conf/bblayers.conf
ls -al $(pwd)/downloads
set +e
bitbake_build() {
local max_retries=3
local retries=0
local success=false
while [ $retries -lt $max_retries ]; do
retries=$((retries + 1))
bitbake st-image-weston
if [ $? -ne 0 ]; then
echo "Attempt $retries failed to Bitbake build. Retrying..."
else
success=true
break
fi
sleep 3
done
if [ $success = false ]; then
echo "Error failed to Bitbake build after $max_retries attempts:"
exit 1
fi
}
bitbake_build
'''
}
}
stage('Upload') {
steps {
sh '''
set -xe
tar -czvf ${JOB_PATH}/stm32mp15-loar-gateway_${BUILD_NUMBER}.tar.gz ${JOB_PATH}/../build/tmp-glibc/deploy/images/stm32mp15-loar-gateway
aws s3 cp ${JOB_PATH}/stm32mp15-loar-gateway_${BUILD_NUMBER}.tar.gz s3://embedded/yocto/chirpstack/Artifacts/
'''
}
}
}
post {
success {
archiveArtifacts artifacts: "lorawan/yocto_ws/stm32mp15-loar-gateway_${BUILD_NUMBER}.tar.gz", fingerprint: true
}
cleanup {
cleanWs()
}
}
}