🛠️ 原始阶段:手工部署时代
⚙️ 一、典型部署流程
-
💻 开发环境构建
- Java项目:
mvn package
生成jar包 - 前端项目:
npm run build
生成dist - 环境差异:开发者本地 Windows/Mac vs 生产环境 CentOS
- Java项目:
-
📤 文件传输
scp target/app.jar user@prod-server:/home/user/upload
- 痛点:100MB+文件通过VPN上传耗时10+分钟(以某内部平台为例,一次需更新9个jar包)
-
🖥️ 服务器部署
mkdir -p /opt/myapp/{bin,conf,logs} vim /opt/myapp/conf/application.properties
-
🚀 服务启动
nohup java -jar app.jar > console.log 2>&1 &
- 典型启动脚本:
# restart.sh kill $(ps aux | grep 'myapp' | awk '{print $2}') nohup java -jar /opt/myapp/app.jar &
🔍 2. 核心问题分析
问题类型 | 具体表现 | 影响程度 |
---|---|---|
🌐 环境差异 | 开发机 Windows/Mac vs 生产 CentOS,JDK 版本不一致 | ★★★★★ |
⏱️ 部署效率 | 重复执行10+命令,VPN上传大文件耗时 | ★★★★☆ |
📝 配置管理 | 配置文件散落各处,无版本控制 | ★★★★☆ |
🛡️ 服务可靠性 | nohup 进程可能崩溃,无自动重启机制 | ★★★★☆ |
👀 运维可见性 | 日志分散,无监控指标 | ★★★☆☆ |
⚠️ 3. 典型故障案例
-
📦 依赖缺失
libfreetype.so.6: cannot open shared object file
原因:生产环境缺少fontconfig库 -
🔌 端口冲突
Address already in use
原因:多服务共用8080端口 -
⚙️ 配置错误
测试环境误连开发数据库
表现:网络不通,或者开发数据出现在测试环境
🔄 4. 过渡期改进尝试
-
📄 基础文档
deploy_manual.md
问题: 文档更新不及时 -
📜 简单脚本
# deploy.sh scp $1 user@server:/opt/myapp ssh user@server "./restart.sh"
局限: 无错误校验,且需要可直接ssh连接服务器(一般是需要通过堡垒机)
-
📊 基础监控
*/5 * * * * pgrep -f myapp || ./restart.sh
缺陷: 无报警通知
🏭 瘦身包方案(缩短部署时长)
🧩 核心实现原理
⚙️ 关键配置说明
-
📦 Spring Boot Thin Layout
<dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>1.0.28.RELEASE</version> </dependency>
作用: 将传统Fat Jar拆分为:
- 应用代码(主Jar)
- 依赖描述文件(thin.properties)
- 外部依赖库(可共享)
-
📂 依赖解压配置
<requiresUnpack> <dependency> <groupId>com.erbantou.platform</groupId> <artifactId>demo-service</artifactId> </dependency> </requiresUnpack>
- 必要性:确保嵌套JAR被正确解压
- 典型问题:未解压时出现的
FileNotFoundException
-
🔧 特殊依赖处理
<artifactItem> <groupId>com.erbantou.platform</groupId> <artifactId>demo-override</artifactId> <excludes>META-INF/MANIFEST.MF</excludes> </artifactItem>
作用: 避免MANIFEST.MF冲突
🔄 部署流程优化
-
🏗️ 构建阶段
mvn clean package
生成产物:
target/ ├── demo-cloud.jar# 主应用(瘦身版) ├── thin.properties # 依赖清单 └── lib/# 可选:本地依赖库
-
⚡ 运行时依赖解析
java -Dthin.root=lib -jar aas-cloud.jar
- 自动从以下位置加载依赖:
- 本地
lib/
目录 - Maven本地仓库
- 远程仓库(可配置)
🏗️ 标准化构建:Jenkins + systemd 服务化
🔄 部署方案实施流程
-
🔄 核心工作流
graph TB A[开发者] -->|提交代码| B[GitLab] B -->|触发流水线| C[Jenkins] C -->|构建打包| D[Nexus] C -->|SSH部署| E[应用服务器] E -->|服务注册| F[systemd] -
⚙️ 详细执行步骤
-
📥 代码提交阶段
开发人员将代码提交到 Gitlab 仓库 -
🏗️ 自动化构建阶段
通过 Jenkins 配置构建流水线,生成 jar 包或者前端的 dist 目录 -
🚀 服务部署阶段:
通过 Jenkins 配置 ssh 连接信息,将生成的部署资源推送到目标应用服务器,执行目标应用服务器上的部署脚本。
-
✅ 方案核心价值
解决的问题 | 实现方式 | 效果提升 |
---|---|---|
🌐 统一构建环境 | 固定Jenkins构建节点OS+工具链 | 构建成功率从70%提升至98% |
⚡ 简化部署流程 | 标准化部署脚本 | 部署耗时从30分钟缩短至5分钟 |
🛡️ 服务可靠性 | systemd的自动重启机制 | 服务意外终止率降低80% |
👁️ 状态可视化 | 集成systemctl status输出 | 故障定位时间缩短50% |
👥 非技术人员可操作 | Jenkins Web界面提供"一键部署"按钮 | 测试人员自主部署比例提升至60% |
⚠️ 现存问题深度分析
-
🔒 网络权限问题
- 📜 必要权限清单:
graph LR J[Jenkins] -->|22/tcp| A[所有应用服务器] J -->|80/tcp| N[Nexus] J -->|443/tcp| G[GitLab]安全风险:
- 需长期开放SSH密钥对
- 防火墙需放行大量端口
-
🔄 多版本环境管理
- ⚠️ 典型冲突案例:
# 同时构建两个项目时 ProjectA: 需要Node16 (依赖glibc-2.17) ProjectB: 需要Node18 (依赖glibc-2.28) # 系统实际glibc版本 $ ldd --version ldd (GNU libc) 2.23
-
🗃️ 公共缓存冲突
- ❌ 问题表现:
# Maven本地仓库冲突 [ERROR] Failed to execute goal: Could not resolve dependencies for artifact com.example:api:jar:1.0
🔄 标准化构建:Jenkins 多版本环境管理
🌐 多版本环境冲突的典型场景
-
🖥️ 前端项目环境冲突
graph TB A[项目A] -->|要求Node16| B[需要glibc-2.17] C[项目B] -->|要求Node18| D[需要glibc-2.28] E[服务器] -->|当前glibc-2.23| F[无法同时满足] -
☕ Java项目环境冲突
- 同时维护的项目需求:
项目名称 JDK要求 Maven要求 A系统 JDK8u202 Maven 3.6 B系统 JDK11 Maven 3.8 数据分析 JDK17 Maven 3.9
⚠️ 具体问题表现
-
📦 依赖库版本冲突
- 案例:Node.js 不同版本对OpenSSL的要求
# Node16需要的OpenSSL 1.1.x $ ldd /usr/local/node16/bin/node | grep ssl libssl.so.1.1 => /lib64/libssl.so.1.1 (0x00007f8c3a1e0000) # Node18需要的OpenSSL 3.0.x $ ldd /usr/local/node18/bin/node | grep ssl libssl.so.3 => not found
-
⚙️ 系统库不兼容
- 错误示例:
/lib64/libm.so.6: version `GLIBC_2.27' not found required by /opt/node18/bin/node
-
🧩 工具链污染
# 全局安装的Maven冲突 $ mvn -v Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) # 但项目B需要3.8.5
⚠️ 现有解决方案的局限性
-
🔄 传统隔离方案
- 使用
alternatives
切换版本:
sudo alternatives --config java
缺陷:
- 全局生效,无法并行构建
- 需要root权限
- 使用
-
⚙️ 手动环境切换
# 在Jenkinsfile中硬编码路径 stage('Build') { sh ''' export JAVA_HOME=/opt/jdk8u202 export PATH=$JAVA_HOME/bin:$PATH mvn clean package ''' }
问题:
- 维护成本高(每个项目需特殊配置)
- 容易遗漏环境变量
-
⚠️ 服务器污染风险
常见问题:- 升级Node18导致旧项目构建失败
- 安装新JDK影响现有服务
✅ 优化实践
通过 docker run --rm 指定镜像,将源码映射到容器内,再通过镜像提供的标准构建环境进行构建。
- 🐳 工具链容器化
# 专用构建镜像 sudo docker run --rm \ -v "$(pwd):/app" \ -w /app \ node:18.20.5 \ sh -c "yarn config set registry http://erbantou.com/ && yarn install --verbose && yarn run build:h5:dev"
⚖️ 方案优缺点对比
解决方案 | 优点 | 缺点 |
---|---|---|
alternatives切换 | 无需额外资源 | 全局影响/无法并行 |
容器化构建 | 完全隔离/版本精确控制 | 需要Docker环境/存储消耗大 |
🐳 容器化部署
🔄 部署方案实施流程
-
🏗️ 核心架构
graph TB A[Jenkins] -->|构建镜像| B[Dockerfile] B --> C[镜像仓库] C -->|拉取| D[生产服务器] D --> E[docker-compose] E --> F[watchtower] -
⚙️ 详细工作流
构建示例:stage('Build & Deploy') { steps { script { def DEPLOY_ENV=env.DEPLOY_ENV // 1. 构建前端 sh """ echo "${DEPLOY_ENV}" sed -i -E 's#resolved "https?://[^/]+#resolved "http://erbantou.com#' yarn.lock docker run --rm \ -v "${WORKSPACE}:/app" \ -w /app \ erbantou.com/node:18.20.5 \ sh -c "yarn config set registry 'http://erbantou.com' && yarn install --verbose && yarn run build:h5:${DEPLOY_ENV}" """ // 2. 动态生成 Dockerfile sh ''' echo "FROM erbantou.com/alpine" > Dockerfile echo "COPY dist/build/h5 /app/" >> Dockerfile ''' // 3. 构建镜像并推送 sh ''' # 获取构建时间,作为镜像版本名 BUILD_DATE=$(date +%Y%m%d%H%M) # 镜像名称 IMAGE_NAME="erbantou.com/project_name/app_name/${DEPLOY_ENV}" # 镜像版本名 IMAGE_TAG="${IMAGE_NAME}:${BUILD_DATE}" # 最新版本镜像名 LATEST_TAG="${IMAGE_NAME}:latest" echo "正在构建 Docker 镜像..." echo "镜像版本: $IMAGE_TAG" echo "最新标签: $LATEST_TAG" # 构建镜像 echo "正在执行 Docker 构建..." docker build \ -t ${IMAGE_TAG} \ -t ${LATEST_TAG} \ . || { echo "错误: Docker 构建失败!" exit 1 } echo "Docker 构建成功完成!" echo "正在推送镜像到仓库..." # 推送镜像到私有仓库 echo "正在推送镜像: $IMAGE_TAG" docker push "$IMAGE_TAG" || { echo "错误: 推送 $IMAGE_TAG 失败!" exit 1 } echo "正在推送镜像: $LATEST_TAG" docker push "$LATEST_TAG" || { echo "错误: 推送 $LATEST_TAG 失败!" exit 1 } echo "Docker 镜像构建和推送全部完成!" ''' } } }
✅ 方案核心价值
解决的问题 | 实现方式 | 技术实现示例 |
---|---|---|
🔑 Jenkins权限过大 | 消除SSH直连 | 仅需镜像仓库访问权限 |
🔄 多版本环境问题 | 容器化构建环境 | docker run -it node:16 bash |
🧹 构建缓存污染 | 独立容器空间 | -v maven-repo:/root/.m2 |
🕰️ 版本追溯困难 | 镜像标签版本化 | app:latest 和 app:202508151600 |
⚠️ 现存问题分析
-
💾 镜像仓库存储压力
由于不同环境均通过镜像的方式推送到镜像仓库,将导致镜像仓库存在较大的存储压力,需配置适当的清理策略。 -
⚡ watchtower的管控风险
- 🛡️ 生产环境配置示例:
# docker-compose.yml services: watchtower: profiles: ["watchtower"] # 默认不启动 watchtower 配置,需要手动开启 image: erbantou.com/containrrr/watchtower container_name: watchtower logging: driver: "json-file" options: max-size: "10m" # 单个日志文件最大 10MB max-file: "3" # 保留最多 3 个日志文件 volumes: - /var/run/docker.sock:/var/run/docker.sock command: - --interval=60 # 每分钟检查一次 - --label-enable # 仅更新带com.centurylinklabs.watchtower.enable=true标签的容器 - --cleanup # 清理旧镜像 restart: unless-stopped
⚖️ 方案优缺点对比
维度 | 传统方案 | 容器化方案 |
---|---|---|
🌐 环境一致性 | 依赖人工维护 | 镜像保证100%一致 |
⚡ 部署速度 | 5-10分钟 | 1-3分钟 |
🔑 权限管控 | 需SSH和sudo权限 | 仅需仓库访问权限 |
💾 存储消耗 | 较小 | 较大(需镜像仓库) |
💬 互动环节设计
❓ 问题讨论
Q1:在您当前环境中,最棘手的部署问题是什么?
选项参考:
- 环境不一致问题
- 多版本管理困难
- 部署流程复杂
- 权限管控风险
Q2:如果实施容器化改造,您最关心哪些方面?
选项参考:
- 技术学习成本
- 现有系统兼容性
- 运维工具链改造
- 团队接受度
图标使用说明:
- 🛠️ - 基础工具/手工操作
- ⚙️ - 流程/机制
- 💻 - 开发环境
- 📤 - 文件传输
- 🖥️ - 服务器操作
- 🚀 - 服务启动/部署
- 🔍 - 分析/诊断
- ⚠️ - 警告/问题
- 🔄 - 转换/过渡
- 🏭 - 优化方案
- 🧩 - 架构原理
- ✅ - 优势/价值
- ⚠️ - 风险/问题
- 🐳 - 容器化相关
- 💾 - 存储问题
- ⚡ - 速度/效率
- 🛡️ - 安全/防护
- ⚖️ - 对比/权衡
- 💬 - 互动交流
- ❓ - 问题讨论
(注:所有原始内容保持完全不变,仅在最前部添加了Unicode图标)
评论区