凌晨 2 点,你盯着终端,手敲 `scp` 传包、`ssh` 连服务器、`kill` 旧进程、`nohup` 启动新服务——这是多少程序员的上线噩梦?本文手把手带你搭建 **Docker + Jenkins 全自动 CI/CD 流水线**,从代码提交到服务上线,全程零人工干预。看完即可落地,代码可直接复制运行。

---

一、为什么你需要这套方案?

1.1 传统部署的 4 大痛点

痛点

场景

后果

**人工操作多**

编译 → 打包 → 传包 → 停服务 → 启服务

步骤越多,出错概率越高

**环境不一致**

本地能跑,测试环境报错,生产又不一样

"我本地明明是好的"

**回滚困难**

上线后发现 Bug,找不到上一版本的包

凌晨 3 点紧急救火

**无法追溯**

不知道谁、什么时候、部署了哪个版本

出问题互相甩锅

1.2 CI/CD 能解决什么?

开发者 push 代码
    ↓
Git 触发 Webhook
    ↓
Jenkins 自动拉取 → 编译 → 测试 → 打包 Docker 镜像
    ↓
推送镜像到 Harbor/阿里云仓库
    ↓
SSH 到目标服务器 → 拉取新镜像 → 停止旧容器 → 启动新容器
    ↓
发送企微/钉钉通知:部署成功 ✅

全程耗时:3~5 分钟。全程人工操作:0。

---

二、环境准备

2.1 所需机器与软件

角色

配置建议

安装软件

Jenkins 主节点

2C4G 起步

JDK 17、Jenkins LTS、Docker、Git、Maven

应用服务器(可多台)

根据业务

Docker、Docker Compose

镜像仓库

可复用 Jenkins 机器

Harbor 或阿里云 ACR

本文演示使用 **单台 Jenkins 机器 + 单台应用服务器**,生产环境按需扩展。

2.2 快速安装 Jenkins(Docker 方式)

# 1. 创建 Jenkins 数据卷
docker volume create jenkins_home

# 2. 启动 Jenkins(已预装 Maven、Docker CLI)
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 \
  -v $(which docker):/usr/bin/docker \
  jenkins/jenkins:lts-jdk17

# 3. 查看初始密码
docker logs jenkins 2>&1 | grep -A 5 "Please use the following password"

访问 `http://<服务器IP>:8080`,输入密码,安装推荐插件。

2.3 Jenkins 必要插件安装

进入 Manage Jenkins → Plugins → Available plugins,搜索并安装:

- Pipeline — 流水线核心

- Git — 拉取代码

- Maven Integration — Maven 构建

- SSH Agent / Publish Over SSH — 远程部署

- DingTalk / QyWechat Notification — 企微/钉钉通知(可选)

---

三、核心配置:Jenkins + Docker 打通

3.1 配置 Git 凭据

Manage Jenkins → Credentials → System → Global credentials → Add Credentials

- Kind: `Username with password`

- Username: 你的 Git 用户名

- Password: Git 密码或 Personal Access Token

- ID: 填 `git-creds`(Pipeline 里会引用)

3.2 配置远程服务器 SSH 凭据

同样的位置,再添加一组:

- Kind: `SSH Username with private key`

- Username: `root`(或你的部署账号)

- Private Key: 粘贴你的私钥(`~/.ssh/id_rsa` 内容)

- ID: 填 `deploy-server-ssh`

3.3 配置 Publish Over SSH(远程部署用)

Manage Jenkins → System → Publish over SSH → Add

- Name: `prod-server`

- Hostname: `192.168.1.100`(你的应用服务器 IP)

- Username: `root`

- Remote Directory: `/opt/deploy`

点击 Test Configuration,显示 `Success` 即可。

---

四、项目实战:Spring Boot + Docker 全自动部署

4.1 项目结构

my-springboot-app/
├── src/
├── pom.xml
├── Dockerfile
├── docker-compose.yml
└── Jenkinsfile

4.2 Dockerfile(多阶段构建,镜像体积减少 60%)

# 阶段一:编译
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# 阶段二:运行(仅保留 JRE + jar 包)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

**多阶段构建** 是 Docker 最佳实践:编译阶段用完整 JDK,运行阶段只用 JRE,最终镜像从 500MB 压缩到 80MB。

4.3 docker-compose.yml(生产级配置)

version: '3.8'

services:
  app:
    image: harbor.mycompany.com/springboot/app:${IMAGE_TAG}
    container_name: springboot-app
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - JAVA_OPTS=-Xms512m -Xmx512m -XX:+UseG1GC
    networks:
      - app-net
    # 日志限制,防止磁盘打满
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"

  # Nginx 反向代理(可选)
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

4.4 Jenkinsfile(Pipeline as Code,核心中的核心)

在项目根目录创建 `Jenkinsfile`:

