跳到主要内容

Jenkins Pipeline

Jenkins Pipeline 是一套插件,支持在 Jenkins 中实现和集成持续交付流水线。Pipeline 将原本独立运行的多个任务连接起来,实现复杂的构建、测试和部署流程的自动化。

什么是 Pipeline?

Pipeline 是 Jenkins 2.0 的核心特性,它使用代码(Groovy 语法)来定义整个构建流程:

  • 代码化:Pipeline 以代码形式存储,可以版本控制
  • 可持久化:Pipeline 可以暂停等待人工输入后继续执行
  • 可视化:通过 Blue Ocean 插件提供可视化界面
  • 可扩展:支持丰富的插件生态系统

Pipeline 的两种语法

1. 声明式 Pipeline(推荐)

声明式 Pipeline 使用结构化方式定义流水线,更易读、易维护。

pipeline {
agent any // 在任何可用节点上运行

environment {
APP_NAME = 'my-application'
VERSION = '1.0.0'
}

options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
}

stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/example/repo.git'
}
}

stage('Build') {
steps {
sh 'mvn clean package'
}
}

stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}

stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'scp target/*.jar user@server:/opt/app/'
}
}
}

post {
success {
echo '构建成功!'
}
failure {
echo '构建失败!'
mail to: '[email protected]',
subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "请检查: ${env.BUILD_URL}"
}
}
}

2. 脚本式 Pipeline

脚本式 Pipeline 提供更灵活的 Groovy 语法,适合复杂逻辑。

node {
try {
stage('Checkout') {
checkout scm
}

stage('Build') {
sh 'mvn clean package'
}

stage('Test') {
sh 'mvn test'
}

if (env.BRANCH_NAME == 'main') {
stage('Deploy') {
sh 'scp target/*.jar user@server:/opt/app/'
}
}

currentBuild.result = 'SUCCESS'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
junit 'target/surefire-reports/*.xml'
}
}

Pipeline 核心概念

Agent

Agent 指定 Pipeline 或 Stage 在哪个节点上运行。

