1. 现实问题:服务器上“复制一下 JAR”不是部署方案
应用在本地能跑,部署后可能因为 JDK、文件路径、端口、环境变量、数据库访问或代理配置不同而失败。一个可交付版本需要知道它来自哪个 Git commit,运行在哪个镜像,监听哪个端口,健康检查如何通过,以及失败时如何回滚。
Git 管改动历史,Linux 管进程和权限,Docker 管运行时封装,Nginx 管外部流量和 TLS/反向代理,CI 把测试与打包自动执行。它们不是五个孤立命令,而是一条有证据的交付链。
2. 最小可运行示例:固定镜像、端口和健康检查
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/blog.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
git status --short
git add src/main/java src/main/resources
git commit -m "feat: publish article flow"
git rev-parse --short HEAD
docker build -t blog:<commit-sha> .
docker run --rm -p 8080:8080 --env-file .env.production blog:<commit-sha>
curl -f http://127.0.0.1:8080/actuator/health
命令中的 <commit-sha> 只是发布标签示意,实际脚本应使用已验证的变量并拒绝空值。Nginx 可以把公网 443 转发到 127.0.0.1:8080,但应用容器内部监听、Docker 映射和 Nginx upstream 三个端口要分别核对。
3. 调用链与对象变化
Git commit 把工作树差异变成不可变记录,CI checkout 该 SHA 后解析 Maven 依赖、编译、测试并生成 JAR。Docker build 把 JRE、JAR 和启动命令打进镜像,运行时读取环境变量并创建容器进程;宿主机端口映射把外部端口转到容器 8080。
请求先到 Nginx,Nginx 根据域名和路径转发到 upstream;应用 Filter、Security 和 MVC 处理请求,Actuator 返回 health。curl 的状态码、容器日志、docker inspect、Nginx error log 和 commit SHA 共同组成证据,不是单独一句“启动成功”。
4. 为什么这样设计
镜像固定运行时,减少“服务器刚好装了什么”的差异;标签绑定 commit,方便回滚到上一镜像。配置外置,避免把密钥打进镜像;容器以非 root 用户运行、只读文件系统和最小 JRE 能降低风险。镜像不是虚拟机,数据要放卷或外部服务,容器销毁不等于数据备份。
Nginx 负责 TLS、域名、静态资源和反代超时,Java 服务负责业务;Forwarded 头、客户端 IP 和上传大小要在两层都明确。CI 先跑快速单测,再跑集成测和构建,失败不推发布;部署策略可以是滚动、蓝绿或单机替换,但都要有健康门和回滚路径。
5. 项目落点:交付清单和回滚清单一起写
博客系统发布前记录:commit SHA、JDK 21、Maven 结果、测试数、镜像摘要、配置键名(不含值)、数据库迁移版本、健康检查 URL、Nginx 配置校验和回滚镜像。Linux 上检查 ss -lntp、journalctl 或容器日志;不要直接 kill -9 作为常规运维。
练习:写一个 CI job 执行 mvn test package、构建带 SHA 的镜像并保存测试报告;在本地启动两个版本,切换 Nginx upstream,验证旧版本仍能回滚。故意错配端口、权限和环境变量,按进程—容器—代理顺序定位。
6. 易错排查
- 容器健康但公网 502:检查应用监听地址、端口映射、Nginx upstream 和防火墙。
- 镜像能构建但启动失败:查看 JAR、工作目录、JRE 版本、环境变量和容器日志。
- CI 绿但部署坏:CI 没有真实启动/健康检查;增加 smoke test 和运行时配置验证。
- 回滚只回 JAR 不回数据库:记录迁移兼容策略,破坏性 schema 变更先做分阶段发布。
7. 一页复习
Git SHA 定位改动,CI 证明构建和测试,Docker 固定运行时,Nginx 转发流量,Actuator/curl 证明服务状态。部署交付物必须包含启动命令、端口、配置来源、健康门和回滚命令。任何未保存的日志、镜像摘要和数据库版本都会让复盘失去抓手。
部署状态可以写成四份对象:Git commit 是源代码快照,CI 产物是经过测试的 JAR,Docker image 是带 JRE 和启动命令的不可变包,容器进程是读取配置后的运行实例。Nginx upstream 再把外部请求转到实例,Actuator/curl 输出健康结果。四份对象必须由 SHA、digest、端口和日志串起来,缺一份就难以回滚。
项目文件可以把 Dockerfile、compose/部署清单、Nginx 配置和 CI workflow 放在仓库的 deploy 目录,README 记录启动、停止、健康检查和回滚;应用配置只写键名和示例,不提交秘密。Git 每个交付 commit 应有测试数量、迁移版本和兼容说明,数据库先做向后兼容变更,避免代码回滚后旧版本无法启动。
排错按进程—容器—代理—外部依赖逐层走:进程没监听查 JDK/JAR/配置,容器访问不了查监听地址和端口映射,Nginx 502 查 upstream/防火墙/超时,应用 200 但数据错查数据库/迁移/缓存。镜像启动成功不代表 volume、权限、DNS 和 TLS 正确,必须用实际 curl 和日志验证。
练习是构建 blog:<sha>,运行两个版本并让 Nginx 切换 upstream;保存 docker inspect、health 响应、Nginx 配置测试和回滚命令。再故意改错端口、删除环境变量和让健康检查失败,记录 CI 或部署门在哪一步拒绝,而不是上线后才靠人工发现。
一次部署的输入是 commit SHA、Maven 依赖、JDK 21、配置键和数据库迁移,CI 输出测试报告、JAR、镜像 digest 和可发布状态;容器启动后输出监听端口、健康状态和日志;Nginx 输入域名/路径,输出转发响应。任何一环使用未锁定的 latest、未记录的环境值或手工复制,都削弱了回滚能力。
项目的 deploy 目录可以包含 Dockerfile、compose/服务单元、Nginx 配置、CI workflow、迁移说明和 runbook。runbook 写启动、查看日志、ss/docker inspect、健康检查、切流和回滚;密钥只写变量名。Git 提交拆成可解释变更,构建结果挂在 SHA 上,避免“部署服务器上的当前文件”成为唯一事实。
排错顺序要从窄到宽:Java/JAR 启动失败查类路径、JRE、配置和文件权限;容器内访问失败查监听 0.0.0.0、端口映射和卷;Nginx 502 查 upstream、超时、DNS 和防火墙;请求 200 但数据错查数据库迁移、缓存和环境 profile。CI 绿而部署坏,通常是流水线没有启动 smoke 或没有使用同一配置形状。
练习是构建带 SHA 的镜像并启动两个版本,使用 Nginx upstream 切换,保存 curl、health、日志和 inspect;故意让旧数据库迁移与新代码不兼容,设计一个向后兼容的两步迁移;再回滚代码但保留数据库,确认旧版本仍可启动。复盘写清每个命令的输入、退出码和恢复动作。
验证清单:从一个 commit SHA 开始,先运行 Java 21/Maven 测试并保存报告,再构建带 SHA 的 Docker image,记录 digest、JRE、配置键形状和端口;容器启动后用 curl 访问 health 与真实业务接口,检查日志和退出码。Nginx 配置测试通过后再转发,比较 upstream、超时、响应状态和 trace;故意改错端口、删环境变量、让 health 失败,确认 CI 或启动门在流量进入前拒绝。
回滚练习要包含数据库:发布 v2 前先做向后兼容迁移,运行 v1/v2 的读写 smoke,再切换 Nginx upstream;回滚代码但保留 schema,确认旧版本仍能启动并读到兼容数据。复盘时把 Git SHA、CI artifact、镜像 digest、容器日志、Nginx 配置、迁移版本和 curl 响应放在同一记录,不能只保存“部署成功”截图。latest、手工服务器文件和未记录环境变量都应列为不可复现风险。
练习复盘:把一次交付记录整理为 SHA、测试报告、JAR、镜像 digest、容器日志、health 响应、Nginx 配置测试和数据库迁移版本八项证据;任意一项缺失都标记为不可完整回滚。先以兼容迁移运行两个版本,再切 upstream,最后回滚代码并重复 curl/health。这样能区分应用进程、容器网络、Nginx 转发、外部依赖和 schema 的故障,不会把 CI 绿灯误当成线上可用。
进阶附录:多阶段构建与供应链
多阶段 Dockerfile 可以在构建阶段使用 Maven,在运行阶段只保留 JRE 和产物;依赖和镜像应扫描漏洞并固定 digest。CI 权限按最小范围设置,生产密钥不写日志。SBOM、签名和 provenance 是进一步的供应链证据,不应靠手工截图替代。
本课按「Git、Linux、Docker、Nginx 与持续集成的 Java 服务交付」的学习范围组织,正文与示例均为本站原创整理。