Skip to content

Jenkins 详解:CI/CD 自动化

  Jenkins 是业界最流行的开源 CI/CD 工具,负责自动化构建、测试、部署。代码提交后,Jenkins 自动拉取、编译、打包镜像、推送仓库、部署到服务器,让开发只关注写代码。

一、为什么需要 Jenkins?

1.1 没有 CI/CD 的痛

手工发布流程(每次 30 分钟 ~ 2 小时):

  1. 开发提交代码到 Git
  2. 开发本地 mvn package 打包
  3. 手动上传 jar 到服务器
  4. 手动停掉旧服务
  5. 手动启动新服务
  6. 手动检查日志确认启动成功
  7. 如果失败,手动回滚

  问题:
  ❌ 重复劳动,容易出错
  ❌ 没人知道"当前线上跑的是哪个版本"
  ❌ 多人同时发布时互相干扰
  ❌ 回滚困难,只能手动替换 jar

1.2 有了 Jenkins 之后

自动化发布流程(每次 5 分钟,全自动):

  1. 开发提交代码到 Git
     └── Jenkins 自动检测到代码变更
  2. Jenkins 自动拉取代码
     └── mvn clean package
  3. Jenkins 自动构建 Docker 镜像
     └── docker build -t order-service:v1.2.3
  4. Jenkins 自动推送镜像到仓库
     └── docker push registry/order-service:v1.2.3
  5. Jenkins 自动部署到 K8s
     └── kubectl set image deployment/order-service order-service=registry/order-service:v1.2.3
  6. Jenkins 自动验证
     └── curl http://order-service/actuator/health
  7. 如果失败,自动回滚到上一版本

  优点:
  ✅ 全过程自动化,人工介入为 0
  ✅ 每次发布都有记录,可追溯
  ✅ 一键回滚到任意版本
  ✅ 发布频率从"月级"提升到"天级"甚至"小时级"

二、核心概念

2.1 CI/CD 是什么?

CI(Continuous Integration,持续集成):
  代码频繁合并到主干,每次合并自动构建 + 测试
  目标:尽早发现集成问题,避免"合并地狱"

CD(Continuous Delivery / Deployment,持续交付/部署):
  通过 CI 的代码自动部署到测试/生产环境
  目标:随时可以一键发布,发布不再是"大事"

2.2 Jenkins 架构

┌──────────────────────────────────────────────────────────────────┐
│                      Jenkins 架构                                 │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    Jenkins Master(主控)                    │  │
│  │                                                            │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐                │  │
│  │  │ 任务调度   │  │ 插件管理   │  │ 用户管理   │                │  │
│  │  └──────────┘  └──────────┘  └──────────┘                │  │
│  │                                                            │  │
│  │  负责:调度任务、管理配置、展示结果,不执行构建               │  │
│  └────────────────────────────────────────────────────────────┘  │
│                              │                                    │
│              ┌───────────────┼───────────────┐                    │
│              ▼               ▼               ▼                    │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐            │
│  │  Agent 1     │  │  Agent 2     │  │  Agent 3     │            │
│  │  (Linux)     │  │  (Linux)     │  │  (Windows)   │            │
│  │              │  │              │  │              │            │
│  │  执行构建    │  │  执行构建    │  │  执行构建    │            │
│  │  Maven/Docker│  │  Maven/Docker│  │  .NET/MSBuild│            │
│  └──────────────┘  └──────────────┘  └──────────────┘            │
│                                                                  │
│  ★ Master 不干活,只调度                                          │
│  ★ Agent 是真正执行构建的节点,可横向扩展                           │
└──────────────────────────────────────────────────────────────────┘

三、快速上手

3.1 Docker 部署 Jenkins

bash
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts

3.2 安装推荐插件

首次启动推荐安装:
  ✅ Git                    — 代码拉取
  ✅ Pipeline               — 流水线核心
  ✅ Docker Pipeline        — Docker 构建
  ✅ Kubernetes CLI         — K8s 部署
  ✅ Blue Ocean             — 可视化流水线
  ✅ Maven Integration      — Maven 构建
  ✅ SonarQube Scanner      — 代码质量扫描
  ✅ Slack Notification     — 通知