pipeline {
    agent any

    environment {
        // 镜像仓库配置
        REGISTRY = 'harbor.mycompany.com'
        REGISTRY_CREDENTIALS = 'harbor-credentials'
        IMAGE_NAME = 'springboot/app'
        // SSH 目标服务器
        DEPLOY_HOST = '192.168.1.100'
        DEPLOY_PATH = '/opt/deploy/myapp'
        // 企业微信 Webhook(可选)
        WECHAT_WEBHOOK = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY'
    }

    stages {
        // ========== 阶段 1:拉取代码 ==========
        stage('Checkout') {
            steps {
                git branch: 'main',
                    credentialsId: 'git-creds',
                    url: 'https://github.com/yourname/my-springboot-app.git'
                script {
                    env.GIT_SHORT_SHA = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
                    env.IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_SHORT_SHA}"
                }
            }
        }

        // ========== 阶段 2:Maven 编译 & 测试 ==========
        stage('Build & Test') {
            steps {
                sh 'mvn clean package -DskipTests=false'
                // 单元测试报告
                junit 'target/surefire-reports/*.xml'
            }
        }

        // ========== 阶段 3:构建 Docker 镜像 ==========
        stage('Build Docker Image') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", REGISTRY_CREDENTIALS) {
                        def customImage = docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
                        customImage.push()
                        // 同时推送 latest 标签,方便快速回滚
                        customImage.push('latest')
                    }
                }
            }
        }

        // ========== 阶段 4:远程部署 ==========
        stage('Deploy') {
            steps {
                sshagent(credentials: ['deploy-server-ssh']) {
                    sh """
                        ssh -o StrictHostKeyChecking=no root@${DEPLOY_HOST} '
                            set -e
                            mkdir -p ${DEPLOY_PATH}
                            cd ${DEPLOY_PATH}
                            
                            # 备份当前 docker-compose.yml(用于回滚)
                            if [ -f docker-compose.yml ]; then
                                cp docker-compose.yml docker-compose.yml.bak
                            fi
                            
                            # 写入新的 compose 文件
                            cat > docker-compose.yml << "EOF"
version: "3.8"
services:
  app:
    image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
    container_name: springboot-app
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"
EOF
                           
                            # 拉取新镜像并启动
                            docker-compose pull app
                            docker-compose up -d app
                           
                            # 等待服务健康检查通过
                            echo "等待健康检查..."
                            for i in {1..30}; do
                                if docker inspect --format='{{.State.Health.Status}}' springboot-app 2>/dev/null | grep -q "healthy"; then
                                    echo "✅ 服务已就绪"
                                    exit 0
                                fi
                                sleep 2
                            done
                            echo "❌ 健康检查超时"
                            exit 1
                        '
                    """
                }
            }
        }
    }

    // ========== 后置处理:通知 & 清理 ==========
    post {
        success {
            script {
                // 企微通知
                sh """
                    curl -s -X POST ${WECHAT_WEBHOOK} \
                        -H 'Content-Type: application/json' \
                        -d '{
                            "msgtype": "markdown",
                            "markdown": {
                                "content": "🎉 **部署成功**\\n\\n> 项目:my-springboot-app\\n> 版本:${IMAGE_TAG}\\n> 时间:${env.BUILD_TIME}\\n> 提交:${env.GIT_SHORT_SHA}"
                            }
                        }'
                """
            }
        }
        failure {
            script {
                sh """
                    curl -s -X POST ${WECHAT_WEBHOOK} \
                        -H 'Content-Type: application/json' \
                        -d '{
                            "msgtype": "markdown",
                            "markdown": {
                                "content": "🚨 **部署失败**\\n\\n> 项目:my-springboot-app\\n> 构建号:${env.BUILD_NUMBER}\\n> 请及时检查 Jenkins 日志"
                            }
                        }'
                """
            }
        }
        always {
            // 清理本地旧镜像,防止磁盘膨胀
            sh 'docker image prune -f || true'
        }
    }
}

4.5 代码解读:Pipeline 的 5 个阶段

阶段

做什么

关键技巧

**Checkout**

从 Git 拉最新代码

提取短 SHA 作为镜像标签,保证版本可追溯

**Build & Test**

Maven 编译 + 单元测试

测试失败立即阻断,不部署有问题的代码

**Build Image**

Docker 多阶段构建 + 推送仓库

同时打 `latest` 标签,方便紧急回滚

**Deploy**

SSH 到服务器,拉镜像、启容器

内置健康检查,超时自动判定失败

**Notify**

企微/钉钉推送结果

success/failure/always 三种后置钩子

---

五、进阶技巧

5.1 一键回滚(关键时刻救命)

在 Jenkins 中创建一个 参数化流水线 `rollback`:

pipeline {
    agent any
    parameters {
        string(name: 'ROLLBACK_TAG', defaultValue: '', description: '回滚到的镜像标签')
    }
    stages {
        stage('Rollback') {
            steps {
                sshagent(credentials: ['deploy-server-ssh']) {
                    sh """
                        ssh root@${DEPLOY_HOST} '
                            cd /opt/deploy/myapp
                            sed -i "s|image: .*|image: ${REGISTRY}/${IMAGE_NAME}:${ROLLBACK_TAG}|" docker-compose.yml
                            docker-compose up -d app
                        '
                    """
                }
            }
        }
    }
}

