AI摘要
第一部分:痛点与架构设计
为什么需要自动化流水线?
我最初接触持续集成时,团队是这样发布应用的:
- 下午6点,在群里喊“开始发布,大家别提交代码”
- 负责人手动执行
mvn clean package -DskipTests - 将打包好的jar通过scp传到服务器
- 登录服务器,备份旧版本,停止服务
- 启动新版本,观察日志是否正常
- 如果有问题,手动回滚到上一个版本
这个过程至少要30分钟,而且容易出错。更糟糕的是,如果同时有多个服务要发布,需要串行操作,整个发布过程可能持续2-3小时。
我们的技术栈选择
我们选择了这样的组合:
- Jenkins:流水线编排的核心,选择它是因为插件生态丰富,能集成我们现有的所有工具
- Docker:解决环境一致性问题
- Docker Compose:多服务编排(测试环境)
- Harbor:私有镜像仓库
- SonarQube:代码质量检查
- Nexus:私有Maven仓库
- Kubernetes(可选):生产环境容器编排
以下是完整的架构图:
开发者推送代码
↓
Git Webhook触发
↓
Jenkins流水线启动
↓
├── 代码质量检查 (SonarQube)
├── 单元测试 (JUnit)
├── 构建Docker镜像
├── 镜像安全扫描 (Trivy)
├── 推送到Harbor仓库
└── 部署到不同环境
├── 开发环境 (Docker Compose)
├── 测试环境 (Docker Compose)
└── 生产环境 (Kubernetes)第二部分:基础环境搭建的细节
1. Jenkins的“正确”安装方式
很多教程直接使用apt-get install jenkins,但这种方式在管理和升级时很麻烦。我们使用Docker安装:
# 创建Jenkins数据目录和配置目录
mkdir -p /data/jenkins/{home,config}
chmod -R 1000:1000 /data/jenkins
# 创建docker-compose.yml文件
cat > /data/jenkins/docker-compose.yml << EOF
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
user: root # 避免权限问题
ports:
- "8080:8080"
- "50000:50000"
volumes:
- /data/jenkins/home:/var/jenkins_home
- /data/jenkins/config:/var/jenkins_config
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker # 挂载docker客户端
- /usr/local/bin/docker-compose:/usr/local/bin/docker-compose
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai
- JENKINS_OPTS=--prefix=/jenkins # 如果你使用反向代理
restart: unless-stopped
EOF
# 启动Jenkins
cd /data/jenkins && docker-compose up -d
# 查看初始密码
docker logs jenkins 2>&1 | grep "Please use the following password"这里的关键点:
- 挂载
/var/run/docker.sock让Jenkins能使用宿主机的Docker引擎 - 同时挂载docker和docker-compose二进制文件
- 配置时区,避免日志时间混乱
- 使用docker-compose管理,方便后续升级
2. Jenkins初始配置的坑
首次访问http://服务器IP:8080后,需要:
安装推荐插件时的网络问题:
由于网络原因,插件安装经常失败。我们有三种解决方案:
# 方案1:使用清华镜像源(在Jenkins容器内执行)
docker exec -it jenkins bash
# 修改更新中心地址
sed -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' /var/jenkins_home/hudson.model.UpdateCenter.xml
sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' /var/jenkins_home/hudson.model.UpdateCenter.xml
# 方案2:手动上传插件(如果网络实在不行)
# 从 https://mirrors.tuna.tsinghua.edu.cn/jenkins/plugins/ 下载以下核心插件:
# - pipeline
# - git
# - docker
# - docker-pipeline
# - blueocean(可视化流水线)
# 然后在 Jenkins 的"管理插件" -> "高级" -> "上传插件"中安装
# 方案3:使用代理(如果有的话)
# 在Jenkins启动参数中添加代理
JAVA_OPTS="-Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080"必须安装的插件列表:
- Pipeline - 流水线核心插件
- Docker Pipeline - Docker集成
- Blue Ocean - 可视化流水线界面
- Git Parameter - 构建参数化,支持选择Git分支
- Build Timestamp - 在构建时添加时间戳
- Pipeline Utility Steps - 流水线工具步骤
- SonarQube Scanner - 代码质量检查
- Email Extension - 邮件通知
3. Docker环境的准备
Docker Daemon配置优化:
# 编辑docker配置文件
sudo vim /etc/docker/daemon.json
# 添加以下配置
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
],
"insecure-registries": [
"192.168.1.100:8083" # 我们的私有Harbor仓库地址
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"data-root": "/data/docker" # Docker数据目录,避免系统盘爆满
}
# 重启docker
sudo systemctl daemon-reload
sudo systemctl restart dockerDocker Compose安装:
# 下载最新版本(这里以2.20.0为例)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version第三部分:项目改造与Dockerfile优化
1. 多阶段构建的Dockerfile详解
对于Spring Boot项目,我们经历了三个版本的Dockerfile优化:
版本1:最简单的Dockerfile(问题:构建慢、镜像大)
FROM openjdk:8-jdk
COPY . /app
WORKDIR /app
RUN ./mvnw clean package -DskipTests
CMD ["java", "-jar", "target/app.jar"]问题分析:
- 每次构建都要下载所有Maven依赖
- 镜像包含JDK(约500MB),而运行只需要JRE
- 源码在镜像中,存在安全风险
版本2:使用阿里云镜像加速(问题:依赖重复下载)
FROM maven:3.8.5-openjdk-11 AS build
COPY . /app
WORKDIR /app
RUN mvn clean package -DskipTests \
-Dmaven.test.skip=true \
-Dmaven.wagon.http.ssl.insecure=true \
-Dmaven.wagon.http.ssl.allowall=true
FROM openjdk:11-jre-slim
COPY --from=build /app/target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]改进:
- 使用多阶段构建,最终镜像只包含JRE
- 最终镜像大小从500MB降到约150MB
版本3:最优化的Dockerfile(利用分层缓存)
# 第一阶段:构建依赖层(最大程度利用缓存)
FROM maven:3.8.5-openjdk-11 AS deps
WORKDIR /build
# 单独复制pom.xml,利用缓存
COPY pom.xml .
# 下载依赖(-o 离线模式,-B 批处理模式)
RUN mvn dependency:go-offline -B
# 第二阶段:构建应用
FROM deps AS builder
COPY src ./src
# 编译打包(跳过测试)
RUN mvn clean package -DskipTests \
-Dmaven.test.skip=true \
-Dmaven.wagon.http.ssl.insecure=true \
-Dmaven.wagon.http.ssl.allowall=true
# 第三阶段:运行时镜像
FROM openjdk:11-jre-slim AS runtime
# 创建非root用户(安全最佳实践)
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring
WORKDIR /app
# 复制jar包
COPY --from=builder --chown=spring:spring /build/target/*.jar app.jar
# JVM参数优化
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=120s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar app.jar"]关键优化点:
- 分层缓存:将依赖下载和源码编译分离,只要
pom.xml不变,就不重新下载依赖 - 多阶段构建:最终镜像只包含运行时环境
- 使用非root用户:提高安全性
- JVM参数优化:设置合理的堆内存和GC参数
- 健康检查:Docker能监控应用健康状态
2. .dockerignore文件的重要性
很多人忽略.dockerignore文件,但它能显著减少构建上下文大小,加速构建过程:
# 忽略git相关
.git
.gitignore
# 忽略IDE文件
.idea
*.iml
.vscode
# 忽略日志和缓存
logs/
*.log
target/
build/
out/
# 忽略配置文件(通常通过环境变量或挂载方式注入)
*.yml
*.properties
*.config
# 忽略测试相关
*.test
*.spec
# 忽略文档
*.md
docs/第四部分:Jenkins流水线深度配置
1. Jenkins凭据(Credentials)管理
安全地管理敏感信息是CI/CD的关键。在Jenkins中配置凭据:
步骤1:创建Docker Hub凭据
类型:Username with password
ID:docker-hub-cred
用户名:你的DockerHub用户名
密码:你的DockerHub密码或Access Token(更安全)步骤2:创建Git凭据
类型:SSH Username with private key
ID:git-ssh-key
用户名:git
私钥:粘贴你的SSH私钥内容步骤3:创建服务器SSH凭据
类型:SSH Username with private key
ID:prod-server-ssh
用户名:deploy
私钥:部署服务器的SSH私钥步骤4:创建SonarQube Token
# 在SonarQube中生成Token
# 然后在Jenkins中配置
类型:Secret text
ID:sonarqube-token
Secret:粘贴你的SonarQube Token2. Jenkins Shared Libraries(共享库)
当有多个项目使用相似的流水线时,使用共享库避免代码重复:
目录结构:
jenkins-shared-library/
├── vars/
│ └── buildPipeline.groovy # 自定义步骤
├── src/
│ └── org/
│ └── company/
│ └── BuildTools.groovy # 工具类
└── resources/ # 非Groovy文件示例:buildPipeline.groovy
def call(Map config = [:]) {
pipeline {
agent any
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['dev', 'test', 'staging'],
description: '选择部署环境'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '是否跳过测试'
)
}
stages {
stage('代码检查') {
steps {
script {
// 调用共享库中的方法
checkoutCode()
runCodeAnalysis()
}
}
}
stage('构建') {
steps {
script {
buildAndPushImage(config.imageName)
}
}
}
stage('部署') {
steps {
script {
deployToEnv(params.DEPLOY_ENV)
}
}
}
}
post {
success {
sendNotification('构建成功', 'green')
}
failure {
sendNotification('构建失败', 'red')
}
}
}
}
// 在Jenkins中配置共享库
// 管理Jenkins -> 系统配置 -> Global Pipeline Libraries
// Name: company-lib
// Default version: main
// Retrieval method: Modern SCM
// Source Code Management: Git3. 完整的Jenkinsfile示例
以下是一个企业级Spring Boot项目的完整Jenkinsfile:
pipeline {
agent {
docker {
image 'maven:3.8.5-openjdk-11'
args '-v /root/.m2:/root/.m2 -v /var/run/docker.sock:/var/run/docker.sock'
reuseNode true
}
}
options {
// 保留最近10次构建记录
buildDiscarder(logRotator(numToKeepStr: '10'))
// 超时设置(2小时)
timeout(time: 2, unit: 'HOURS')
// 禁止并行执行(同一时间只能有一个构建)
disableConcurrentBuilds()
// 添加时间戳到控制台输出
timestamps()
}
parameters {
choice(
name: 'BRANCH',
choices: ['develop', 'release', 'main'],
description: '选择要构建的分支'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '跳过测试'
)
booleanParam(
name: 'SKIP_SONAR',
defaultValue: false,
description: '跳过代码质量检查'
)
string(
name: 'IMAGE_TAG',
defaultValue: 'latest',
description: 'Docker镜像标签'
)
}
environment {
// 项目信息
PROJECT_NAME = 'user-service'
GROUP_ID = 'com.example'
ARTIFACT_ID = 'user-service'
// Docker配置
DOCKER_REGISTRY = 'harbor.example.com'
DOCKER_PROJECT = 'microservices'
DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_PROJECT}/${PROJECT_NAME}"
// SonarQube配置
SONAR_HOST_URL = 'http://sonarqube.example.com:9000'
// Nexus配置
NEXUS_URL = 'http://nexus.example.com:8081'
MAVEN_SETTINGS = credentials('maven-settings')
// 其他
BUILD_TIMESTAMP = sh(script: "date '+%Y%m%d%H%M%S'", returnStdout: true).trim()
GIT_COMMIT_SHORT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
}
stages {
stage('准备') {
steps {
script {
echo "========== 构建信息 =========="
echo "项目: ${PROJECT_NAME}"
echo "分支: ${params.BRANCH}"
echo "提交: ${env.GIT_COMMIT_SHORT}"
echo "镜像: ${DOCKER_IMAGE}:${params.IMAGE_TAG}"
echo "================================"
// 检查Docker服务
sh 'docker --version'
sh 'docker-compose --version'
}
}
}
stage('代码检出') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "*/${params.BRANCH}"]],
extensions: [
// 清理工作空间
[$class: 'CleanBeforeCheckout'],
// 设置超时
[$class: 'CloneOption', timeout: 30],
// 设置浅克隆(加速)
[$class: 'CloneOption', depth: 1, shallow: true]
],
userRemoteConfigs: [[
credentialsId: 'git-ssh-key',
url: 'git@github.com:company/user-service.git'
]]
])
// 获取最近提交信息
script {
LAST_COMMIT = sh(script: "git log -1 --pretty=format:'%h - %s'", returnStdout: true).trim()
echo "最近提交: ${LAST_COMMIT}"
}
}
}
stage('代码质量检查') {
when {
expression { return !params.SKIP_SONAR.toBoolean() }
}
steps {
withSonarQubeEnv('sonarqube') {
sh """
mvn clean compile sonar:sonar \
-Dsonar.projectKey=${PROJECT_NAME} \
-Dsonar.projectName=${PROJECT_NAME} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.java.binaries=target/classes
"""
}
}
}
stage('等待质量门检查') {
when {
expression { return !params.SKIP_SONAR.toBoolean() }
}
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('依赖下载') {
steps {
sh '''
echo "使用本地Maven缓存..."
mvn dependency:go-offline -B -s $MAVEN_SETTINGS
'''
}
}
stage('单元测试') {
when {
expression { return !params.SKIP_TESTS.toBoolean() }
}
steps {
sh '''
echo "运行单元测试..."
mvn test -B -s $MAVEN_SETTINGS
'''
// 收集测试报告
post {
always {
junit 'target/surefire-reports/**/*.xml'
archiveArtifacts 'target/surefire-reports/**/*'
}
}
}
}
stage('集成测试') {
when {
expression { return !params.SKIP_TESTS.toBoolean() }
}
steps {
sh '''
echo "启动测试环境..."
docker-compose -f docker-compose.test.yml up -d
echo "等待服务启动..."
sleep 30
echo "运行集成测试..."
mvn verify -B -s $MAVEN_SETTINGS -Pintegration-test
'''
post {
always {
echo "清理测试环境..."
sh 'docker-compose -f docker-compose.test.yml down -v'
}
}
}
}
stage('编译打包') {
steps {
sh '''
echo "编译打包..."
mvn clean package -B -s $MAVEN_SETTINGS -DskipTests
echo "检查生成的jar包..."
ls -lh target/*.jar
'''
}
}
stage('构建Docker镜像') {
steps {
script {
// 构建镜像
sh """
docker build \
--build-arg BUILD_VERSION=${BUILD_TIMESTAMP} \
--build-arg GIT_COMMIT=${GIT_COMMIT_SHORT} \
-t ${DOCKER_IMAGE}:${params.IMAGE_TAG} \
-t ${DOCKER_IMAGE}:${BUILD_TIMESTAMP} \
-t ${DOCKER_IMAGE}:${GIT_COMMIT_SHORT} \
.
"""
// 查看镜像信息
sh "docker images | grep ${PROJECT_NAME}"
}
}
}
stage('镜像安全扫描') {
steps {
script {
// 使用Trivy进行安全扫描
sh """
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/trivy-cache:/root/.cache \
aquasec/trivy image \
--severity HIGH,CRITICAL \
--exit-code 1 \
--no-progress \
${DOCKER_IMAGE}:${params.IMAGE_TAG}
"""
}
}
}
stage('推送镜像') {
steps {
script {
// 登录私有仓库
withCredentials([usernamePassword(
credentialsId: 'harbor-cred',
passwordVariable: 'DOCKER_PASSWORD',
usernameVariable: 'DOCKER_USERNAME'
)]) {
sh """
echo \$DOCKER_PASSWORD | docker login ${DOCKER_REGISTRY} \
-u \$DOCKER_USERNAME \
--password-stdin
"""
}
// 推送镜像
sh """
docker push ${DOCKER_IMAGE}:${params.IMAGE_TAG}
docker push ${DOCKER_IMAGE}:${BUILD_TIMESTAMP}
docker push ${DOCKER_IMAGE}:${GIT_COMMIT_SHORT}
"""
// 清理本地镜像
sh """
docker rmi ${DOCKER_IMAGE}:${params.IMAGE_TAG} || true
docker rmi ${DOCKER_IMAGE}:${BUILD_TIMESTAMP} || true
docker rmi ${DOCKER_IMAGE}:${GIT_COMMIT_SHORT} || true
"""
}
}
}
stage('部署到开发环境') {
when {
expression {
return params.BRANCH == 'develop'
}
}
steps {
script {
withCredentials([sshUserPrivateKey(
credentialsId: 'dev-server-ssh',
keyFileVariable: 'SSH_KEY'
)]) {
sh """
ssh -o StrictHostKeyChecking=no -i $SSH_KEY deploy@dev-server << 'EOF'
# 拉取最新镜像
docker pull ${DOCKER_IMAGE}:${params.IMAGE_TAG}
# 停止并删除旧容器
docker stop ${PROJECT_NAME} || true
docker rm ${PROJECT_NAME} || true
# 运行新容器
docker run -d \\
--name ${PROJECT_NAME} \\
--network=my-network \\
--restart=unless-stopped \\
-p 8080:8080 \\
-e "SPRING_PROFILES_ACTIVE=dev" \\
-e "TZ=Asia/Shanghai" \\
-v /app/logs:/app/logs \\
${DOCKER_IMAGE}:${params.IMAGE_TAG}
# 健康检查
sleep 10
if curl -f http://localhost:8080/actuator/health; then
echo "部署成功"
else
echo "部署失败"
exit 1
fi
EOF
"""
}
}
}
}
stage('部署到测试环境') {
when {
expression {
return params.BRANCH == 'release'
}
}
steps {
script {
withCredentials([sshUserPrivateKey(
credentialsId: 'test-server-ssh',
keyFileVariable: 'SSH_KEY'
)]) {
sh """
ssh -o StrictHostKeyChecking=no -i $SSH_KEY deploy@test-server << 'EOF'
# 使用docker-compose部署
export IMAGE_TAG=${params.IMAGE_TAG}
docker-compose -f /opt/apps/${PROJECT_NAME}/docker-compose.yml pull
docker-compose -f /opt/apps/${PROJECT_NAME}/docker-compose.yml up -d
# 等待并检查服务状态
for i in {1..10}; do
if docker-compose -f /opt/apps/${PROJECT_NAME}/docker-compose.yml ps | grep -q "Up"; then
echo "服务启动成功"
break
fi
echo "等待服务启动... (\$i/10)"
sleep 10
done
EOF
"""
}
}
}
}
stage('生产环境部署审批') {
when {
expression {
return params.BRANCH == 'main'
}
}
steps {
script {
// 发送部署通知
emailext(
subject: "待审批:${PROJECT_NAME} 生产环境部署",
body: """
项目:${PROJECT_NAME}
分支:${params.BRANCH}
版本:${params.IMAGE_TAG}
提交:${GIT_COMMIT_SHORT}
最近提交:${LAST_COMMIT}
请审批是否部署到生产环境。
""",
to: 'devops-team@example.com'
)
// 等待人工审批
timeout(time: 24, unit: 'HOURS') {
input(
message: '确认部署到生产环境?',
ok: '确认部署',
parameters: [
string(
name: 'CONFIRMATION',
defaultValue: '我已确认检查清单',
description: '请输入确认信息'
)
]
)
}
}
}
}
stage('部署到生产环境') {
when {
expression {
return params.BRANCH == 'main'
}
}
steps {
script {
// 使用Kubernetes部署(如果使用K8s)
sh """
kubectl set image deployment/${PROJECT_NAME} \
${PROJECT_NAME}=${DOCKER_IMAGE}:${params.IMAGE_TAG} \
-n production
kubectl rollout status deployment/${PROJECT_NAME} -n production --timeout=300s
"""
}
}
}
}
post {
always {
// 清理工作空间
cleanWs()
// 更新构建状态
script {
currentBuild.description = "分支: ${params.BRANCH}, 镜像: ${params.IMAGE_TAG}, 提交: ${GIT_COMMIT_SHORT}"
}
}
success {
// 成功通知
script {
def duration = currentBuild.durationString
def message = "✅ 构建成功\n" +
"项目: ${PROJECT_NAME}\n" +
"分支: ${params.BRANCH}\n" +
"版本: ${params.IMAGE_TAG}\n" +
"耗时: ${duration}\n" +
"构建: ${env.BUILD_URL}"
// 发送Slack通知
slackSend(
channel: '#deployments',
color: 'good',
message: message
)
// 发送邮件通知
emailext(
subject: "构建成功: ${PROJECT_NAME} - ${params.BRANCH}",
body: message,
to: 'dev-team@example.com'
)
}
}
failure {
// 失败通知
script {
def message = "❌ 构建失败\n" +
"项目: ${PROJECT_NAME}\n" +
"分支: ${params.BRANCH}\n" +
"构建: ${env.BUILD_URL}\n" +
"日志: ${env.BUILD_URL}console"
slackSend(
channel: '#deployments',
color: 'danger',
message: message
)
// 获取失败阶段的日志
def failedStage = currentBuild.rawBuild.getExecution().getStages()
.find { it.status.toString() == 'FAILED' }?.getDisplayName()
if (failedStage) {
echo "失败阶段: ${failedStage}"
}
}
}
unstable {
// 不稳定通知(如测试失败)
echo '构建结果不稳定,请检查测试报告'
}
}
}4. 辅助配置文件
docker-compose.test.yml(集成测试用):
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: test
DB_URL: jdbc:mysql://mysql:3306/test_db
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy5. Jenkins流水线调试技巧
调试方法1:使用echo打印变量
stage('调试') {
steps {
script {
echo "当前分支: ${env.BRANCH_NAME}"
echo "构建编号: ${env.BUILD_NUMBER}"
echo "工作目录: ${env.WORKSPACE}"
// 打印所有环境变量
sh 'printenv | sort'
}
}
}调试方法2:使用timeout和retry
stage('部署') {
steps {
retry(3) { // 重试3次
timeout(time: 5, unit: 'MINUTES') { // 超时5分钟
script {
sh './deploy.sh'
}
}
}
}
}调试方法3:条件执行
stage('条件部署') {
when {
// 仅在特定分支执行
branch 'main|release'
// 并且不是周末
expression {
def day = new Date().format('u')
return day != '6' && day != '7'
}
// 并且环境变量不为空
environment name: 'DEPLOY_ENV', value: 'production'
}
steps {
// 部署步骤
}
}第五部分:常见问题与解决方案
问题1:Docker构建时Maven依赖下载慢
解决方案:使用本地Nexus仓库
- 搭建Nexus私有仓库:
docker run -d \
--name nexus \
-p 8081:8081 \
-v /data/nexus:/nexus-data \
sonatype/nexus3- 配置Maven的
settings.xml:
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://nexus.example.com:8081/repository/maven-public/</url>
</mirror>- 在Jenkins中挂载配置:
agent {
docker {
image 'maven:3.8.5-openjdk-11'
args '-v /root/.m2:/root/.m2 -v /data/maven-settings.xml:/root/.m2/settings.xml'
}
}问题2:流水线执行权限问题
解决方案:正确配置Jenkins用户
pipeline {
agent any
tools {
maven 'Maven-3.8.5'
jdk 'JDK-11'
}
environment {
// 在Docker容器中运行
DOCKER_HOST = 'unix:///var/run/docker.sock'
}
stages {
stage('构建') {
steps {
// 使用withCredentials包装敏感操作
withCredentials([
usernamePassword(
credentialsId: 'docker-cred',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)
]) {
sh '''
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
docker build -t myapp .
'''
}
}
}
}
}问题3:多模块项目构建
对于Maven多模块项目,需要特殊处理:
stage('多模块构建') {
steps {
script {
// 获取所有子模块
def modules = sh(script: 'find . -name "pom.xml" -type f | xargs dirname | grep -v "^.$"', returnStdout: true).trim().split('\n')
modules.each { module ->
dir(module) {
echo "构建模块: ${module}"
sh "mvn clean install -DskipTests"
// 如果模块有Dockerfile,构建镜像
if (fileExists('Dockerfile')) {
def imageName = module.replace('/', '-').replace(' ', '-')
sh "docker build -t ${imageName}:${env.BUILD_ID} ."
}
}
}
}
}
}问题4:构建触发策略
GitHub Webhook自动触发:
properties([
pipelineTriggers([
// GitHub push事件触发
[
$class: 'GitHubPushTrigger',
adminList: '',
allowMembersOfWhitelistedOrgsAsAdmin: false,
cron: '',
triggerOnEvents: [
[
$class: 'GitHubPushTriggerEvent'
]
]
],
// 定时构建
cron('H */4 * * *')
])
])根据分支不同策略:
// 在Jenkinsfile开始处添加
def isMainBranch = env.BRANCH_NAME == 'main'
def isReleaseBranch = env.BRANCH_NAME.startsWith('release/')
def isFeatureBranch = env.BRANCH_NAME.startsWith('feature/')
pipeline {
agent any
stages {
stage('代码检查') {
when {
// 所有分支都执行
anyOf {
branch 'main'
branch 'release/*'
branch 'feature/*'
}
}
steps {
// SonarQube检查
}
}
stage('测试') {
when {
// 仅非main分支执行完整测试
not { branch 'main' }
}
steps {
// 运行测试
}
}
stage('部署到开发环境') {
when {
// 仅feature分支自动部署到开发环境
branch 'feature/*'
}
steps {
// 部署
}
}
}
}第六部分:监控与优化
1. Jenkins构建监控
添加构建度量:
post {
always {
script {
// 记录构建指标
def duration = currentBuild.duration
def success = currentBuild.resultIsBetterOrEqualTo('SUCCESS')
// 可以发送到监控系统
echo "构建耗时: ${duration}ms"
echo "构建结果: ${success ? '成功' : '失败'}"
// 清理旧的构建记录
def buildsToKeep = isMainBranch ? 20 : 10
buildDiscarder(logRotator(numToKeepStr: buildsToKeep.toString()))
}
}
}2. Docker镜像优化
使用多架构构建:
stage('多架构构建') {
steps {
script {
// 安装构建x插件
sh '''
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --name multiarch --use
'''
// 构建并推送多架构镜像
sh """
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ${DOCKER_IMAGE}:${params.IMAGE_TAG} \
-t ${DOCKER_IMAGE}:latest \
--push .
"""
}
}
}3. 流水线性能优化
并行执行:
stage('并行测试') {
parallel {
stage('单元测试') {
steps {
sh 'mvn test -Dtest=UnitTest*'
}
}
stage('集成测试') {
steps {
sh 'mvn test -Dtest=IntegrationTest*'
}
}
stage('API测试') {
steps {
sh 'mvn test -Dtest=ApiTest*'
}
}
}
}缓存优化:
stage('依赖缓存') {
steps {
// 使用Docker镜像层缓存
sh '''
docker build \
--cache-from ${DOCKER_IMAGE}:latest \
-t ${DOCKER_IMAGE}:${BUILD_NUMBER} \
.
'''
}
}总结
搭建基于Jenkins和Docker的CI/CD流水线是一个系统工程,需要关注:
- 环境一致性:通过Docker确保开发、测试、生产环境一致
- 流程自动化:从代码提交到部署上线全流程自动化
- 质量门禁:通过代码检查、测试、安全扫描保证质量
- 安全可靠:凭据管理、权限控制、审计日志
- 可观测性:构建监控、日志收集、告警通知
这套流水线实施后,我们的发布效率提升了10倍以上,发布窗口从原来的1小时缩短到5分钟,而且完全消除了人为操作失误。更重要的是,开发人员可以专注于代码编写,运维人员可以专注于基础设施,实现了真正的DevOps协同。
记住,CI/CD不是一蹴而就的,而是需要持续改进的过程。从最简单的自动化构建开始,逐步添加测试、部署、监控等环节,最终形成适合自己团队的完整流水线。