当前位置: 首页 > news >正文

Docker + Jenkins 自动化部署实战:一行命令,告别凌晨上线

凌晨 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 凭据,不要写死在代码里

---

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

http://www.jsqmd.com/news/695612/

相关文章:

  • Vek385评估板(二):板子联网 memtester安装(LPDDR5X测试)
  • ESP32C3 + ESP-Rainmaker 保姆级配网教程:从代码修改到APP控制,手把手搞定物联网开关
  • 搞定微信过期文件恢复,简单几步
  • 避开这些坑!GD32F470 ADC同步模式与DMA配置详解(以梁山派双通道同步采样为例)
  • Spring Boot 事务超时与回滚策略
  • vue3 element-plus el-option滚动分页
  • 计算机毕业设计:Python股市交易后台管理系统 Django框架 requests爬虫 数据分析 可视化 大数据 大模型(建议收藏)✅
  • 深入TI DSP的EPWM影子寄存器:为什么以及如何正确使用它?
  • 空调行业“铜铝之争”深度解析:从技术探讨到舆论大战,理性回归正当时
  • Kylin麒麟操作系统查询防火墙状态及端口开放
  • 在Ubuntu 22.04上从源码编译安装gnina 1.1:一个生物信息学新手的踩坑与填坑全记录
  • FastDFS 分布式存储
  • 如何轻松实现i茅台自动预约:告别早起抢购的终极解决方案
  • 彩云岛去水印
  • 暗黑破坏神2角色编辑器:5分钟掌握Diablo Edit2终极指南
  • 光伏MMC并网系统(两级式)交流故障穿越与电网对称与不对称故障:simulink仿真模型及光伏经模
  • 别再只读ADC值了!STM32标准库下光敏传感器的校准与标定实战
  • Python脚本参数传递与命令行工具开发实战
  • 别再手动加标签了!用MATLAB的text函数给你的图表自动添加专业注释(附TeX公式教程)
  • 无人机视角田间土豆马铃薯苗和杂草检测数据集VOC+YOLO格式384张5类别
  • MySQL主从复制支持跨版本吗_不同版本间同步的注意事项
  • 电话营销机器人,智能语音外呼获客系统
  • 从厨房秤到智能仓储:HX711的增益、标定与线性拟合,让你的项目精度提升一个档次
  • 盘古50K开发板PCIE性能初探:如何利用PGL50H的HSST高速收发器进行通信验证
  • SVD降维技术解析与Python实战指南
  • OceanBase-Desktop-Setup-1.0.0.exe
  • OpenUI:用流式语言标准解决AI生成UI的解析与渲染难题
  • 框架之战——Infoseek舆情系统解析回应如何塑造公众认知
  • 5分钟免费升级:如何在Word中启用APA第7版参考文献格式
  • 完整指南:如何在UKB_RAP上高效完成生物医学数据分析的5个关键步骤