Jenkins pipeline例子
这个例子展示了一个包含构建、测试、安全扫描、多环境部署(预发/生产)和金丝雀发布的完整流程。
JAVA
pipeline {
agent {
kubernetes {
label 'backend-java-agent'
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.6-openjdk-17
command: ['cat']
tty: true
resources:
requests:
cpu: "1000m"
memory: "2Gi"
volumeMounts:
- name: maven-cache
mountPath: /root/.m2
- name: docker
image: docker:20.10
command: ['cat']
tty: true
securityContext:
privileged: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: kubectl
image: bitnami/kubectl:1.25
command: ['cat']
tty: true
volumes:
- name: maven-cache
persistentVolumeClaim:
claimName: maven-cache-pvc
- name: docker-sock
hostPath:
path: /var/run/docker.sock
"""
}
}
environment {
// 镜像仓库配置
REGISTRY = "harbor.yourcompany.com"
PROJECT = "myapp"
SERVICE = "backend"
IMAGE_NAME = "${REGISTRY}/${PROJECT}/${SERVICE}"
// 环境配置
K8S_NAMESPACE_DEV = "myapp-dev"
K8S_NAMESPACE_STAGING = "myapp-staging"
K8S_NAMESPACE_PROD = "myapp-prod"
// 动态生成的标签
BRANCH_NAME = "${env.GIT_BRANCH.replace('origin/', '')}"
COMMIT_SHORT = "${env.GIT_COMMIT.take(8)}"
BUILD_TIME = sh(script: "date +%Y%m%d-%H%M%S", returnStdout: true).trim()
IMAGE_TAG = "${BRANCH_NAME}-${COMMIT_SHORT}-${BUILD_TIME}"
}
stages {
// 阶段1: 代码检查与质量门禁
stage('代码检查') {
steps {
container('maven') {
script {
dir('backend') {
// 1.1 代码格式化检查
sh '''
mvn spotless:check
if [ $? -ne 0 ]; then
echo "代码格式不符合规范,请运行: mvn spotless:apply"
exit 1
fi
'''
// 1.2 依赖漏洞扫描
sh 'mvn org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7'
// 1.3 代码静态分析 (SonarQube)
withSonarQubeEnv('sonar-server') {
sh '''
mvn clean verify sonar:sonar \
-Dsonar.projectKey=myapp-backend \
-Dsonar.branch.name=${BRANCH_NAME}
'''
}
// 1.4 等待SonarQube质量门禁结果
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
}
// 阶段2: 构建与测试
stage('构建与测试') {
steps {
container('maven') {
script {
dir('backend') {
// 2.1 运行所有测试(单元测试 + 集成测试)
sh '''
mvn clean test \
-DskipITs=false \
-Dtest.parallel=classes \
-Dtest.threadCount=4
'''
// 2.2 生成测试报告和覆盖率
sh '''
mvn jacoco:report
mvn surefire-report:report-only
'''
// 2.3 构建JAR包(用于归档)
sh 'mvn clean package -DskipTests'
// 存档构建产物
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}
}
post {
always {
// 发布测试报告
junit 'backend/target/surefire-reports/*.xml'
publishHTML(target: [
reportName: '后端测试报告',
reportDir: 'backend/target/site',
reportFiles: 'surefire-report.html',
keepAll: true
])
}
}
}
// 阶段3: Docker镜像构建与安全扫描
stage('构建Docker镜像') {
when {
anyOf {
branch 'main'
branch 'develop'
expression { return env.CHANGE_ID } // MR/PR触发
}
}
steps {
script {
dir('backend') {
// 3.1 构建Docker镜像
sh """
docker build \
--build-arg JAR_FILE=target/*.jar \
--build-arg BUILD_TIME=${BUILD_TIME} \
--build-arg GIT_COMMIT=${env.GIT_COMMIT} \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-t ${IMAGE_NAME}:latest \
.
"""
// 3.2 镜像安全扫描 (Trivy)
sh """
trivy image \
--severity HIGH,CRITICAL \
--exit-code 1 \
--format template \
--template "@/usr/local/share/trivy/templates/html.tpl" \
-o trivy-report.html \
${IMAGE_NAME}:${IMAGE_TAG}
"""
// 3.3 推送镜像到仓库
withCredentials([usernamePassword(
credentialsId: 'harbor-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASSWORD'
)]) {
sh """
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD ${REGISTRY}
docker push ${IMAGE_NAME}:${IMAGE_TAG}
docker push ${IMAGE_NAME}:latest
"""
}
}
}
}
}
// 阶段4: 部署到开发环境(自动)
stage('部署到开发环境') {
when {
branch 'develop'
}
steps {
container('kubectl') {
script {
// 使用Kustomize进行环境配置管理
sh """
cd backend/k8s/overlays/development
kustomize edit set image backend=${IMAGE_NAME}:${IMAGE_TAG}
kubectl apply -k . -n ${K8S_NAMESPACE_DEV}
"""
// 等待部署完成
sh """
kubectl rollout status deployment/backend \
-n ${K8S_NAMESPACE_DEV} \
--timeout=300s
"""
// 运行冒烟测试
sh """
curl -f http://backend.${K8S_NAMESPACE_DEV}.svc.cluster.local:8080/actuator/health \
--retry 10 \
--retry-delay 5 \
--retry-max-time 60
"""
}
}
}
}
// 阶段5: 部署到预发环境(手动触发)
stage('部署到预发环境') {
when {
branch 'main'
}
input {
message "是否部署到预发环境?"
ok "确认部署"
parameters {
string(
name: 'DEPLOY_VERSION',
defaultValue: "${IMAGE_TAG}",
description: '要部署的镜像版本'
)
}
}
steps {
container('kubectl') {
script {
// 5.1 部署到预发环境
sh """
cd backend/k8s/overlays/staging
kustomize edit set image backend=${IMAGE_NAME}:${DEPLOY_VERSION}
kubectl apply -k . -n ${K8S_NAMESPACE_STAGING}
kubectl rollout status deployment/backend \
-n ${K8S_NAMESPACE_STAGING} \
--timeout=300s
"""
// 5.2 执行API集成测试
dir('backend') {
sh '''
mvn test-compile failsafe:integration-test \
-Dit.test="*IntegrationTest" \
-Dbase.url=http://backend.${K8S_NAMESPACE_STAGING}.svc.cluster.local:8080
'''
}
// 5.3 性能测试(采样)
sh """
wrk -t4 -c100 -d30s \
http://backend.${K8S_NAMESPACE_STAGING}.svc.cluster.local:8080/api/v1/products \
--latency
"""
}
}
}
}
// 阶段6: 金丝雀发布到生产环境
stage('生产环境金丝雀发布') {
when {
branch 'main'
beforeInput true
}
input {
message "是否开始生产环境金丝雀发布?"
ok "开始发布"
}
steps {
container('kubectl') {
script {
// 6.1 创建金丝雀部署(10%流量)
sh """
cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-canary
labels:
app: backend
version: canary-${IMAGE_TAG}
spec:
replicas: 1 # 10%的副本数
selector:
matchLabels:
app: backend
version: canary
template:
metadata:
labels:
app: backend
version: canary
spec:
containers:
- name: backend
image: ${IMAGE_NAME}:${IMAGE_TAG}
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
EOF
"""
// 6.2 等待金丝雀版本就绪
sleep time: 60 // 等待1分钟让应用启动
// 6.3 配置Istio流量分流(10%到金丝雀)
sh """
cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: backend-vs
spec:
hosts:
- backend.prod.yourcompany.com
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: backend-canary
port:
number: 8080
- route:
- destination:
host: backend
port:
number: 8080
weight: 90
- destination:
host: backend-canary
port:
number: 8080
weight: 10
EOF
"""
echo "金丝雀发布已启动。10%的流量将被导向新版本。"
echo "请监控以下指标:"
echo "1. 错误率: http://grafana.yourcompany.com/d/backend"
echo "2. 响应时间: http://grafana.yourcompany.com/d/latency"
echo "3. 业务指标: http://grafana.yourcompany.com/d/business"
// 6.4 等待监控验证(15分钟)
sleep time: 900
}
}
}
}
// 阶段7: 全量发布或回滚
stage('全量发布或回滚') {
steps {
container('kubectl') {
script {
// 检查金丝雀版本的健康状态
def canaryStatus = sh(
script: """
kubectl get pods -n ${K8S_NAMESPACE_PROD} \
-l version=canary \
-o jsonpath='{.items[*].status.containerStatuses[*].ready}' \
| grep -c true
""",
returnStdout: true
).trim()
def errorRate = sh(
script: """
curl -s http://prometheus.yourcompany.com/api/v1/query?query=rate(http_requests_total{status!~\"2..\"}[5m]) | jq '.data.result[0].value[1]'
""",
returnStdout: true
).trim()
if (canaryStatus == "1" && errorRate.toFloat() < 0.01) {
// 7.1 金丝雀健康,进行全量发布
input {
message "监控显示金丝雀版本运行正常。是否进行全量发布?"
ok "全量发布"
}
sh """
# 更新主部署
kubectl set image deployment/backend \
backend=${IMAGE_NAME}:${IMAGE_TAG} \
-n ${K8S_NAMESPACE_PROD}
kubectl rollout status deployment/backend \
-n ${K8S_NAMESPACE_PROD} \
--timeout=300s
# 清理金丝雀部署
kubectl delete deployment backend-canary -n ${K8S_NAMESPACE_PROD}
# 恢复流量配置
cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: backend-vs
spec:
hosts:
- backend.prod.yourcompany.com
http:
- route:
- destination:
host: backend
port:
number: 8080
EOF
"""
echo "✅ 全量发布完成"
} else {
// 7.2 金丝雀不健康,触发自动回滚
echo "⚠️ 金丝雀版本异常,触发自动回滚"
sh """
# 删除金丝雀部署
kubectl delete deployment backend-canary -n ${K8S_NAMESPACE_PROD}
# 恢复流量配置
cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: backend-vs
spec:
hosts:
- backend.prod.yourcompany.com
http:
- route:
- destination:
host: backend
port:
number: 8080
EOF
"""
// 发送告警通知
slackSend(
channel: '#prod-alerts',
color: 'danger',
message: "后端金丝雀发布失败,已自动回滚。版本: ${IMAGE_TAG}。错误率: ${errorRate}"
)
}
}
}
}
}
}
post {
always {
// 清理Docker镜像节省空间
sh """
docker image prune -f --filter \"until=24h\"
"""
// 归档安全扫描报告
archiveArtifacts artifacts: 'backend/trivy-report.html', allowEmptyArchive: true
}
success {
script {
if (env.BRANCH_NAME == 'main') {
slackSend(
channel: '#deployments',
color: 'good',
message: "后端生产部署成功!\n版本: ${IMAGE_TAG}\n构建: ${env.BUILD_URL}"
)
// 生成发布说明
sh """
echo "## 后端发布说明 ${IMAGE_TAG}" > release-notes.md
echo "构建时间: \$(date)" >> release-notes.md
echo "Git提交: ${env.GIT_COMMIT}" >> release-notes.md
echo "变更内容:" >> release-notes.md
git log --oneline -n 5 >> release-notes.md
"""
}
}
}
failure {
slackSend(
channel: '#build-failures',
color: 'danger',
message: "后端Pipeline失败!\nJob: ${env.JOB_NAME}\n构建: ${env.BUILD_NUMBER}\n查看: ${env.BUILD_URL}"
)
// 自动创建JIRA问题(如果严重失败)
script {
if (currentBuild.resultIsWorseOrEqualTo('FAILURE')) {
jiraNewIssue(
projectKey: 'DEV',
issueType: 'Bug',
summary: "Pipeline构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
description: "构建详情: ${env.BUILD_URL}",
priorityName: 'High'
)
}
}
}
}
}前端
pipeline {
agent {
docker {
image 'node:18-alpine'
args '-p 5173:5173' // Vite开发服务器端口
}
}
environment {
// 前端专用环境变量
APP_NAME = "myapp-frontend"
NPM_REGISTRY = "https://registry.npmmirror.com"
S3_BUCKET = "myapp-static-assets"
CLOUDFRONT_ID = "E2ABCDEFGHIJK"
// 构建产物路径
DIST_DIR = "dist"
BUILD_MODE = "${env.BRANCH_NAME == 'main' ? 'production' : 'staging'}"
}
stages {
stage('依赖安装与缓存') {
steps {
script {
dir('frontend') {
// 配置npm镜像源
sh "npm config set registry ${NPM_REGISTRY}"
// 检查package-lock.json变化
sh """
if [ -f package-lock.json ]; then
npm ci --prefer-offline
else
npm install --frozen-lockfile
fi
"""
// 缓存node_modules
stash name: 'node_modules', includes: 'node_modules/**'
}
}
}
}
stage('代码质量检查') {
steps {
script {
dir('frontend') {
// ESLint代码检查
sh """
npx eslint "src/**/*.{js,jsx,vue,ts,tsx}" \
--format html \
-o eslint-report.html || true
"""
// TypeScript类型检查
sh 'npx tsc --noEmit'
// 单元测试(Jest/Vitest)
sh """
npm test -- \
--coverage \
--coverageReporters=html \
--passWithNoTests
"""
// 样式检查(Stylelint)
sh 'npx stylelint "src/**/*.{css,scss,vue}"'
}
}
}
post {
always {
// 发布代码质量报告
publishHTML(target: [
reportName: 'ESLint报告',
reportDir: 'frontend/',
reportFiles: 'eslint-report.html',
keepAll: true
])
// 发布测试覆盖率报告
publishHTML(target: [
reportName: '测试覆盖率',
reportDir: 'frontend/coverage/',
reportFiles: 'index.html',
keepAll: true
])
}
}
}
stage('构建与优化') {
steps {
script {
dir('frontend') {
// 根据环境变量生成配置
sh """
cat > .env.${BUILD_MODE} << EOF
VITE_APP_VERSION=${env.BUILD_ID}
VITE_APP_BUILD_TIME=$(date +%Y-%m-%dT%H:%M:%S)
VITE_API_BASE_URL=${env.BRANCH_NAME == 'main' ? 'https://api.prod.yourcompany.com' : 'https://api.staging.yourcompany.com'}
VITE_ENABLE_ANALYTICS=${env.BRANCH_NAME == 'main' ? 'true' : 'false'}
EOF
"""
// 执行构建
sh """
npm run build:${BUILD_MODE} || \
npm run build -- --mode ${BUILD_MODE}
"""
// 构建产物分析
sh 'npx vite-bundle-analyzer dist/stats.html'
// 检查构建产物
sh """
echo "构建产物大小:"
du -sh dist/
echo "文件数量:"
find dist/ -type f | wc -l
"""
}
}
}
}
stage('Docker镜像构建') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
// 多阶段Docker构建
sh """
docker build \
--target=${BUILD_MODE} \
--build-arg BUILD_MODE=${BUILD_MODE} \
-t frontend:${env.BUILD_ID} \
-f frontend/Dockerfile \
frontend/
"""
// 镜像扫描
sh 'trivy image --severity HIGH,CRITICAL frontend:${env.BUILD_ID}'
// 推送到仓库
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASSWORD'
)]) {
sh """
docker tag frontend:${env.BUILD_ID} ${REGISTRY}/frontend:${IMAGE_TAG}
docker push ${REGISTRY}/frontend:${IMAGE_TAG}
"""
}
}
}
}
stage('部署到CDN(静态资源)') {
when {
branch 'main'
}
steps {
script {
withAWS(credentials: 'aws-jenkins', region: 'us-east-1') {
// 上传到对象存储并设置缓存策略,以AWS S3为例,可换成阿里云/腾讯云等
sh """
aws s3 sync frontend/${DIST_DIR}/ s3://${S3_BUCKET}/v${env.BUILD_ID}/ \
--delete \
--cache-control "public, max-age=31536000"
# 上传index.html(无缓存)
aws s3 cp frontend/${DIST_DIR}/index.html s3://${S3_BUCKET}/v${env.BUILD_ID}/ \
--cache-control "no-cache"
"""
// 创建CloudFront失效
sh """
aws cloudfront create-invalidation \
--distribution-id ${CLOUDFRONT_ID} \
--paths "/v${env.BUILD_ID}/*"
"""
}
}
}
}
stage('部署到K8s') {
steps {
container('kubectl') {
script {
dir('frontend/k8s') {
// 使用Kustomize更新镜像标签
sh """
kustomize edit set image frontend=${REGISTRY}/frontend:${IMAGE_TAG}
# 部署到对应环境
if [ "${env.BRANCH_NAME}" = "main" ]; then
kubectl apply -k overlays/production
kubectl rollout status deployment/frontend -n production
else
kubectl apply -k overlays/staging
kubectl rollout status deployment/frontend -n staging
fi
"""
}
}
}
}
}
stage('端到端测试') {
when {
branch 'main'
}
steps {
script {
dir('frontend') {
// 使用Cypress进行E2E测试
sh """
npx cypress run \
--config baseUrl=https://staging.yourcompany.com \
--record \
--key \$CYPRESS_KEY \
--parallel \
--ci-build-id ${env.BUILD_ID}
"""
}
}
}
}
}
post {
always {
// 清理工作空间
cleanWs()
// 存档构建产物
archiveArtifacts artifacts: "frontend/${DIST_DIR}/**", fingerprint: true
}
success {
// 发送构建成功通知
slackSend(
color: 'good',
message: "前端${BUILD_MODE}构建成功!\n版本: v${env.BUILD_ID}\n预览: https://${env.BRANCH_NAME == 'main' ? 'prod' : 'staging'}.yourcompany.com"
)
}
}
}0