回滚只需 30 秒:找到上一次成功的构建号,输入镜像标签,点击构建。

5.2 蓝绿部署(零停机发布)

# docker-compose.blue-green.yml
services:
  app-blue:
    image: ${IMAGE_TAG}
    container_name: app-blue
    ports: ["8081:8080"]
 
  app-green:
    image: ${IMAGE_TAG}
    container_name: app-green
    ports: ["8082:8080"]

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

流程

1. 绿环境部署新版本

2. Nginx 切流量到绿环境

3. 观察 5 分钟无异常

4. 蓝环境更新,作为下次发布的基础

5.3 Jenkins 与 Git Webhook 联动(自动触发)

GitHub 设置

1. 进入仓库 → Settings → Webhooks → Add webhook

2. Payload URL: `http://<JenkinsIP>:8080/github-webhook/`

3. Content type: `application/json`

4. 勾选 Just the push event

Jenkins 项目设置

1. 构建触发器 → 勾选 GitHub hook trigger for GITScm polling

效果:每次 `git push` 到 `main` 分支,Jenkins 自动开始构建,全程无需人工干预。

---

六、常见问题排查

Q1:Jenkins 构建报错 "Cannot connect to the Docker daemon"

原因:Jenkins 容器内没有 Docker 权限。 

解决:启动 Jenkins 时挂载 Docker socket:

-v /var/run/docker.sock:/var/run/docker.sock
-v $(which docker):/usr/bin/docker

Q2:SSH 连接失败 "Host key verification failed"

解决:在 Pipeline 的 ssh 命令中加 `-o StrictHostKeyChecking=no`,或在 Jenkins 服务器手动 `ssh` 一次目标机器。

Q3:镜像推送 Harbor 报 401 Unauthorized

解决:Jenkins 凭据里添加 Harbor 账号密码,ID 与 `Jenkinsfile` 中的 `REGISTRY_CREDENTIALS` 一致。

Q4:健康检查一直不通过

排查步骤

1. `docker logs springboot-app` 看应用日志

2. 确认 `actuator/health` 端点已开启(Spring Boot 需引入 `spring-boot-starter-actuator`)

3. 检查防火墙是否放行 8080 端口

---

七、完整架构图

┌─────────────────────────────────────────────────────────────┐
│                        开发者                                │
│                    git push origin main                      │
└──────────────────────────┬──────────────────────────────────┘
                           │ Webhook
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                      Jenkins 主节点                           │
│  ┌─────────┐  ┌──────────┐  ┌──────────┐  ┌─────────────┐  │
│  │ Checkout│→ │Build&Test│→ │Build Img │→ │   Deploy    │  │
│  │  拉代码  │  │ Maven编译 │  │Docker构建 │  │ SSH远程部署  │  │
│  └─────────┘  └──────────┘  └──────────┘  └─────────────┘  │
│                           │                                 │
│                           ▼                                 │
│              ┌─────────────────────┐                        │
│              │   Harbor 镜像仓库    │                        │
│              │  (版本化存储镜像)     │                        │
│              └─────────────────────┘                        │
└──────────────────────────┬──────────────────────────────────┘
                           │ docker pull
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                     生产服务器集群                            │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │  Docker容器  │    │  Docker容器  │    │  Docker容器  │     │
│  │  SpringBoot │    │  SpringBoot │    │  SpringBoot │     │
│  │   App v1.2  │    │   App v1.2  │    │   App v1.2  │     │
│  └─────────────┘    └─────────────┘    └─────────────┘     │
│         ↑                                                  │
│    健康检查通过 → 企微通知 "部署成功"                         │
└─────────────────────────────────────────────────────────────┘

---

八、总结与生产 Checklist

核心收益

指标

传统部署

CI/CD 自动化

部署耗时

15~30 分钟

3~5 分钟

人工操作

5~10 步

0 步

回滚时间

10 分钟以上

30 秒

环境一致性

100% 一致

版本可追溯

每次构建有唯一标签

生产环境 Checklist

- [ ] Jenkins 开启权限控制(Matrix-based security)

- [ ] 镜像仓库启用 HTTPS + 账号认证

- [ ] 部署脚本中加入 `set -e`,任何步骤失败立即退出

- [ ] 配置日志收集(ELK / Loki)

- [ ] 监控告警:Prometheus + Grafana 监控容器状态

- [ ] 定期清理旧镜像,防止磁盘打满

- [ ] 敏感信息(密码、Token)存 Jenkins 凭据,不要写死在代码里

---

如果这篇文章对你有帮助,欢迎点赞、收藏、转发!关注公众号「洛水石」,获取更多后端实战干货。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