四、Pipeline 流水线

4.1 为什么用 Pipeline?

传统 Jenkins Job(自由风格):
  ❌ 配置写在 Web UI 里,无法版本管理
  ❌ 迁移到新 Jenkins 需要重新配置
  ❌ 多人协作时,改了什么无法追踪

Pipeline as Code(声明式流水线):
  ✅ Jenkinsfile 文件,随代码一起提交到 Git
  ✅ 重构、回滚、审查都像代码一样
  ✅ 换 Jenkins 服务器,只要代码在,流水线就在

4.2 声明式 vs 脚本式

groovy
// 声明式(推荐,结构清晰,适合大多数场景)
pipeline {
    agent any
    stages {
        stage('编译') {
            steps {
                sh 'mvn clean compile'
            }
        }
        stage('测试') {
            steps {
                sh 'mvn test'
            }
        }
    }
}

// 脚本式(灵活,适合复杂逻辑)
node {
    stage('编译') {
        sh 'mvn clean compile'
    }
    stage('测试') {
        sh 'mvn test'
    }
}

4.3 微服务完整 Pipeline

groovy
pipeline {
    agent any

    // ============ 环境变量 ============
    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/order-service"
        K8S_NAMESPACE = 'production'
        K8S_DEPLOYMENT = 'order-service'
    }

    // ============ 参数(手动触发时可选择) ============
    parameters {
        choice(name: 'ENVIRONMENT', choices: ['dev', 'test', 'prod'], description: '部署环境')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
    }

    stages {

        // ============ 阶段 1:代码检出 ============
        stage('Checkout') {
            steps {
                checkout scm
                // 获取当前分支和 commit ID
                script {
                    env.GIT_BRANCH = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
                    env.GIT_COMMIT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
                    env.IMAGE_TAG = "${env.GIT_COMMIT}-${env.BUILD_NUMBER}"
                }
            }
        }

        // ============ 阶段 2:编译 ============
        stage('Compile') {
            steps {
                sh 'mvn clean compile'
            }
        }

        // ============ 阶段 3:单元测试 ============
        stage('Unit Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit '**/target/surefire-reports/*.xml'
                }
            }
        }

        // ============ 阶段 4:代码扫描 ============
        stage('SonarQube') {
            steps {
                sh 'mvn sonar:sonar -Dsonar.projectKey=order-service'
            }
        }

        // ============ 阶段 5:打包 ============
        stage('Package') {
            steps {
                sh 'mvn package -DskipTests'
            }
        }

        // ============ 阶段 6:构建 Docker 镜像 ============
        stage('Docker Build') {
            steps {
                script {
                    docker.build("${DOCKER_IMAGE}:${IMAGE_TAG}")
                }
            }
        }

        // ============ 阶段 7:推送镜像 ============
        stage('Docker Push') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
                        docker.image("${DOCKER_IMAGE}:${IMAGE_TAG}").push()
                    }
                }
            }
        }

        // ============ 阶段 8:部署到 K8s ============
        stage('Deploy to K8s') {
            when {
                expression { params.ENVIRONMENT == 'prod' }
            }
            steps {
                sh """
                    kubectl set image deployment/${K8S_DEPLOYMENT} \
                        ${K8S_DEPLOYMENT}=${DOCKER_IMAGE}:${IMAGE_TAG} \
                        -n ${K8S_NAMESPACE} --record
                """
                // 等待部署完成
                sh """
                    kubectl rollout status deployment/${K8S_DEPLOYMENT} \
                        -n ${K8S_NAMESPACE} --timeout=5m
                """
            }
        }

        // ============ 阶段 9:健康检查 ============
        stage('Health Check') {
            steps {
                script {
                    sleep 30  // 等待服务启动
                    def response = sh(
                        script: 'curl -s -o /dev/null -w "%{http_code}" http://order-service/actuator/health',
                        returnStdout: true
                    ).trim()
                    if (response != '200') {
                        error("健康检查失败,HTTP 状态码: ${response}")
                    }
                }
            }
        }
    }

    // ============ 全局 Post 处理 ============
    post {
        // 成功时通知
        success {
            script {
                echo "构建成功!版本: ${IMAGE_TAG}"
                // slackSend(channel: '#deploy', message: "✅ order-service ${IMAGE_TAG} 部署成功")
            }
        }
        // 失败时通知 + 回滚
        failure {
            script {
                echo "构建失败!版本: ${IMAGE_TAG}"
                // slackSend(channel: '#deploy', message: "❌ order-service ${IMAGE_TAG} 部署失败")
                // 如果 K8s 部署失败,回滚到上一版本
                sh """
                    kubectl rollout undo deployment/${K8S_DEPLOYMENT} -n ${K8S_NAMESPACE} || true
                """
            }
        }
        // 无论成功失败都清理
        always {
            cleanWs()
        }
    }
}

五、多分支流水线

  多分支流水线自动为每个分支创建独立的构建任务:

仓库分支                     Jenkins 自动创建的 Job
─────────────────────────────────────────────────
main       ──────────→     order-service/main
develop    ──────────→     order-service/develop
feature/xxx ──────────→     order-service/feature-xxx
hotfix/yyy ──────────→     order-service/hotfix-yyy

PR 合并到 main 时自动触发 main 分支构建 + 部署到生产
push 到 develop 时自动触发 develop 分支构建 + 部署到测试
groovy
// Jenkinsfile 中按分支做不同处理
pipeline {
    agent any
    stages {
        stage('Deploy') {
            steps {
                script {
                    if (env.BRANCH_NAME == 'main') {
                        deployTo('prod')
                    } else if (env.BRANCH_NAME == 'develop') {
                        deployTo('test')
                    } else {
                        deployTo('dev')
                    }
                }
            }
        }
    }
}

六、与 Git 集成

6.1 Webhook 自动触发

Git 平台(GitHub/GitLab/Gitee)                Jenkins
┌──────────────────────────┐          ┌──────────────────┐
│                          │          │                  │
│  开发者 push 代码         │          │  接收 Webhook    │
│         │                │  POST    │        │         │
│         ▼                │─────────→│        ▼         │
│  触发 Webhook             │          │  解析分支信息    │
│  http://jenkins:8080/    │          │        │         │
│  github-webhook          │          │        ▼         │
│                          │          │  触发对应分支    │
│                          │          │  的 Pipeline     │
└──────────────────────────┘          └──────────────────┘

6.2 配置步骤

1. Jenkins 安装 "GitHub Integration" / "GitLab" 插件
2. Pipeline 配置中勾选 "GitHub hook trigger for GITScm polling"
3. Git 平台设置 Webhook URL:http://jenkins:8080/github-webhook/
4. 选择触发事件:Push events + Pull Request events

七、面试要点

Q1:CI/CD 是什么?CI 和 CD 有什么区别?

  CI(持续集成)是频繁合并代码并自动构建测试,尽早发现集成问题。CD 有两种:持续交付(手动部署)和持续部署(自动部署到生产)。CI 保证代码质量,CD 保证快速交付。

Q2:Jenkins Pipeline 有哪两种?

  声明式(Declarative)和脚本式(Scripted)。声明式结构固定(pipeline → stages → stage → steps),易读易维护,推荐日常使用。脚本式是 Groovy 代码,灵活但复杂,适合高级场景。

Q3:Agent 和 Master 的区别?

  Master 负责调度任务、管理配置、展示结果,不执行构建。Agent 是真正执行构建的节点,可以横向扩展。生产环境 Master 不要执行构建,避免资源抢占。

Q4:如何实现构建失败自动回滚?

  在 Pipeline 的 post { failure {} } 块中执行 kubectl rollout undo 回滚 K8s 部署,或执行 docker service update --rollback 回滚 Docker Swarm。

Q5:如何实现多环境部署?

  通过 Pipeline 参数(choice parameter)选择环境,或按分支自动判断(main → prod,develop → test)。每个环境对应不同的 K8s namespace 或配置文件。

Q6:Jenkins 如何实现高可用?

  方案一:Master + 多个 Agent(Agent 高可用)。方案二:Jenkins Master 主备(通过共享存储 + 冷备)。方案三:CloudBees Jenkins(企业版高可用)。实际中,Agent 扩展比 Master 高可用更重要。