pipeline {
// 在任何可用节点上运行
agent any

// 指定标签
agent { label 'linux && java11' }

// 使用 Docker 容器
agent {
docker {
image 'maven:3.8.6-openjdk-11'
args '-v /root/.m2:/root/.m2'
}
}

// Kubernetes Pod
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.6-openjdk-11
command: ['cat']
tty: true
'''
}
}
}

Stages 和 Stage

Stages 包含一个或多个 Stage,每个 Stage 代表一个阶段。

pipeline {
agent any

stages {
// 并行执行多个 Stage
stage('Parallel Stage') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test -Dtest=UnitTests'
}
}
stage('Integration Tests') {
steps {
sh 'mvn test -Dtest=IntegrationTests'
}
}
stage('Code Quality') {
steps {
sh 'mvn sonar:sonar'
}
}
}
}

// 顺序执行
stage('Build') {
steps {
sh 'mvn clean package'
}
}
}
}

Steps

Steps 定义在每个 Stage 中执行的具体操作。

stage('Example') {
steps {
// 执行 Shell 命令
sh 'echo "Hello World"'
sh '''
echo "多行命令"
ls -la
pwd
'''

// 执行 Windows 批处理
bat 'echo Hello'

// 执行 PowerShell
powershell 'Write-Output "Hello"'

// 打印信息
echo "当前构建号: ${env.BUILD_NUMBER}"

// 设置环境变量
script {
env.MY_VAR = 'some value'
}

// 使用工具
withMaven(maven: 'Maven-3.8') {
sh 'mvn clean install'
}

// 使用凭证
withCredentials([usernamePassword(
credentialsId: 'my-credentials',
usernameVariable: 'USER',
passwordVariable: 'PASS'
)]) {
sh 'echo $USER'
}
}
}

Environment

定义环境变量,可在整个 Pipeline 中使用。

pipeline {
agent any

environment {
// 普通变量
APP_NAME = 'myapp'
VERSION = '1.0.0'

// 从凭证获取
API_KEY = credentials('api-key-id')

// 动态设置
BUILD_DATE = sh(script: 'date +%Y%m%d', returnStdout: true).trim()
}

stages {
stage('Build') {
environment {
// Stage 级别的环境变量
STAGE_VAR = 'stage value'
}
steps {
echo "Building ${env.APP_NAME} v${env.VERSION}"
echo "Build date: ${env.BUILD_DATE}"
}
}
}
}

Post

定义 Pipeline 或 Stage 运行后的操作。

pipeline {
agent any

stages {
stage('Build') {
steps {
sh 'mvn clean package'
}
}
}

post {
// 总是执行
always {
cleanWs() // 清理工作空间
}

// 成功时执行
success {
echo '构建成功!'
slackSend color: 'good', message: '构建成功'
}

// 失败时执行
failure {
echo '构建失败!'
mail to: '[email protected]',
subject: "构建失败: ${env.JOB_NAME}",
body: "查看详情: ${env.BUILD_URL}"
}

// 不稳定时执行(如测试失败)
unstable {
echo '构建不稳定'
}

// 状态改变时执行
changed {
echo '构建状态发生变化'
}

// 成功或不稳定时执行
fixed {
echo '之前失败,现在成功了'
}

// 失败或不稳定时执行
regression {
echo '之前成功,现在失败了'
}
}
}

条件执行

When 指令

控制 Stage 的执行条件。

pipeline {
agent any

stages {
stage('Deploy to Dev') {
when {
branch 'develop'
}
steps {
sh 'deploy-to-dev.sh'
}
}

stage('Deploy to Prod') {
when {
allOf {
branch 'main'
environment name: 'DEPLOY_ENV', value: 'production'
}
}
steps {
sh 'deploy-to-prod.sh'
}
}

stage('Release') {
when {
anyOf {
branch 'main'
tag 'release-*'
}
}
steps {
sh 'create-release.sh'
}
}

stage('Manual Approval') {
when {
expression {
params.DEPLOY_PROD == true
}
}
steps {
input message: '确认部署到生产环境?', ok: '部署'
sh 'deploy.sh'
}
}
}
}

参数化构建

pipeline {
agent any

parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: '要构建的分支'
)

choice(
name: 'ENVIRONMENT',
choices: ['dev', 'staging', 'prod'],
description: '部署环境'
)

booleanParam(
name: 'RUN_TESTS',
defaultValue: true,
description: '是否运行测试'
)

password(
name: 'SECRET_KEY',
description: '密钥'
)

text(
name: 'DESCRIPTION',
defaultValue: '',
description: '构建描述'
)
}

stages {
stage('Build') {
steps {
echo "Building branch: ${params.BRANCH}"
echo "Environment: ${params.ENVIRONMENT}"

script {
if (params.RUN_TESTS) {
sh 'mvn test'
}
}
}
}
}
}

常用插件集成

Git 集成

stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/example/repo.git',
credentialsId: 'github-credentials'
}
}

Docker 集成

pipeline {
agent any

environment {
DOCKER_REGISTRY = 'registry.example.com'
IMAGE_NAME = 'myapp'
}

stages {
stage('Build Docker Image') {
steps {
script {
docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}")
}
}
}

stage('Push Docker Image') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'registry-credentials') {
docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}").push()
docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}").push('latest')
}
}
}
}
}
}

Kubernetes 集成

stage('Deploy to Kubernetes') {
steps {
withKubeConfig([credentialsId: 'kube-config']) {
sh '''
kubectl set image deployment/myapp \
myapp=registry.example.com/myapp:${BUILD_NUMBER}
kubectl rollout status deployment/myapp
'''
}
}
}

SonarQube 集成

stage('Code Quality') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar'
}
}
}

stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}

共享库(Shared Library)

将常用代码封装到共享库中,供多个 Pipeline 使用。

创建共享库

目录结构:

(shared-library)
├── vars/
│ ├── buildMaven.groovy
│ ├── deployKubernetes.groovy
│ └── sendNotification.groovy
├── src/
│ └── org/example/
│ └── Utils.groovy
└── resources/
└── scripts/
└── deploy.sh

定义步骤

// vars/buildMaven.groovy
def call(Map config = [:]) {
def goals = config.goals ?: 'clean install'
def options = config.options ?: ''

sh "mvn ${goals} ${options}"
}

使用共享库

@Library('my-shared-library') _

pipeline {
agent any

stages {
stage('Build') {
steps {
buildMaven(
goals: 'clean package',
options: '-DskipTests'
)
}
}

stage('Deploy') {
steps {
deployKubernetes(
namespace: 'production',
deployment: 'myapp'
)
}
}
}

post {
always {
sendNotification()
}
}
}

最佳实践

1. 使用 Jenkinsfile

将 Pipeline 代码存储在项目根目录的 Jenkinsfile 中:

// Jenkinsfile
pipeline {
agent any

stages {
stage('Build') {
steps {
sh 'make build'
}
}
}
}

2. 错误处理

stage('Deploy') {
steps {
script {
try {
sh 'deploy.sh'
} catch (Exception e) {
echo "部署失败: ${e.message}"
currentBuild.result = 'UNSTABLE'
}
}
}
}

3. 超时设置

stage('Long Running Task') {
options {
timeout(time: 10, unit: 'MINUTES')
}
steps {
sh 'long-running-script.sh'
}
}

4. 重试机制

stage('Flaky Test') {
steps {
retry(3) {
sh 'flaky-test.sh'
}
}
}

小结

  1. 声明式 Pipeline:结构化、易读、适合大多数场景
  2. 脚本式 Pipeline:灵活、适合复杂逻辑
  3. 核心概念:Agent、Stages、Steps、Environment、Post
  4. 条件执行:使用 When 控制 Stage 执行
  5. 参数化构建:支持多种参数类型
  6. 共享库:封装常用代码,提高复用性

练习

  1. 创建一个简单的 Jenkinsfile,实现代码检出、构建、测试流程
  2. 添加并行 Stage,同时运行单元测试和集成测试
  3. 配置条件部署,只在 main 分支部署到生产环境
  4. 创建一个共享库,封装常用的构建步骤