第六章:Pipeline 实战
常见场景的 Pipeline 模板和最佳实践,包括 Java、Node.js、Python、Go 等项目。
最后更新: 2024-01-15
页面目录
Pipeline 实战
本章提供多种常见场景的 Pipeline 模板,帮助您快速构建适合自己项目的流水线。
Java/Maven 项目
基础 Maven Pipeline
// Jenkinsfile for Maven Project
@Library('shared-library') _
pipeline {
agent {
docker {
image 'maven:3.8-openjdk-11'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}
options {
timestamps()
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
}
parameters {
choice(name: 'MAVEN_GOAL', choices: ['package', 'install', 'deploy'], description: 'Maven 目标')
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
}
environment {
MAVEN_OPTS = '-Xmx512m'
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
}
}
}
stage('Build') {
steps {
sh 'mvn clean ${MAVEN_GOAL} ${SKIP_TESTS ? "-DskipTests" : ""}'
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true, onlyIfSuccessful: true
}
}
}
stage('Test') {
when {
expression { !params.SKIP_TESTS }
}
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/**/*.xml'
jacoco execPattern: 'target/jacoco.exec'
}
}
}
stage('Code Quality') {
steps {
sh 'mvn verify -DskipTests -Dspotbugs.skip=true -Dcheckstyle.skip=true'
recordIssues(
enabledForFailure: true,
tools: [java(), javaDoc()],
qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
)
}
}
stage('Docker Build & Push') {
when {
branch 'main'
}
steps {
script {
def imageTag = "${env.REGISTRY}/${env.IMAGE_NAME}:${env.GIT_COMMIT_SHORT}"
def imageLatest = "${env.REGISTRY}/${env.IMAGE_NAME}:latest"
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin ${env.REGISTRY}
docker build -t ${imageTag} -t ${imageLatest} .
docker push ${imageTag}
docker push ${imageLatest}
"""
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
sh '''
kubectl config use-context staging
kubectl set image deployment/app app=${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT_SHORT}
kubectl rollout status deployment/app
'''
}
}
}
post {
success {
emailext(
subject: "✅ 构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
项目: ${env.JOB_NAME}
构建号: ${env.BUILD_NUMBER}
分支: ${env.BRANCH_NAME}
Commit: ${env.GIT_COMMIT_SHORT}
状态: 成功
""",
recipientProviders: [developers(), requestor()]
)
}
failure {
emailext(
subject: "❌ 构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
项目: ${env.JOB_NAME}
构建号: ${env.BUILD_NUMBER}
分支: ${env.BRANCH_NAME}
状态: 失败
日志: ${env.BUILD_URL}console
""",
recipientProviders: [developers(), requestor()]
)
}
always {
cleanWs()
}
}
}
多模块 Maven 项目
pipeline {
agent { label 'maven' }
stages {
stage('Build All Modules') {
steps {
sh 'mvn clean install -DskipTests'
}
}
stage('Test') {
parallel {
stage('Module A Tests') {
steps {
dir('module-a') {
sh 'mvn test'
}
}
}
stage('Module B Tests') {
steps {
dir('module-b') {
sh 'mvn test'
}
}
}
}
}
}
}
Node.js/JavaScript 项目
NPM 项目 Pipeline
// Jenkinsfile for Node.js Project
pipeline {
agent {
docker {
image 'node:18-alpine'
args '-v $HOME/.npm:/root/.npm'
}
}
environment {
NODE_ENV = 'test'
REGISTRY = 'registry.example.com'
}
stages {
stage('Install Dependencies') {
steps {
sh '''
npm config set registry https://registry.npmmirror.com
npm ci --legacy-peer-deps
'''
stash 'node_modules'
}
}
stage('Lint') {
steps {
unstash 'node_modules'
sh '''
npm run lint || true
npm run lint:style
'''
recordIssues(
enabledForFailure: true,
tools: [eslint()],
qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
)
}
}
stage('Build') {
steps {
unstash 'node_modules'
sh '''
npm run build
'''
stash name: 'build-output', includes: 'dist/**/*'
}
}
stage('Test') {
steps {
unstash 'node_modules'
sh '''
npm run test:unit
npm run test:e2e
'''
}
post {
always {
junit 'coverage/junit/*.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepOnNewChange: true,
reportDir: 'coverage/html',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Docker Build') {
steps {
unstash 'build-output'
script {
def imageName = "${env.REGISTRY}/frontend:${env.BUILD_NUMBER}"
sh """
docker build -t ${imageName} -t ${env.REGISTRY}/frontend:latest .
docker push ${imageName}
"""
}
}
}
}
post {
cleanup {
sh 'npm cache clean --force'
}
}
}
Python 项目
Django/Python Pipeline
// Jenkinsfile for Python/Django Project
pipeline {
agent {
docker {
image 'python:3.11-slim'
args '-v $HOME/.cache/pip:/root/.cache/pip'
}
}
environment {
PYTHONUNBUFFERED = '1'
DATABASE_URL = credentials('database-url')
REDIS_URL = credentials('redis-url')
}
stages {
stage('Setup') {
steps {
sh '''
python -m venv venv
. venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
'''
}
}
stage('Lint') {
steps {
sh '''
. venv/bin/activate
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
black --check --diff .
isort --check-only --diff .
'''
}
}
stage('Test') {
steps {
sh '''
. venv/bin/activate
pytest --cov=myapp --cov-report=xml --cov-report=html
'''
}
post {
always {
junit 'test-results/*.xml'
cobertura coberturaReportFile: 'coverage.xml'
}
}
}
stage('Security Scan') {
steps {
sh '''
. venv/bin/activate
safety check
bandit -r myapp
'''
}
}
stage('Build Docker') {
steps {
sh '''
docker build -t myapp:${BUILD_NUMBER} .
docker tag myapp:${BUILD_NUMBER} myapp:latest
'''
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh '''
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp \
-e DATABASE_URL=${DATABASE_URL} \
-e REDIS_URL=${REDIS_URL} \
-p 8000:8000 \
myapp:latest
'''
}
}
}
}
Go 项目
Go 应用 Pipeline
// Jenkinsfile for Go Project
pipeline {
agent {
docker {
image 'golang:1.21-alpine'
args '-v $HOME/go:/go -w /workspace'
}
}
environment {
CGO_ENABLED = '0'
GOOS = 'linux'
GOARCH = 'amd64'
REGISTRY = 'registry.example.com'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Dependencies') {
steps {
sh '''
go mod download
go mod verify
'''
}
}
stage('Lint') {
steps {
sh '''
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run ./...
'''
}
}
stage('Build') {
steps {
sh '''
go build -ldflags="-s -w" -o bin/app .
file bin/app
'''
}
post {
success {
archiveArtifacts artifacts: 'bin/app', fingerprint: true
}
}
}
stage('Test') {
steps {
sh '''
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
'''
}
post {
always {
junit '**/test-results/*.xml'
publishHTML([
reportDir: '.',
reportFiles: 'coverage.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build Docker Image') {
steps {
script {
def image = "${env.REGISTRY}/app:${env.BUILD_NUMBER}"
sh """
docker build -t ${image} .
docker push ${image}
"""
}
}
}
stage('Helm Deploy') {
when {
branch 'main'
}
steps {
sh '''
helm upgrade --install myapp ./chart \
--set image.tag=${BUILD_NUMBER} \
--wait --timeout 5m
'''
}
}
}
}
多环境部署 Pipeline
GitOps 风格部署
// GitOps Pipeline
pipeline {
agent any
parameters {
choice(
name: 'ENV',
choices: ['dev', 'staging', 'production'],
description: '部署环境'
)
string(
name: 'VERSION',
defaultValue: '',
description: '版本号(留空使用最新)'
)
}
environment {
KUBECONFIG = credentials('kubeconfig')
}
stages {
stage('Validate Environment') {
steps {
script {
assert env.ENV : "ENV 参数不能为空"
def allowedEnvs = ['dev', 'staging', 'production']
assert env.ENV in allowedEnvs : "不支持的环境: ${env.ENV}"
if (env.ENV == 'production' && env.BRANCH_NAME != 'main') {
error "生产环境部署必须使用 main 分支"
}
}
}
}
stage('Prepare') {
steps {
script {
env.IMAGE_TAG = params.VERSION ?: env.GIT_COMMIT_SHORT
env.HELM_VALUES_FILE = "values-${env.ENV}.yaml"
}
checkout scm
}
}
stage('Deploy to ${ENV}') {
steps {
sh '''
kubectl config use-context ${ENV}
helm upgrade --install myapp ./charts/myapp \
--namespace ${ENV} \
--create-namespace \
--values ${HELM_VALUES_FILE} \
--set image.tag=${IMAGE_TAG} \
--wait \
--timeout 10m \
--debug
'''
}
}
stage('Verify Deployment') {
steps {
script {
def timeout = 300 // 5 minutes
def interval = 10
def elapsed = 0
while (elapsed < timeout) {
def status = sh(
script: """
kubectl get pod -n ${ENV} -l app=myapp \
-o jsonpath='{.items[0].status.phase}'
""",
returnStdout: true
).trim()
if (status == 'Running') {
echo "应用已成功启动"
break
}
sleep interval
elapsed += interval
}
if (elapsed >= timeout) {
error "应用启动超时"
}
}
}
}
stage('Health Check') {
steps {
sh '''
curl -f http://myapp.${ENV}.example.com/health || exit 1
'''
}
}
}
post {
success {
echo "部署成功!环境: ${ENV}, 版本: ${IMAGE_TAG}"
}
failure {
script {
def rollbackCmd = "helm rollback myapp -n ${ENV}"
echo "部署失败,可执行回滚: ${rollbackCmd}"
}
}
}
}
微服务 Pipeline
Monorepo 微服务架构
// Jenkinsfile for Microservices
@Library('shared-library') _
def services = [
[name: 'user-service', port: 8081, healthPath: '/actuator/health'],
[name: 'order-service', port: 8082, healthPath: '/actuator/health'],
[name: 'payment-service', port: 8083, healthPath: '/health'],
[name: 'notification-service', port: 8084, healthPath: '/health']
]
def changedServices = []
pipeline {
agent none
options {
timestamps()
timeout(time: 2, unit: 'HOURS')
disableConcurrentBuilds()
}
stages {
stage('Detect Changed Services') {
steps {
script {
def changedFiles = sh(
script: 'git diff --name-only $COMMIT_RANGE',
returnStdout: true
).split('\n')
services.each { svc ->
def svcName = svc.name
if (changedFiles.any { it.startsWith(svcName) || it == "services/${svcName}/Dockerfile" }) {
changedServices.add(svc)
echo "检测到变更: ${svcName}"
}
}
if (changedServices.isEmpty()) {
echo "没有检测到服务变更,跳过构建"
}
}
}
}
stage('Build Changed Services') {
when {
expression { !changedServices.isEmpty() }
}
steps {
script {
def builds = [:]
changedServices.each { svc ->
def service = svc
builds[service.name] = {
node('docker') {
stage("${service.name} - Build") {
dir("services/${service.name}") {
sh """
docker build -t ${service.name}:${BUILD_NUMBER} .
docker tag ${service.name}:${BUILD_NUMBER} ${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
docker push ${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
"""
}
}
}
}
}
parallel builds
}
}
}
stage('Integration Tests') {
when {
expression { !changedServices.isEmpty() }
}
steps {
script {
def tests = [:]
changedServices.each { svc ->
def service = svc
tests[service.name] = {
node('docker') {
stage("${service.name} - Test") {
sh """
docker-compose -f docker-compose.test.yml up -d ${service.name}
sleep 10
curl -f http://localhost:${service.port}${service.healthPath}
docker-compose -f docker-compose.test.yml down
"""
}
}
}
}
parallel tests
}
}
}
stage('Deploy') {
when {
allOf {
branch 'main'
expression { !changedServices.isEmpty() }
}
}
steps {
script {
def deploys = [:]
changedServices.each { svc ->
def service = svc
deploys[service.name] = {
sh """
kubectl set image deployment/${service.name} \
${service.name}=${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
kubectl rollout status deployment/${service.name}
"""
}
}
parallel deploys
}
}
}
}
}
下一步
接下来让我们学习如何管理 Jenkins 节点和代理。
👉 节点管理
💡 提示:建议将这些 Pipeline 模板保存到共享库中,方便团队复用。