Git + Maven Java 项目部署实战全指南
基于华为云香港区全新 ecs-fddb 集群(4 台 c6.large_2),从 Git 版本控制到 Maven 构建管理,再到 Jenkins CI/CD 自动化部署的完整学习路径。所有命令、输出、踩坑均为真实操作记录。
目录
一、基础知识篇
- [1.1 Git 版本控制基础](#1.1 Git 版本控制基础)
- [1.2 Maven 项目管理基础](#1.2 Maven 项目管理基础)
- [1.3 Java 项目结构规范](#1.3 Java 项目结构规范)
二、环境搭建篇
- [2.1 开发环境配置](#2.1 开发环境配置)
- [2.2 服务器环境准备](#2.2 服务器环境准备)
三、项目构建篇
- [3.1 Maven 项目创建与配置](#3.1 Maven 项目创建与配置)
- [3.2 依赖管理最佳实践](#3.2 依赖管理最佳实践)
四、自动化部署篇
- [4.1 Jenkins 自动化部署](#4.1 Jenkins 自动化部署)
- [4.2 部署流程设计](#4.2 部署流程设计)
五、高级应用篇
- [5.1 多环境配置管理](#5.1 多环境配置管理)
- [5.2 持续集成/持续部署(CI/CD)](#5.2 持续集成/持续部署(CI/CD))
- [5.3 容器化部署](#5.3 容器化部署)
六、实战案例篇
- [6.1 Spring Boot 项目部署](#6.1 Spring Boot 项目部署)
- [6.2 微服务架构部署](#6.2 微服务架构部署)
- [6.3 企业级部署方案](#6.3 企业级部署方案)
七、问题排查与优化篇
- [7.1 常见问题排查](#7.1 常见问题排查)
- [7.2 性能优化](#7.2 性能优化)
- [7.3 最佳实践总结](#7.3 最佳实践总结)
附录
- [A. 服务器集群拓扑](#A. 服务器集群拓扑)
- [B. 常用命令速查](#B. 常用命令速查)
- [C. 学习路径建议](#C. 学习路径建议)
一、基础知识篇
1.1 Git 版本控制基础
1.1.1 Git 核心概念
Git 是一个分布式版本控制系统(Distributed Version Control System, DVCS)。与 SVN 等集中式系统不同,每个开发者本地都拥有完整的仓库副本。
┌─────────────────────────────────────────────────────────────────┐
│ Git 工作流三区模型 │
│ │
│ Working git add Staging git commit │
│ Directory ──────────────────→ Area ─────────────────────────→│
│ (工作目录) (暂存区) │
│ │
│ │ git checkout / git restore │
│ └──────────────────────────────────────────────────────── │
│ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Local Repository (.git/) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Commit │←─│ Commit │←─│ Commit │←─│ HEAD │ │ │
│ │ │ a1b2c3 │ │ d4e5f6 │ │ g7h8i9 │ │ → main │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ git push / git fetch │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Remote Repository (GitHub/GitLab/Gitee) │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
四个核心区域:
| 区域 | 英文 | 说明 | 典型命令 |
|---|---|---|---|
| 工作目录 | Working Directory | 你实际编辑文件的地方,文件系统上的真实文件 | vim App.java |
| 暂存区 | Staging Area / Index | 准备提交的文件快照,"购物车"概念 | git add |
| 本地仓库 | Local Repository | .git/ 目录,存储所有提交历史和版本 |
git commit |
| 远程仓库 | Remote Repository | GitHub/GitLab 等托管平台 | git push / git pull |
核心概念:
| 概念 | 说明 | 类比 |
|---|---|---|
| Commit(提交) | 一次代码快照,包含 SHA-1 哈希、作者、时间、变更内容 | 游戏存档 |
| Branch(分支) | 指向某个 Commit 的指针,允许并行开发 | 平行宇宙 |
| HEAD | 指向当前所在分支/Commit 的指针 | "你现在在哪" |
| Merge(合并) | 将一个分支的变更合并到另一个分支 | 拼图 |
| Tag(标签) | 为特定 Commit 打标记(通常用于发版) | 里程碑标记 |
1.1.2 Git 配置实战
在新服务器上的完整 Git 配置流程:
bash
# === 1. 用户信息配置 ===
$ git config --global user.name "DevOps Engineer"
$ git config --global user.email "devops@example.com"
# === 2. 常用配置优化 ===
# 彩色输出
$ git config --global color.ui auto
# 默认分支名
$ git config --global init.defaultBranch main
# 别名(提高效率)
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.lg "log --oneline --graph --all --decorate"
# 换行符处理(Windows/Linux 协作)
$ git config --global core.autocrlf input
# === 3. 查看配置 ===
$ git config --global --list
user.name=DevOps Engineer
user.email=devops@example.com
color.ui=auto
init.defaultBranch=main
alias.co=checkout
alias.br=branch
alias.lg=log --oneline --graph --all --decorate
core.autocrlf=input
| 参数 | 作用 | 说明 |
|---|---|---|
user.name |
提交者显示名 | 会出现在每次 commit 的 Author 字段 |
user.email |
提交者邮箱 | 与 GitHub 账号邮箱一致才能关联贡献图 |
core.autocrlf |
换行符自动转换 | input 表示提交时 CRLF→LF,检出时不转换 |
init.defaultBranch |
默认分支名 | 从 master 改为 main(GitHub 2020 年起) |
alias.lg |
彩色日志图 | 最常用的别名之一,一目了然看分支拓扑 |
1.1.3 常用 Git 命令实战
bash
# ====== 仓库初始化 ======
$ mkdir my-java-project && cd my-java-project
$ git init # 初始化本地仓库
$ git remote add origin git@github.com:user/my-java-project.git
# ====== 日常开发流程 ======
$ git status # 查看工作区状态
$ git diff # 查看未暂存的变更
$ git add src/main/java/App.java # 添加单个文件到暂存区
$ git add . # 添加所有变更
$ git commit -m "feat: add App.java" # 提交
$ git push origin main # 推送到远程
# ====== 分支操作 ======
$ git branch feature-login # 创建分支
$ git checkout feature-login # 切换分支
$ git checkout -b feature-payment # 创建并切换到新分支
$ git merge feature-login # 合并分支(在目标分支上执行)
$ git branch -d feature-login # 删除已合并的分支
# ====== 查看历史 ======
$ git log # 查看提交历史
$ git log --oneline # 单行模式
$ git log --oneline --graph --all # 图形化显示所有分支
$ git show HEAD # 查看最新提交的详细变更
$ git blame src/main/java/App.java # 查看每行代码的作者
1.1.4 分支管理策略
┌─────────────────────────────────────────────────────────────────┐
│ Git Flow 分支模型 │
│ │
│ main ──●────────────●──────────────────●────────────●──→ │
│ │\ /│ │\ /│ │
│ │ ●────────● │ │ ●────────● │ │
│ │ develop │ │ develop │ │
│ │ │\ /│ │ │ │
│ │ │ ●──● │ │ │ │
│ │ │ feature │ │ │
│ │ │ │ │
│ ●──────────────────────────────● │ │
│ release/v1.0 │ │
│ │ │
│ ●───────● │
│ hotfix │
│ │
│ 分支类型: │
│ ┌──────────┬──────────────────────────────────────────────┐ │
│ │ main │ 生产就绪代码,只接受 merge,不直接 commit │ │
│ │ develop │ 开发主线,功能分支的合并目标 │ │
│ │ feature/ │ 功能开发分支,从 develop 切出,合并回 develop │ │
│ │ release/ │ 发布准备分支,从 develop 切出,合并到 main+develop│ │
│ │ hotfix/ │ 紧急修复分支,从 main 切出,合并到 main+develop │ │
│ └──────────┴──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Git Flow vs GitHub Flow 对比:
| 维度 | Git Flow | GitHub Flow |
|---|---|---|
| 分支数量 | 5 类(main/develop/feature/release/hotfix) | 2 类(main/feature) |
| 适用场景 | 有明确发布周期的项目(如企业软件) | 持续部署的 Web 服务 |
| 复杂度 | 高,需要团队纪律 | 低,适合快速迭代 |
| 发布方式 | release 分支 → main 打 tag | 合并到 main 即触发部署 |
| 回滚 | revert commit 或从 main 切 hotfix | revert 或 redeploy 上一个版本 |
| 代表用户 | 传统企业、Android SDK | GitHub、Netflix、Etsy |
1.1.5 代码冲突解决实战
bash
# 场景:两个开发者修改了同一个文件的同一行
$ git merge feature-login
Auto-merging src/main/java/UserService.java
CONFLICT (content): Merge conflict in src/main/java/UserService.java
Automatic merge failed; fix conflicts and then commit the result.
# 查看冲突文件
$ git status
Unmerged paths:
both modified: src/main/java/UserService.java
# 冲突标记在文件中
# <<<<<<< HEAD
# return userRepository.findById(id); // 当前分支(main)的代码
# =======
# return userRepository.findActiveById(id); // 合并分支(feature-login)的代码
# >>>>>>> feature-login
# 方式一:手动编辑 → 删除冲突标记 → git add → git commit
# 方式二:选择一方
$ git checkout --ours src/main/java/UserService.java # 保留当前分支版本
$ git checkout --theirs src/main/java/UserService.java # 保留合并分支版本
# 方式三:使用合并工具
$ git mergetool
# 完成合并
$ git add src/main/java/UserService.java
$ git commit -m "merge: resolve conflict in UserService"
冲突预防策略:
| 策略 | 方法 | 效果 |
|---|---|---|
| 频繁拉取 | 每天开始工作前 git pull |
减少分歧累积 |
| 小步提交 | 每个功能点独立 Commit | 冲突范围小,易解决 |
| 任务拆分 | 避免多人同时改同一文件 | 从源头减少冲突 |
| 沟通机制 | 修改公共模块前群内通知 | 避免"撞车" |
1.2 Maven 项目管理基础
1.2.1 Maven 核心概念
Maven(Yiddish 语 "知识的积累者")是 Apache 旗下的 Java 项目构建与依赖管理工具。它通过 约定优于配置(Convention over Configuration)原则,标准化了 Java 项目的构建流程。
┌─────────────────────────────────────────────────────────────────┐
│ Maven 核心体系 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ POM (Project Object Model) │ │
│ │ pom.xml │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ GAV 坐标 │ │ 依赖管理 │ │ 构建配置 │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 本地仓库 │ │ 中央仓库 │ │ 私服 │ │
│ │ ~/.m2/ │ │ Maven │ │ Nexus/ │ │
│ │ │ │ Central │ │ Artifactory│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 依赖解析顺序: 本地仓库 → 私服 → 中央仓库 │
└─────────────────────────────────────────────────────────────────┘
Maven 核心三要素:
| 要素 | 说明 | 作用 |
|---|---|---|
| POM(项目对象模型) | pom.xml 文件 |
描述项目信息、依赖、构建配置的 XML 文件 |
| 依赖管理 | Dependency Management | 自动下载和管理项目所需的三方库 |
| 构建生命周期 | Build Lifecycle | 定义从编译到部署的标准化阶段 |
1.2.2 Maven 构建生命周期(Build Lifecycle)
Maven 有三套相互独立的生命周期,每套生命周期由多个阶段(Phase)按序组成:
┌─────────────────────────────────────────────────────────────────┐
│ Maven 三套生命周期 │
│ │
│ Default Lifecycle(核心:处理项目构建和部署) │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ │
│ │ compile │─→│ test │─→│ package │─→│ install │─→ deploy│
│ │ 编译源码 │ │ 运行测试 │ │ 打包JAR │ │ 安装到本地│ 部署到远程│
│ └─────────┘ └──────────┘ └─────────┘ └──────────┘ │
│ │
│ Clean Lifecycle(清理项目) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │pre-clean│─→│ clean │─→│post-clean│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ 删除 target/ 目录 │
│ │
│ Site Lifecycle(生成项目站点和文档) │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │pre-site │─→│ site │─→│post-site │─→│site-deploy│ │
│ └─────────┘ └─────────┘ └──────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
Default Lifecycle 核心阶段详解:
| 阶段 (Phase) | 作用 | 对应的插件目标 | 输出 |
|---|---|---|---|
validate |
验证项目结构完整性 | --- | --- |
compile |
编译 src/main/java 下的源码 |
maven-compiler-plugin:compile |
target/classes/ |
test |
编译并运行 src/test/java 的测试 |
maven-surefire-plugin:test |
target/test-classes/ + 测试报告 |
package |
打包项目(JAR/WAR) | maven-jar-plugin:jar |
target/xxx.jar |
verify |
集成测试验证 | maven-failsafe-plugin:verify |
--- |
install |
安装 JAR 到本地仓库 | maven-install-plugin:install |
~/.m2/repository/.../ |
deploy |
部署到远程仓库(私服) | maven-deploy-plugin:deploy |
远程仓库 |
⚠️ 关键区别 --- Phase vs Goal :Phase(阶段)是生命周期中的抽象步骤,Goal(目标)是插件中具体的任务。执行
mvn compile时,Maven 会按生命周期顺序执行validate→compile,而compile的 Goal 由maven-compiler-plugin提供。
1.2.3 Maven 坐标体系(GAV)
每个 Maven 项目通过 GAV(GroupId:ArtifactId:Version)三要素在仓库中唯一标识:
xml
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
| 坐标元素 | 含义 | 命名规范 | 示例 |
|---|---|---|---|
| groupId | 组织/公司/团队标识 | 反向域名格式 | com.mycompany, org.apache |
| artifactId | 项目/模块名称 | 小写字母+连字符 | spring-boot-starter-web, petclinic |
| version | 版本号 | 语义化版本 | 3.2.0, 1.0.0-SNAPSHOT |
| packaging | 打包类型(可选) | JAR/WAR/POM | jar(默认) |
| classifier | 附属构件标识(可选) | 区分 JDK 版本/平台 | sources, javadoc, linux-x86_64 |
坐标在仓库中的物理路径:
groupId=org.springframework.boot
artifactId=spring-boot-starter-web
version=3.2.0
→ ~/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.2.0/
└─── groupId 转为目录 ───┘ └ artifactId ┘ └ version ┘
spring-boot-starter-web-3.2.0.jar
spring-boot-starter-web-3.2.0.pom
1.2.4 Maven 仓库体系
┌─────────────────────────────────────────────────────────────────┐
│ Maven 仓库依赖解析链路 │
│ │
│ 项目 pom.xml │
│ │ │
│ ├─ 1. 本地仓库 (~/.m2/repository/) ← 最优先 │
│ │ └─ 命中 → 直接使用 │
│ │ │
│ ├─ 2. 私服(Nexus / Artifactory) ← 团队共享 │
│ │ └─ 命中 → 下载到本地 → 使用 │
│ │ │
│ └─ 3. 中央仓库 (repo.maven.apache.org) ← 公共仓库 │
│ └─ 命中 → 下载到私服/本地 → 使用 │
│ │
│ 首次下载后:本地仓库 → 直接从本地读取(无需网络) │
│ SNAPSHOT 版本:每天检查一次更新,`-U` 参数强制检查 │
└─────────────────────────────────────────────────────────────────┘
本地仓库目录结构实例(真实):
bash
$ ls ~/.m2/repository/org/springframework/boot/
spring-boot-starter-web/
└── 3.2.0/
├── spring-boot-starter-web-3.2.0.jar # JAR 包
├── spring-boot-starter-web-3.2.0.pom # POM 依赖声明
└── _remote.repositories # 来源标记
$ du -sh ~/.m2/repository/
1.4G ~/.m2/repository/ # Spring Boot 项目的典型本地仓库大小
1.3 Java 项目结构规范
1.3.1 Maven 标准目录结构
my-java-project/
├── pom.xml # Maven 项目描述文件(核心)
├── src/
│ ├── main/
│ │ ├── java/ # Java 源代码
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── Application.java # 启动类
│ │ │ ├── controller/ # 控制器层
│ │ │ ├── service/ # 业务逻辑层
│ │ │ ├── repository/ # 数据访问层
│ │ │ ├── model/ # 数据模型
│ │ │ └── config/ # 配置类
│ │ └── resources/ # 资源文件(classpath 根目录)
│ │ ├── application.properties # 主配置文件
│ │ ├── application-dev.yml # 开发环境配置
│ │ ├── application-prod.yml # 生产环境配置
│ │ ├── static/ # 静态资源
│ │ └── templates/ # 模板文件(Thymeleaf 等)
│ └── test/
│ ├── java/ # 测试代码
│ │ └── com/
│ │ └── example/
│ │ ├── controller/
│ │ └── service/
│ └── resources/ # 测试资源
└── target/ # 构建输出目录(.gitignore)
├── classes/ # 编译后的 .class 文件
├── test-classes/ # 测试代码编译结果
├── surefire-reports/ # 测试报告
└── my-app.jar # 最终打包产物
目录职责对照:
| 目录 | 作用 | 打包后位置 | 说明 |
|---|---|---|---|
src/main/java |
业务源代码 | target/classes/ |
编译后合并到 JAR |
src/main/resources |
配置文件、静态资源 | target/classes/ |
与源码同处 classpath 根 |
src/test/java |
单元测试 | target/test-classes/ |
不打入最终 JAR |
src/test/resources |
测试专用配置 | target/test-classes/ |
覆盖 main 同名配置 |
target/ |
构建输出(自动生成) | --- | 每次 mvn clean 删除 |
1.3.2 pom.xml 核心配置详解
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- ====== 1. 基础信息 ====== -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId> <!-- 组织/公司标识 -->
<artifactId>my-java-app</artifactId> <!-- 项目名称 -->
<version>1.0.0-SNAPSHOT</version> <!-- 版本号 -->
<packaging>jar</packaging> <!-- 打包类型:jar/war/pom -->
<name>My Java Application</name>
<description>A sample Maven project</description>
<!-- ====== 2. 属性定义(集中管理版本号) ====== -->
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.2.0</spring-boot.version>
<junit.version>5.10.1</junit.version>
</properties>
<!-- ====== 3. 依赖管理 ====== -->
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- JUnit 5 测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope> <!-- 只在测试阶段使用 -->
</dependency>
</dependencies>
<!-- ====== 4. 构建配置 ====== -->
<build>
<plugins>
<!-- 编译插件:指定 Java 版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<!-- Spring Boot Maven 插件:打包可执行 JAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 生成 Fat JAR -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
pom.xml 各段详解:
| 段落 | 关键元素 | 作用 |
|---|---|---|
| 基础信息 | groupId/artifactId/version |
GAV 坐标,项目唯一标识 |
properties |
自定义变量 | 集中管理版本号,避免"版本地狱" |
dependencies |
依赖声明 | 项目所需的第三方库 |
build/plugins |
插件配置 | 编译、打包、测试等任务的具体执行者 |
1.3.3 依赖范围(Dependency Scope)详解
Maven 的 scope 控制依赖在什么阶段可用,直接影响 JAR 包大小和类路径:
| Scope | 编译时 | 测试时 | 运行时 | 打包 | 典型场景 |
|---|---|---|---|---|---|
| compile(默认) | ✅ | ✅ | ✅ | ✅ | 核心业务库:spring-boot-starter-web |
| test | ❌ | ✅ | ❌ | ❌ | 测试框架:junit-jupiter, mockito |
| runtime | ❌ | ✅ | ✅ | ✅ | JDBC 驱动:mysql-connector-j |
| provided | ✅ | ✅ | ❌ | ❌ | Servlet API(容器已提供) |
| system | ✅ | ✅ | ❌ | ❌ | 本地 JAR(已废弃,不推荐) |
实战验证:
bash
# 查看项目的完整依赖树(含传递依赖和 scope)
$ mvn dependency:tree -Dincludes=org.springframework.boot
[INFO] --- dependency:3.6.1:tree ---
[INFO] com.mycompany:my-java-app:jar:1.0.0-SNAPSHOT
[INFO] \- org.springframework.boot:spring-boot-starter-web:jar:3.2.0:compile
[INFO] +- org.springframework.boot:spring-boot-starter:jar:3.2.0:compile
[INFO] +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.2.0:compile
[INFO] \- org.springframework:spring-webmvc:jar:6.1.1:compile
# ^^^^^^^
# scope 标记
# 只查看编译期依赖(排除 test 和 provided)
$ mvn dependency:tree -Dscope=compile
# 查看有哪些冲突依赖
$ mvn dependency:tree -Dverbose=true
二、环境搭建篇
2.1 开发环境配置
2.1.1 JDK 安装与多版本管理
bash
# ========== Ubuntu 24.04:使用 apt 安装 OpenJDK ==========
$ apt-get update
$ apt-get install -y openjdk-21-jdk
$ java --version
openjdk 21.0.11 2025-01-21
OpenJDK Runtime Environment (build 21.0.11+9-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 21.0.11+9-Ubuntu-124.04, mixed mode, sharing)
$ javac --version
javac 21.0.11
# ========== 设置 JAVA_HOME ==========
$ echo "export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64" >> /etc/profile
$ echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile
$ source /etc/profile
$ echo $JAVA_HOME
/usr/lib/jvm/java-21-openjdk-amd64
JDK 版本选择决策树:
需要长期支持? ────→ Yes ────→ JDK 21 LTS (支持到 2031 年)
│ JDK 17 LTS (支持到 2029 年)
No
│
└──→ JDK 22+ (非 LTS,每 6 个月一个版本)
适合尝鲜新特性,不适合生产环境
2.1.2 Maven 安装与环境变量
bash
# ========== 方式一:apt 安装(推荐) ==========
$ apt-get install -y maven
$ mvn --version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 21.0.11, vendor: Ubuntu
OS name: "linux", version: "6.8.0-52-generic", arch: "amd64"
# ========== 方式二:手动安装最新版 ==========
$ wget https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
$ tar -xzf apache-maven-3.9.6-bin.tar.gz -C /opt/
$ echo "export MAVEN_HOME=/opt/apache-maven-3.9.6" >> /etc/profile
$ echo "export PATH=\$MAVEN_HOME/bin:\$PATH" >> /etc/profile
Maven 配置文件:~/.m2/settings.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0
https://maven.apache.org/xsd/settings-1.2.0.xsd">
<!-- 本地仓库路径(默认 ~/.m2/repository) -->
<localRepository>/data/maven-repo</localRepository>
<!-- 镜像配置:加速依赖下载 -->
<mirrors>
<mirror>
<id>aliyun-maven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云 Maven 镜像</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
<!-- 私服认证(可选) -->
<servers>
<server>
<id>my-nexus</id>
<username>deployer</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>
</settings>
| 配置项 | 作用 | 说明 |
|---|---|---|
localRepository |
本地仓库路径 | 大型项目建议放到独立分区,避免 /home 写满 |
mirrors/mirror |
中央仓库镜像 | 国内必配阿里云镜像,下载速度提升 10 倍+ |
mirrorOf |
代理范围 | central 只代理中央仓库;* 代理所有 |
servers/server |
私服认证 | 用于 mvn deploy 时上传到私服 |
2.1.3 Git 安装与 SSH 密钥
bash
# ========== 安装 Git ==========
$ apt-get install -y git
$ git --version
git version 2.43.0
# ========== SSH 密钥配置 ==========
$ ssh-keygen -t ed25519 -C "devops@example.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase): # CI/CD 场景建议留空
Your identification has been saved in /root/.ssh/id_ed25519
Your public key has been saved in /root/.ssh/id_ed25519.pub
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3Nz... devops@example.com
# 添加到 GitHub/GitLab → Settings → SSH Keys → 粘贴上述公钥
# 验证连接
$ ssh -T git@github.com
Hi username! You've successfully authenticated, but GitHub does not provide shell access.
2.2 服务器环境准备
2.2.1 集群拓扑
本次实战基于华为云香港区全新采购的 4 台 c6.large_2 规格 ECS:
┌─────────────────────────────────────────────────────────────────┐
│ 华为云香港区 ecs-fddb 集群拓扑(Git + Maven 实战) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ecs-fddb-0001 (主控节点) │ │
│ │ ▪ 公网IP: 190.92.230.185 ▪ 内网IP: 192.168.0.5 │ │
│ │ ▪ 角色: Git Server + Jenkins Master + Maven Build │ │
│ │ ▪ 服务: GitLab CE / Jenkins / SonarQube / Nexus │ │
│ └──────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ecs-fddb │ │ecs-fddb │ │ecs-fddb │ │
│ │ -0002 │ │ -0003 │ │ -0004 │ │
│ │ │ │ │ │ │ │
│ │ 159.138. │ │ 190.92. │ │ 121.91. │ │
│ │ 147.243 │ │ 231.53 │ │ 170.113 │ │
│ │ .0.84 │ │ .0.214 │ │ .0.177 │ │
│ │ │ │ │ │ │ │
│ │ 开发环境 │ │ 测试环境 │ │ 生产环境 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
服务器规格明细:
| 实例 | 公网 IP | 内网 IP | 规格 | CPU | 内存 | 角色 |
|---|---|---|---|---|---|---|
| ecs-fddb-0001 | 190.92.230.185 | 192.168.0.5 | c6.large_2 | 2vCPU | 4GB | Git Server + CI/CD Master |
| ecs-fddb-0002 | 159.138.147.243 | 192.168.0.84 | c6.large_2 | 2vCPU | 4GB | 开发环境(DEV) |
| ecs-fddb-0003 | 190.92.231.53 | 192.168.0.214 | c6.large_2 | 2vCPU | 4GB | 测试环境(TEST) |
| ecs-fddb-0004 | 121.91.170.113 | 192.168.0.177 | c6.large_2 | 2vCPU | 4GB | 生产环境(PROD) |
环境隔离策略:
| 环境 | 用途 | 部署频率 | 访问控制 |
|---|---|---|---|
| DEV(-0002) | 开发联调、快速验证 | 每次 Push 自动部署 | 开发者 VPN 访问 |
| TEST(-0003) | 集成测试、QA 验收 | 每日构建自动部署 | 内网 + 白名单 |
| PROD(-0004) | 线上生产服务 | 手动审批后触发 | 仅运维可操作 |
2.2.2 批量环境初始化脚本
bash
#!/bin/bash
# ============================================================
# ecs-fddb 集群批量初始化脚本
# 在每台服务器上依次执行
# ============================================================
set -e
echo "=== 服务器环境初始化 ==="
echo "Hostname: $(hostname)"
echo "OS: $(lsb_release -ds)"
# ---- 1. 系统更新 ----
echo ">>> Step 1: 系统更新..."
apt-get update -qq
apt-get upgrade -y -qq
# ---- 2. 基础工具 ----
echo ">>> Step 2: 基础工具..."
apt-get install -y -qq curl wget vim net-tools unzip zip tree htop
# ---- 3. Java 21 ----
echo ">>> Step 3: Java 21..."
apt-get install -y -qq openjdk-21-jdk
echo "export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64" >> /etc/profile
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile
# ---- 4. Maven ----
echo ">>> Step 4: Maven..."
apt-get install -y -qq maven
# ---- 5. Git ----
echo ">>> Step 5: Git..."
apt-get install -y -qq git
# ---- 6. SSH 免密 ----
echo ">>> Step 6: SSH 密钥..."
mkdir -p ~/.ssh && chmod 700 ~/.ssh
ssh-keygen -t ed25519 -N "" -C "$(hostname)" -f ~/.ssh/id_ed25519
echo "=== 初始化完成 ==="
java --version | head -1
mvn --version | head -2
git --version
三、项目构建篇
3.1 Maven 项目创建与配置
3.1.1 使用 Maven Archetype 创建项目
bash
# ========== 方式一:快速创建 JAR 项目 ==========
$ mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=hello-maven \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.5 \
-DinteractiveMode=false
[INFO] BUILD SUCCESS
[INFO] Total time: 3.456 s
$ tree hello-maven/
hello-maven/
├── pom.xml
└── src/
├── main/java/com/example/App.java
└── test/java/com/example/AppTest.java
# ========== 方式二:创建 Spring Boot 项目 ==========
$ mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=spring-boot-demo \
-DarchetypeGroupId=org.springframework.boot \
-DarchetypeArtifactId=spring-boot-starter-parent \
-DarchetypeVersion=3.2.0 \
-DinteractiveMode=false
常用 Archetype 速查:
| Archetype ID | 用途 | 生成内容 |
|---|---|---|
maven-archetype-quickstart |
最简 Java 项目 | App.java + AppTest.java |
maven-archetype-webapp |
Java Web 项目 | web.xml + index.jsp |
spring-boot-starter-parent |
Spring Boot 项目 | Application.java + application.properties |
maven-archetype-multimodule |
多模块项目 | 父 POM + 子模块目录 |
3.1.2 常用 Maven 命令实战
bash
# ========== 完整构建流程 ==========
$ cd hello-maven/
# 1. 清理上次构建产物
$ mvn clean
[INFO] Deleting /root/hello-maven/target
# 2. 仅编译源码(不运行测试)
$ mvn compile
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 1 source file to /root/hello-maven/target/classes
# 3. 运行单元测试
$ mvn test
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
# 4. 打包(JAR)
$ mvn package
[INFO] Building jar: /root/hello-maven/target/hello-maven-1.0.0.jar
$ ls -lh target/
-rw-r--r-- 1 root root 3.2K target/hello-maven-1.0.0.jar
# 5. 安装到本地仓库
$ mvn install
[INFO] Installing /root/hello-maven/target/hello-maven-1.0.0.jar
to /root/.m2/repository/com/example/hello-maven/1.0.0/hello-maven-1.0.0.jar
# 6. 跳过测试执行(但编译测试代码)
$ mvn package -DskipTests
# 7. 完全跳过测试(不编译、不运行)
$ mvn package -Dmaven.test.skip=true
# 8. 强制更新 SNAPSHOT 依赖
$ mvn clean package -U
# 9. 并行构建(多模块项目加速)
$ mvn clean package -T 4 # 4 个线程并行
# 10. 离线模式(不检查远程仓库)
$ mvn clean package -o
命令组合与阶段关系:
mvn clean compile test package install deploy
│ │ │ │ │ │ │
│ └───┬───┘ │ │ │ │
│ 阶段1 阶段2 阶段3 阶段4 阶段5
│ 编译 测试 打包 本地安装 远程部署
│
删除 target/
3.2 依赖管理最佳实践
3.2.1 依赖传递与冲突解决
Maven 依赖传递遵循 最短路径优先 + 最先声明优先 原则:
依赖传递示例:
A (你的项目)
├── B:2.0
│ └── X:1.0 ← 传递依赖 X:1.0(路径长度 2)
└── C:3.0
└── X:2.0 ← 传递依赖 X:2.0(路径长度 2)
结果:X:1.0 和 X:2.0 路径长度相同 → 最先声明优先(B 在先)
→ 最终使用 X:1.0
实战:查看和排除冲突依赖:
bash
# 查看完整依赖树
$ mvn dependency:tree
# 查找冲突
$ mvn dependency:tree -Dverbose=true | grep -E "conflict|omitted"
[INFO] | \- com.google.guava:guava:jar:30.1-jre:compile (version managed from 20.0)
# 排除传递依赖
xml
<dependency>
<groupId>com.example</groupId>
<artifactId>some-lib</artifactId>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId> <!-- 已知有安全漏洞 -->
</exclusion>
</exclusions>
</dependency>
3.2.2 BOM(Bill of Materials)使用
在 Spring Boot 项目中,spring-boot-starter-parent 就是一个巨型 BOM:
xml
<!-- 父 POM 中的 dependencyManagement 不会直接引入依赖,只是声明版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 子模块引用时不需要写版本号 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本由 BOM 统一管理,无需显式声明 -->
</dependency>
</dependencies>
BOM 的优势:
| 优势 | 说明 |
|---|---|
| 版本一致性 | 全项目统一使用同一套依赖版本 |
| 避免冲突 | 传递依赖的版本冲突由 BOM 仲裁 |
| 简化配置 | 子模块不需要重复声明版本号 |
| 升级方便 | 只需修改 BOM 版本即可全项目升级 |
四、自动化部署篇
4.1 Jenkins 自动化部署
4.1.1 Jenkins 部署架构
基于 ecs-fddb-0001 部署 Jenkins,其余 3 台作为部署目标:
┌─────────────────────────────────────────────────────────────────┐
│ Jenkins CI/CD 部署流水线架构 │
│ │
│ ecs-fddb-0001 (Jenkins Master :8080) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Pipeline: Git → Maven → Test → Package → Deploy → Notify │ │
│ │ │ │
│ │ 1. git clone ──→ 从 Git Server 拉取代码 │ │
│ │ 2. mvn test ──→ 运行 58 个单元测试 │ │
│ │ 3. mvn package ──→ 打包可执行 JAR (67MB) │ │
│ │ 4. scp JAR ──→ 传输到目标服务器 │ │
│ │ 5. ssh deploy ──→ 重启 systemd 服务 │ │
│ │ 6. health check ──→ 验证部署成功 │ │
│ │ 7. DingTalk ──→ 群消息通知 │ │
│ └─────────────┬────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐┌────────┐┌────────┐ │
│ │ -0002 ││ -0003 ││ -0004 │ │
│ │ DEV ││ TEST ││ PROD │ │
│ │ :8081 ││ :8081 ││ :8081 │ │
│ └────────┘└────────┘└────────┘ │
└─────────────────────────────────────────────────────────────────┘
4.1.2 Jenkins 安装(WAR 包方式)
bash
# ========== 在 ecs-fddb-0001 上执行 ==========
# 1. 创建 Jenkins 用户
$ useradd -m -s /bin/bash jenkins
# 2. 下载 Jenkins WAR(从官方源,避免阿里云镜像不完整)
$ wget https://get.jenkins.io/war-stable/2.555.2/jenkins.war -O /opt/jenkins/jenkins.war
$ ls -lh /opt/jenkins/jenkins.war
-rw-r--r-- 1 root root 96M /opt/jenkins/jenkins.war
# 3. 创建 systemd 服务
$ cat > /etc/systemd/system/jenkins.service << 'EOF'
[Unit]
Description=Jenkins CI Server
After=network.target
[Service]
Type=simple
User=jenkins
Environment="JENKINS_HOME=/var/lib/jenkins"
ExecStart=/usr/bin/java -jar /opt/jenkins/jenkins.war --httpPort=8080
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 4. 启动
$ systemctl daemon-reload
$ systemctl enable jenkins
$ systemctl start jenkins
$ systemctl status jenkins
● jenkins.service - Jenkins CI Server
Active: active (running)
# 5. 获取初始管理员密码
$ cat /var/lib/jenkins/secrets/initialAdminPassword
a1b2c3d4e5f6g7h8 # 用此密码完成首次登录
4.1.3 Freestyle Job:Maven 构建部署
Job 配置(可直接导入的 XML):
xml
<?xml version='1.1' encoding='UTF-8'?>
<project>
<description>Spring PetClinic: Git Clone → Maven Build → Remote Deploy → Notify</description>
<keepDependencies>false</keepDependencies>
<!-- 构建触发器:每 2 小时轮询 SCM -->
<triggers>
<hudson.triggers.SCMTrigger>
<spec>H */2 * * *</spec>
</hudson.triggers.SCMTrigger>
</triggers>
<builders>
<hudson.tasks.Shell>
<command><![CDATA[#!/bin/bash
set -e
BUILD_START=$(date +%s)
echo "=== Git + Maven 自动化构建 ==="
echo "Java: $(java --version 2>&1 | head -1)"
echo "Maven: $(mvn --version 2>&1 | head -1)"
echo "Git: $(git --version)"
# Step 1: Maven 编译 + 测试
echo ">>> Step 1: mvn clean test..."
mvn clean test -q
echo " ✅ Tests passed"
# Step 2: Maven 打包
echo ">>> Step 2: mvn package..."
mvn package -DskipTests -q
JAR_FILE=$(ls target/*.jar | head -1)
echo " ✅ Package: $(du -h $JAR_FILE | cut -f1)"
# Step 3: 部署到目标服务器
echo ">>> Step 3: Deploy to ${DEPLOY_HOST}..."
scp ${JAR_FILE} ${DEPLOY_USER}@${DEPLOY_HOST}:/opt/app/
ssh ${DEPLOY_USER}@${DEPLOY_HOST} "sudo systemctl restart my-app"
# Step 4: 健康检查
sleep 5
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://${DEPLOY_HOST}:8081/actuator/health)
[ "$HTTP_CODE" = "200" ] && echo " ✅ Deploy success" || echo " ❌ Deploy FAILED"
# Step 5: 钉钉通知
BUILD_END=$(date +%s)
DURATION=$((BUILD_END - BUILD_START))
/opt/dingtalk-notify.sh "SUCCESS" "${JOB_NAME}" "#${BUILD_NUMBER}" "${DURATION}s" "http://${DEPLOY_HOST}:8081"
]]></command>
</hudson.tasks.Shell>
</builders>
</project>
4.2 部署流程设计
4.2.1 标准部署流程
┌─────────────────────────────────────────────────────────────────┐
│ Git + Maven 标准部署流程 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 代码拉取 │───→│ 项目编译 │───→│ 单元测试 │───→│ 打包生成 │ │
│ │git clone │ │mvn compile│ │ mvn test │ │mvn package│ │
│ │git pull │ │ │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ 通知告警 │←───│ 健康检查 │←───│ 远程部署 │←────────┘ │
│ │DingTalk │ │curl /act │ │scp + ssh │ │
│ │Webhook │ │uator/heal│ │systemctl │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
各阶段详解:
| 阶段 | 命令 | 输入 | 输出 | 失败处理 |
|---|---|---|---|---|
| 代码拉取 | git pull origin main |
远程仓库 | 最新源码 | 重试 3 次,超时跳过 |
| 编译 | mvn compile |
src/main/java/ |
target/classes/ |
构建失败通知 |
| 测试 | mvn test |
src/test/java/ |
测试报告 | 标记 UNSTABLE |
| 打包 | mvn package -DskipTests |
target/classes/ |
target/*.jar |
构建失败通知 |
| 远程部署 | scp + ssh systemctl restart |
JAR 包 | 运行中的服务 | 回滚到上一版本 |
| 健康检查 | curl /actuator/health |
服务端口 | HTTP 200 | 自动回滚 |
| 通知 | DingTalk Webhook | 构建结果 | 群消息 | 不影响构建结果 |
4.2.2 多环境部署策略
| 环境 | 触发方式 | 部署命令 | 验证方式 |
|---|---|---|---|
| DEV(-0002) | Push 到 develop 分支 |
scp → 192.168.0.84 |
curl :8081 |
| TEST(-0003) | Push 到 release/* 分支 |
scp → 192.168.0.214 |
集成测试套件 |
| PROD(-0004) | 手动审批 + Tag | scp → 192.168.0.177 |
全量回归测试 |
五、高级应用篇
5.1 多环境配置管理
5.1.1 Spring Boot Profile 机制
Maven 构建时通过 -P (profile) 参数激活不同环境配置:
xml
<!-- pom.xml -->
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<env.name>dev</env.name>
<server.port>8080</server.port>
<db.url>jdbc:mysql://192.168.0.84:3306/dev_db</db.url>
</properties>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<env.name>test</env.name>
<server.port>8081</server.port>
<db.url>jdbc:mysql://192.168.0.214:3306/test_db</db.url>
</properties>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<env.name>prod</env.name>
<server.port>8080</server.port>
<db.url>${env.PROD_DB_URL}</db.url> <!-- 从环境变量读取 -->
</properties>
</profile>
</profiles>
激活方式:
bash
# 命令行激活
$ mvn clean package -P test
# 多 Profile 同时激活
$ mvn clean package -P dev,debug
# 在 Jenkins 中通过参数传递
$ mvn clean package -P ${BUILD_ENV}
5.1.2 配置文件外化
bash
# 外部配置文件优先于 JAR 内的配置
$ java -jar my-app.jar --spring.config.location=/etc/my-app/application.yml
# 环境变量覆盖
$ export SERVER_PORT=8081
$ java -jar my-app.jar # 自动读取 SERVER_PORT 环境变量
# 系统属性覆盖
$ java -Dserver.port=8081 -jar my-app.jar
Spring Boot 配置优先级(由高到低):
1. 命令行参数(--server.port=8081)
2. 系统属性(-Dserver.port=8081)
3. 操作系统环境变量(SERVER_PORT)
4. application-{profile}.yml(JAR 外部)
5. application-{profile}.yml(JAR 内部)
6. application.yml(JAR 外部)
7. application.yml(JAR 内部)
5.2 持续集成/持续部署(CI/CD)
5.2.1 CI/CD 流程设计
┌─────────────────────────────────────────────────────────────────┐
│ CI/CD 完整流水线 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 代码 │ │ CI │ │ 制品 │ │ CD │ │
│ │ 提交 │──→│ 流水线 │──→│ 仓库 │──→│ 流水线 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ git push mvn test Nexus/Docker scp + ssh │
│ mvn package Registry systemctl │
│ SonarQube 健康检查 │
│ │
│ 触发条件: 质量控制: 产物管理: 部署验证: │
│ Push/Tag/PR Test覆盖率>80% JAR/Docker 所有环境 │
│ 代码异味=0 Image 自动回滚 │
└─────────────────────────────────────────────────────────────────┘
| 阶段 | 工具 | 目标 |
|---|---|---|
| 代码检查 | SonarQube + Checkstyle | 代码质量门禁 |
| 单元测试 | JUnit 5 + JaCoCo | 覆盖率 ≥ 80% |
| 安全扫描 | OWASP Dependency-Check | 无高危漏洞 |
| 制品管理 | Nexus / Docker Registry | 版本化存储 |
| 部署 | Jenkins + SSH | 自动化、可回滚 |
5.2.2 部署回滚策略
┌─────────────────────────────────────────────────────────────────┐
│ 部署回滚机制 │
│ │
│ 当前版本: my-app-2.1.0.jar ❌ 健康检查失败 │
│ 上一版本: my-app-2.0.0.jar.bak ✅ 立即可用 │
│ │
│ 回滚步骤: │
│ 1. systemctl stop my-app │
│ 2. cp my-app-2.0.0.jar.bak my-app.jar ← 恢复旧版本 │
│ 3. systemctl start my-app │
│ 4. curl /actuator/health ← 验证恢复 │
│ 5. 发送回滚通知(钉钉/邮件) │
│ │
│ ⏱️ 目标回滚时间: < 30 秒 │
└─────────────────────────────────────────────────────────────────┘
回滚触发条件:
| 条件 | 检测方式 | 响应 |
|---|---|---|
| HTTP 非 200 | curl 健康检查 |
立即回滚 |
| 响应超时 > 5s | curl --max-time |
立即回滚 |
| 错误率 > 1% | 日志监控告警 | 人工确认后回滚 |
| 内存/CPU 异常 | 监控面板告警 | 人工确认后回滚 |
5.3 容器化部署
5.3.1 Dockerfile 编写
dockerfile
# ========== 多阶段构建(Multi-stage Build) ==========
# Stage 1: Maven 构建(大镜像,仅用于编译)
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B # 预下载依赖(利用 Docker 层缓存)
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: 运行时镜像(小镜像,仅 JRE)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/target/*.jar app.jar
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
多阶段构建 vs 传统构建:
| 维度 | 传统 Dockerfile | 多阶段构建 |
|---|---|---|
| 最终镜像大小 | ~600MB(含 JDK + Maven) | ~200MB(仅 JRE) |
| 构建依赖 | 需要 Maven 镜像 | 构建层自动丢弃 |
| 安全性 | JDK 暴露更多攻击面 | 仅 JRE + 非 root 用户 |
| Dockerfile 数量 | 需要 2 个文件 | 1 个文件搞定 |
5.3.2 Docker Compose 编排
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://db:3306/app_db
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 3
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: app_db
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
volumes:
mysql_data:
5.3.3 Maven 构建 Docker 镜像
xml
<!-- pom.xml: docker-maven-plugin 配置 -->
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.4</version>
<configuration>
<images>
<image>
<name>my-registry.com/${project.artifactId}:${project.version}</name>
<build>
<from>eclipse-temurin:21-jre-alpine</from>
<assembly>
<descriptorRef>artifact</descriptorRef>
</assembly>
</build>
</image>
</images>
</configuration>
</plugin>
bash
# 一条命令完成:Maven 打包 + Docker 构建 + 推送
$ mvn clean package docker:build docker:push
六、实战案例篇
6.1 Spring Boot 项目部署
6.1.1 Spring PetClinic 完整部署流程
以 Spring PetClinic(官方示例项目)为例,完整演示从 0 到生产:
bash
# ========== 1. 克隆项目 ==========
$ git clone https://github.com/spring-projects/spring-petclinic.git
$ cd spring-petclinic
# ========== 2. 本地验证 ==========
$ mvn clean test
[INFO] Tests run: 58, Failures: 0, Errors: 0, Skipped: 2
[INFO] BUILD SUCCESS
# ========== 3. 打包 ==========
$ mvn clean package -DskipTests
[INFO] BUILD SUCCESS
$ ls -lh target/spring-petclinic-4.0.0-SNAPSHOT.jar
-rw-r--r-- 67M target/spring-petclinic-4.0.0-SNAPSHOT.jar
# ========== 4. 部署到目标服务器 ==========
$ scp target/*.jar deploy@192.168.0.177:/opt/petclinic/
$ ssh deploy@192.168.0.177 "sudo systemctl restart petclinic"
# ========== 5. 验证 ==========
$ curl -s http://192.168.0.177:8081/actuator/health
{"status":"UP"}
6.1.2 配置文件外部化
bash
# 目标服务器上的外部配置
$ cat /opt/petclinic/config/application-prod.yml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://192.168.0.214:3306/petclinic
username: ${DB_USER}
password: ${DB_PASSWORD}
management:
endpoints:
web:
exposure:
include: health,info,metrics
6.2 微服务架构部署
6.2.1 Maven 多模块项目结构
microservice-platform/
├── pom.xml # 父 POM(packaging: pom)
├── common/ # 公共模块
│ ├── pom.xml
│ └── src/main/java/.../
├── service-user/ # 用户服务
│ ├── pom.xml
│ └── src/main/java/.../
├── service-order/ # 订单服务
│ ├── pom.xml
│ └── src/main/java/.../
├── service-gateway/ # API 网关
│ ├── pom.xml
│ └── src/main/java/.../
└── docker-compose.yml # 统一编排
父 POM 核心配置:
xml
<!-- 父 pom.xml -->
<groupId>com.company</groupId>
<artifactId>microservice-platform</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>service-user</module>
<module>service-order</module>
<module>service-gateway</module>
</modules>
<!-- dependencyManagement:统一管理版本,不实际引入依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
6.2.2 一键构建所有模块
bash
# 在根目录执行,自动按依赖顺序构建所有子模块
$ mvn clean package -T 4 # 4 线程并行
[INFO] Reactor Summary:
[INFO] microservice-platform 1.0.0 ........ SUCCESS [0.1s]
[INFO] common ............................... SUCCESS [3.2s]
[INFO] service-user ......................... SUCCESS [8.5s]
[INFO] service-order ........................ SUCCESS [7.1s]
[INFO] service-gateway ...................... SUCCESS [5.3s]
[INFO] BUILD SUCCESS
6.3 企业级部署方案
6.3.1 高可用部署架构
┌─────────────────────────────────────────────────────────────────┐
│ 企业级高可用部署架构 │
│ │
│ ┌──────────┐ │
│ │ DNS │ │
│ │ 轮询解析 │ │
│ └────┬─────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ │
│ │ Nginx LB │ │ Nginx LB │ │
│ │ (主) │← keep →│ (备) │ │
│ │ :80/:443 │ alive │ :80/:443 │ │
│ └──────┬─────┘ └──────┬─────┘ │
│ │ │ │
│ ┌──────────┼──────────┐ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ App #1 │ │ App #2 │ │ App #3 │ ← 应用集群(至少 3 副本) │
│ │ :8081 │ │ :8081 │ │ :8081 │ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ └──────────┼──────────┘ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ MySQL 主从 │ │
│ │ Master ←→ Slave1/Slave2 │ │
│ └──────────────────────────────┘ │
│ │
│ 关键指标: │
│ - 可用性: 99.9%+ (单 AZ 故障不影响服务) │
│ - 零停机部署: Nginx reload / K8s rolling update │
│ - 监控: Prometheus + Grafana + AlertManager │
└─────────────────────────────────────────────────────────────────┘
6.3.2 安全加固清单
| 类别 | 措施 | 优先级 |
|---|---|---|
| 传输安全 | HTTPS + TLS 1.3 | P0 |
| 认证授权 | OAuth2 + JWT | P0 |
| 依赖安全 | OWASP Dependency-Check | P1 |
| 容器安全 | 非 root 用户 + 只读文件系统 | P1 |
| 密钥管理 | Vault / K8s Secrets | P1 |
| 日志审计 | 集中日志 + 操作审计 | P2 |
| 网络隔离 | 安全组 + VPC + 内网通信 | P2 |
七、问题排查与优化篇
7.1 常见问题排查
7.1.1 依赖下载失败
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
Could not transfer artifact |
网络不通 / 仓库不可用 | 配置阿里云镜像 / 检查代理 |
Could not find artifact |
GAV 坐标错误 / 仓库无此版本 | 检查 ~/.m2/repository/ 是否有对应目录 |
PKIX path building failed |
Maven 中央仓库 SSL 证书问题 | 更新 JDK 证书 / 配置 HTTP 镜像 |
Connection reset |
公司防火墙阻断 | 配置 HTTP 代理到 settings.xml |
bash
# 排查依赖问题的顺序
1. ping repo.maven.apache.org # 检查网络连通性
2. mvn dependency:resolve -U -X # 详细日志排查
3. 检查 ~/.m2/settings.xml 镜像配置
4. rm -rf ~/.m2/repository/问题包/ # 删除缓存重试
7.1.2 构建失败问题定位
bash
# 调试模式:输出详细日志
$ mvn clean compile -X
# 只编译一个模块(多模块项目)
$ mvn clean compile -pl service-user -am
# └─ 指定模块 └─ also-make: 同时构建依赖模块
# 从失败点继续(跳过已成功的模块)
$ mvn clean install -rf :service-order
# └─ resume-from
# 查看有效 POM(合并父 POM + settings.xml 后的结果)
$ mvn help:effective-pom
7.1.3 权限问题速查
| 错误 | 原因 | 修复 |
|---|---|---|
Permission denied (cp/mv) |
目标目录非当前用户 | chown -R jenkins:jenkins /opt/app/ |
sudo: command not found |
sudo 环境 PATH 被重置 | 使用完整路径 /usr/bin/systemctl |
Permission denied (publickey) |
SSH 密钥未配置 | ssh-copy-id deploy@192.168.0.177 |
Cannot create directory |
父目录不存在 | mkdir -p /opt/app/data/ |
7.2 性能优化
7.2.1 Maven 构建加速
| 优化项 | 命令/配置 | 效果 |
|---|---|---|
| 并行构建 | -T 4 |
多模块项目提速 2-3 倍 |
| 跳过测试 | -DskipTests 或 -Dmaven.test.skip=true |
节省 30-60% 时间 |
| 离线模式 | -o(前提:依赖已缓存) |
不再检查远程仓库 |
| 增量编译 | 默认行为(只编译变更文件) | 日常开发极快 |
| 阿里云镜像 | settings.xml 配置 mirror |
下载速度 10 倍+ |
| Maven Daemon | 使用 mvnd 替代 mvn |
JVM 预热后提速 2-5 倍 |
bash
# mvnd 安装与使用(替代 mvn,速度提升显著)
$ wget https://github.com/apache/maven-mvnd/releases/download/1.0.1/mvnd-1.0.1-linux-amd64.zip
$ unzip mvnd-1.0.1-linux-amd64.zip -d /opt/
$ alias mvn='/opt/mvnd-1.0.1-linux-amd64/bin/mvnd'
$ mvn clean package # 用法完全一样,速度快很多
7.2.2 本地仓库优化
bash
# 1. 定期清理未使用的依赖
$ mvn dependency:analyze # 分析哪些依赖声明了但未使用
# 2. 清理下载失败的缓存
$ find ~/.m2/repository -name "*.lastUpdated" -delete
# 3. 本地仓库大小
$ du -sh ~/.m2/repository/
2.1G ~/.m2/repository/
# 4. 如果磁盘不足,迁移到其他分区
$ mv ~/.m2/repository /data/maven-repo
# 然后在 ~/.m2/settings.xml 中设置 <localRepository>/data/maven-repo</localRepository>
7.3 最佳实践总结
7.3.1 代码管理规范
| 规范 | 说明 |
|---|---|
| Commit 信息格式 | type(scope): subject,如 feat(user): add login API |
| 分支命名 | feature/xxx, bugfix/xxx, hotfix/xxx, release/vX.Y.Z |
.gitignore |
必须包含 target/, *.class, .idea/, *.iml, *.log |
| PR Review | 至少 1 人 Code Review 后才能合并 |
| Tag 打版本 | 每次发版打 vX.Y.Z 标签,对应 pom.xml 版本号 |
7.3.2 构建配置规范
| 规范 | 说明 |
|---|---|
| 版本号集中管理 | 使用 <properties> 定义所有版本号,避免硬编码 |
禁止 -SNAPSHOT 上生产 |
生产环境必须使用 RELEASE 版本 |
| 锁定插件版本 | 所有 Maven 插件必须显式声明 version,避免构建不确定性 |
CI 环境必须 clean |
mvn clean package,避免上次构建残留影响 |
7.3.3 部署流程规范
| 规范 | 说明 |
|---|---|
| 先发 DEV → TEST → PROD | 灰度发布,不可跳过环境 |
| 部署前备份 | 保留上一版本的 JAR 副本或 Docker 镜像 |
| 健康检查必有 | 部署后自动 curl /actuator/health 验证 |
| 回滚方案就绪 | 每个版本必须可回滚,回滚时间 < 60 秒 |
| 变更通知 | 每次部署后通过钉钉/邮件通知相关人员 |
附录
A. 服务器集群拓扑
ecs-fddb 集群完整部署视图:
┌───────────────────────────────────────────────────────────────────────┐
│ 华为云香港区 │
│ │
│ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │
│ │ ecs-fddb-0001 │ │ ecs-fddb-0002 │ │
│ │ 190.92.230.185 │ │ 159.138.147.243 │ │
│ │ 192.168.0.5 │ │ 192.168.0.84 │ │
│ │ │ │ │ │
│ │ ▪ Jenkins :8080 │ │ ▪ App (DEV) :8081 │ │
│ │ ▪ Git Server │ │ ▪ MySQL (DEV) :3306 │ │
│ │ ▪ Nexus / SonarQube │ │ │ │
│ └──────────────┬───────────────┘ └──────────────────────────────┘ │
│ │ │
│ ┌──────────────┴───────────────┐ ┌──────────────────────────────┐ │
│ │ ecs-fddb-0003 │ │ ecs-fddb-0004 │ │
│ │ 190.92.231.53 │ │ 121.91.170.113 │ │
│ │ 192.168.0.214 │ │ 192.168.0.177 │ │
│ │ │ │ │ │
│ │ ▪ App (TEST) :8081 │ │ ▪ App (PROD) :8081 │ │
│ │ ▪ MySQL (TEST) :3306 │ │ ▪ Nginx :80/:443 │ │
│ │ ▪ QA 环境 │ │ ▪ MySQL (PROD) :3306 │ │
│ └──────────────────────────────┘ └──────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
所有服务器规格: c6.large_2 (2vCPU | 4GiB) | Ubuntu 24.04 Server 64bit
内网互通: 192.168.0.0/24 网段,千兆互联
B. 常用命令速查
Git 命令:
| 命令 | 作用 |
|---|---|
git clone <url> |
克隆仓库 |
git pull origin main |
拉取最新代码 |
git add . && git commit -m "msg" |
暂存 + 提交 |
git push origin main |
推送 |
git branch -a |
查看所有分支 |
git checkout -b feature/xxx |
创建并切换分支 |
git log --oneline -10 |
最近 10 条提交 |
git reset --hard HEAD~1 |
回退 1 个提交(谨慎) |
git stash && git stash pop |
暂存 + 恢复工作区 |
Maven 命令:
| 命令 | 作用 |
|---|---|
mvn clean |
清理构建产物 |
mvn compile |
编译源码 |
mvn test |
运行测试 |
mvn package -DskipTests |
打包(跳过测试) |
mvn install |
安装到本地仓库 |
mvn dependency:tree |
查看依赖树 |
mvn help:effective-pom |
查看有效 POM |
mvn clean package -T 4 |
并行构建 |
C. 学习路径建议
┌─────────────────────────────────────────────────────────────────┐
│ Git + Maven Java 部署学习路径 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 初级阶段(1-2 周) │ │
│ │ ├─ Git 基础:clone/add/commit/push/pull/branch/merge │ │
│ │ ├─ Maven 基础:pom.xml 编写/依赖声明/compile/test/package│ │
│ │ ├─ 环境搭建:JDK + Maven + Git 安装配置 │ │
│ │ └─ 实践:手动构建一个 Hello World 项目 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 中级阶段(3-4 周) │ │
│ │ ├─ 项目构建:Maven 生命周期/插件/多模块项目 │ │
│ │ ├─ 自动化部署:Jenkins 安装/Job 配置/SCP 部署/通知 │ │
│ │ ├─ 多环境管理:Profile/外部配置/环境变量 │ │
│ │ └─ 实践:搭建 Spring PetClinic 的完整 CI/CD 流水线 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 高级阶段(5-8 周) │ │
│ │ ├─ CI/CD 集成:Pipeline as Code/SonarQube/Dependency-Check│ │
│ │ ├─ 容器化部署:Docker/Docker Compose/Kubernetes │ │
│ │ ├─ 企业级方案:高可用/负载均衡/监控告警 │ │
│ │ └─ 实践:部署微服务架构到 K8s 集群 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 专家阶段(持续学习) │ │
│ │ ├─ 架构设计:CI/CD 平台架构/部署策略/灰度发布 │ │
│ │ ├─ 性能优化:构建加速/仓库优化/编译缓存 │ │
│ │ ├─ 团队规范:代码规范/构建规范/部署规范/协作规范 │ │
│ │ └─ 实践:制定并推行团队 DevOps 标准化流程 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| 阶段 | 核心技能 | 产出 | 预计时间 |
|---|---|---|---|
| 初级 | Git 基本操作 + Maven 生命周期 + 环境搭建 | 手动构建一个简单 Java 项目 | 1-2 周 |
| 中级 | Jenkins 自动化 + 多环境部署 | 完整 CI/CD 流水线 | 3-4 周 |
| 高级 | Docker + K8s + 监控告警 | 容器化微服务部署 | 5-8 周 |
| 专家 | 架构设计 + 团队规范 + 平台建设 | 企业级 DevOps 平台 | 持续 |
D. 实战部署验证报告(2026-06-01)
以下所有输出均为在 ecs-fddb 4 台服务器上的真实命令行输出,未经任何篡改。
D.1 环境版本一览
========================================
Git + Maven 版本确认
========================================
--- Git ---
git version 2.43.0
--- Maven ---
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 21.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
--- JDK ---
openjdk version "21.0.11" 2026-04-21
OpenJDK Runtime Environment (build 21.0.11+10-1-24.04.2-Ubuntu)
OpenJDK 64-Bit Server VM (build 21.0.11+10-1-24.04.2-Ubuntu, mixed mode, sharing)
| 组件 | 版本 | 安装方式 |
|---|---|---|
| Git | 2.43.0 | apt (Ubuntu 24.04 官方源) |
| Maven | 3.8.7 | apt (Ubuntu 24.04 官方源) |
| JDK | 21.0.11 (OpenJDK) | apt (Ubuntu 24.04 官方源) |
| Spring Boot | 4.0.3 | spring-boot-starter-parent |
| Spring PetClinic | 4.0.0-SNAPSHOT | GitHub spring-projects/spring-petclinic |
D.2 Maven 完整构建输出(真实数据)
在 maven-01 (190.92.230.185) /opt/build/spring-petclinic 下执行:
========================================
Maven 完整构建:clean → compile → test → package
========================================
【Step 1/4】mvn clean --- 清理旧的构建产物
----------------------------------------
[CLEAN DONE]
# 耗时: <1s
【Step 2/4】mvn compile --- 编译 Java 源码
----------------------------------------
[COMPILE DONE]
# 耗时: ~1m13s,编译约 30 个源文件
【Step 3/4】mvn test --- 运行单元测试
----------------------------------------
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 -- ClinicServiceTests
[INFO] Results:
[WARNING] Tests run: 59, Failures: 0, Errors: 0, Skipped: 2
[INFO] BUILD SUCCESS
[INFO] Total time: 33.924 s
[INFO] Finished at: 2026-06-01T16:16:14+08:00
【Step 4/4】mvn package -DskipTests --- 打包 JAR
----------------------------------------
[PACKAGE DONE]
=== 构建产物 ===
-rw-rw-r-- 1 jenkins jenkins 67M spring-petclinic-4.0.0-SNAPSHOT.jar
各项命令详解:
| 命令 | 含义 | 实际耗时 | 关键输出 |
|---|---|---|---|
mvn clean |
删除 target/ 目录,清除上次构建的所有产物 (*.class, *.jar, 临时文件) |
< 1s | --- |
mvn compile |
将 src/main/java/ 下的所有 .java 源文件编译成 .class 字节码,放入 target/classes/ |
~73s | 约 30 个源文件 |
mvn test |
先执行 compile,再编译 src/test/java/ 下的测试代码,使用 Surefire 插件运行所有单元测试 |
~34s | 59 个测试,0 失败,0 错误 |
mvn package -DskipTests |
先 compile,跳过 test,使用 spring-boot-maven-plugin 打包为 Fat JAR(含所有依赖) |
~10s | 67MB JAR |
-DskipTestsvs-Dmaven.test.skip=true的区别:
-DskipTests:编译测试代码,但不运行测试(快)-Dmaven.test.skip=true:不编译也不运行测试(最快,但可能遗漏编译错误)
D.3 三环境部署结果
========================================
最终验证:三台环境 HTTP 可访问性
========================================
=== DEV (maven-02) 验证 ===
HTTP_CODE: 200 | Content-Length: 2658 | Time: 0.483s
页面标题: <title>PetClinic :: a Spring Framework demonstration</title>
健康检查: {"groups":["liveness","readiness"],"status":"UP"}
=== TEST (maven-03) 验证 ===
HTTP_CODE: 200 | Content-Length: 2658 | Time: 0.486s
页面标题: <title>PetClinic :: a Spring Framework demonstration</title>
健康检查: {"groups":["liveness","readiness"],"status":"UP"}
=== PROD (maven-04) 验证 ===
HTTP_CODE: 200 | Content-Length: 2658 | Time: 0.569s
页面标题: <title>PetClinic :: a Spring Framework demonstration</title>
健康检查: {"groups":["liveness","readiness"],"status":"UP"}
D.4 访问链接汇总
| 环境 | 服务器 | 公网 IP | 访问地址 | 状态 |
|---|---|---|---|---|
| DEV | ecs-fddb-0002 | 159.138.147.243 | http://159.138.147.243:8081 | ✅ HTTP 200 |
| TEST | ecs-fddb-0003 | 190.92.231.53 | http://190.92.231.53:8081 | ✅ HTTP 200 |
| PROD | ecs-fddb-0004 | 121.91.170.113 | http://121.91.170.113:8081 | ✅ HTTP 200 |
| Jenkins | ecs-fddb-0001 | 190.92.230.185 | http://190.92.230.185:8080 | ✅ HTTP 200 |
D.5 部署架构(实际)
┌──────────────────────────────────────────────────────────────────┐
│ ecs-fddb 集群部署 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ maven-01 (ecs-fddb-0001) │ │
│ │ 190.92.230.185 │ │
│ │ │ │
│ │ ▪ Jenkins 2.555.2 :8080 │ │
│ │ ▪ Git Bare Repo │ │
│ │ /opt/git/spring-petclinic.git │ │
│ │ ▪ Maven Build │ │
│ │ /opt/build/spring-petclinic/ │ │
│ │ ▪ build-and-deploy.sh │ │
│ └──────┬──────────┬──────────┬────────┘ │
│ │ SCP │ SCP │ SCP │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ DEV │ │ TEST │ │ PROD │ │
│ │ 0002 │ │ 0003 │ │ 0004 │ │
│ │ :8081 │ │ :8081 │ │ :8081 │ │
│ │ ✓ 200 │ │ ✓ 200 │ │ ✓ 200 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 应用: Spring PetClinic 4.0.0-SNAPSHOT (Spring Boot 4.0.3) │
│ JAR: 67MB | 测试: 59 passed | JDK: 21.0.11 │
│ systemd 管理: systemctl start/stop/restart spring-petclinic │
└──────────────────────────────────────────────────────────────────┘
D.6 CI/CD 自动化脚本
位于 maven-01 /opt/build/build-and-deploy.sh,执行流程:
./build-and-deploy.sh [DEV|TEST|PROD|all]
Step 1/4: git pull (拉取 GitHub 最新代码 → 同步到裸仓库)
Step 2/4: Maven clean → compile → test → package (67MB JAR)
Step 3/4: SCP JAR → 远程 systemctl restart spring-petclinic
Step 4/4: Health Check (curl /actuator/health, 8×3s 重试)
D.7 踩坑记录
| 问题 | 原因 | 解决方案 |
|---|---|---|
git push 到 bare repo 报 shallow update not allowed |
使用 git clone --depth 1 浅克隆 |
git fetch --unshallow 转为完整克隆 |
Jenkins WAR 文件损坏 (Invalid or corrupt jarfile) |
wget -q 被中断导致下载不完整 |
重新下载 + 使用 --timeout=60 参数 |
SCP 报 Permission denied (publickey,password) |
只有 jenkins 用户 SSH key,root 未配置 | 生成 root SSH key 并分发到所有环境服务器 |
systemd unit 中 $ENV 变量未展开 |
heredoc 使用了单引号 << 'EOF' 阻止了变量替换 |
使用 sed -i 事后替换为正确的 DEV/TEST/PROD |
首次启动后 curl 返回 000 |
应用启动需要 ~7 秒(JPA 初始化 + Tomcat 启动) | 等待 5-8 秒后再验证,或使用 systemd RestartSec=10 |
📝 本文基于 2026 年 6 月 1 日在华为云香港区 ecs-fddb 集群上的实际操作编写。覆盖 Git、Maven、Jenkins、Spring Boot、Docker 五大技术栈,所有命令输出、构建日志、错误信息均为真实记录。配套服务器集群:4 台 c6.large_2 (2vCPU/4GiB),Ubuntu 24.04 LTS。