Linux第18篇:Spring Boot 生产部署完全指南——从“能跑”到“敢拍胸脯” 系列导读这是《Java微服务运维实战》系列第10篇。在上一篇第9篇我们学习了网络诊断工具解决了“怎么连”的问题。本文将解决“怎么部署”的问题——当你的Spring Boot应用准备上线时你需要的绝不仅仅是java -jar。本文将带你走过从配置分离 → 零停部署 → 健康监控 → 应急排障的完整生命周期。一、一个让你后背发凉的部署事故深夜你信心满满地执行了部署脚本。5分钟后监控大屏开始报警接口响应时间从50ms飙升到3s。数据库连接池爆满。更诡异的是订单表里新生成的创建时间全部变成了UTC时间比实际时间晚了8小时。你慌忙回滚但为时已晚。事故复盘发现配置文件打进了JAR包导致生产数据库密码随包分发安全组紧急要求更换所有密码。服务器时区是UTC而代码里用了new Date()导致时间错乱。没有配置优雅停机回滚时的强制杀进程导致大量正在处理的请求报错产生了脏数据。这就是生产部署的残酷现实“能跑”和“能稳定运行”之间隔着一整个银河系。本文将为你建立一套生产级部署的“标准作业程序”确保你再也不会踩进这些坑里。二、部署形态抉择JAR包 vs WAR包在开始部署前我们得先选好“打包方式”。Spring Boot应用主要有两种形态特性JAR 包 (推荐)WAR 包容器内嵌 Tomcat/Netty依赖外部 Tomcat 容器部署方式java -jar app.jar放入webapps/目录启动 Tomcat适用场景微服务、云原生、容器化(Docker)传统单体、受制于运维规范的老项目优点自包含、依赖清晰、启动快、易于自动化可复用现有Tomcat配置、支持热部署不建议生产用缺点对底层容器配置的灵活性稍弱部署复杂、Tomcat版本与应用可能冲突结论对于新项目请毫不犹豫地选择JAR包。它是Spring Boot的“原生”形态与微服务和容器化Docker/K8s天生一对。三、外部化配置把“命根子”从JAR包里拎出来铁律永远不要将生产环境的配置文件尤其是包含密码的打包进JAR文件3.1 配置文件加载“军规”Spring Boot有一套严谨的配置加载优先级。理解它是解决“为什么配置没生效”的唯一途径。优先级从高到低命令行参数--server.port8081最高用于临时覆盖环境变量SPRING_DATASOURCE_URL...K8s/Docker中首选/opt/myapp/config/目录下的application-{profile}.yml生产环境推荐方式/opt/myapp/config/目录下的application.ymlJAR包内部的application-{profile}.ymlJAR包内部的application.yml最低仅用于默认值3.2 生产级目录与配置实践遵循第2篇的标准化目录规范推荐的生产目录结构如下/opt/myapp/ ├── current/ │ └── myapp.jar# 1. 只有代码无任何环境配置├── config/ │ ├── application-prod.yml# 2. 生产环境配置不含密码│ └── .env# 3. 敏感信息密钥、密码权限600└── scripts/ └── deploy.sh# 4. 部署脚本配置文件内容 (application-prod.yml):spring:datasource:url:jdbc:mysql://192.168.1.200:3306/myapp?useSSLtrueserverTimezoneAsia/Shanghaiusername:myapp_userpassword:${MYSQL_PASSWORD}# 关键从环境变量读取redis:host:192.168.1.201password:${REDIS_PASSWORD}# 关键从环境变量读取server:port:8080shutdown:graceful# 优雅停机必须开启敏感信息文件 (.env):# 此文件权限必须为 600MYSQL_PASSWORDyour_strong_passwordREDIS_PASSWORDyour_redis_password系统服务配置 (/etc/systemd/system/myapp.service):[Service] Usermyapp Groupmyapp EnvironmentFile/opt/myapp/config/.env # 加载敏感信息 ExecStart/usr/bin/java \ -Dspring.profiles.activeprod \ -Dspring.config.location/opt/myapp/config/application-prod.yml \ -jar /opt/myapp/current/myapp.jar 核心思想整个部署过程我们只变配置和脚本JAR包本身是“一次构建各处运行”的不可变产物。这正是持续交付的精髓。四、蓝绿部署实现“用户无感知”的零停机发布停机发布那是上个时代的事情了。对于微服务我们追求零停机。蓝绿部署是实现这一目标的经典且可靠的模式。4.1 蓝绿部署原理维护两套完全一样的环境蓝色当前生产环境绿色新版本环境。发布将新版本部署到绿色环境。验证在绿色环境上跑通所有冒烟测试。切换将负载均衡器如Nginx的流量一键切到绿色环境。回滚万一出问题只需将流量瞬间切回蓝色环境秒级恢复。发布后Nginx绿色环境 v1.1蓝色环境 v1.0 待命回滚发布前Nginx蓝色环境 v1.0绿色环境 闲置4.2 自动化蓝绿部署脚本下面是一个实战级的部署脚本它实现了无损切换。关键点在于健康检查切换前确保新应用已完全就绪。优雅停机切换后给旧应用足够时间处理完正在进行的请求。#!/bin/bash# 文件名: blue-green-deploy.shset-euopipefailAPP_NAMEmyappAPP_HOME/opt/${APP_NAME}NEW_JAR${1:?请指定新JAR包路径}HEALTH_CHECK_URLhttp://localhost:8080/actuator/health/readiness# 1. 确定当前活跃颜色CURRENT_COLOR$(cat${APP_HOME}/.active_color2/dev/null||echoblue)if[$CURRENT_COLORblue];thenNEW_COLORgreenNEW_PORT8081elseNEW_COLORblueNEW_PORT8080filog(){echo[$(date%Y-%m-%d %H:%M:%S)]$1;}log 开始蓝绿部署: 当前活跃 [$CURRENT_COLOR:$CURRENT_PORT] → 目标 [$NEW_COLOR:$NEW_PORT]# 2. 部署新版本cp${NEW_JAR}${APP_HOME}/${NEW_COLOR}/app.jarsudosystemctl start${APP_NAME}-${NEW_COLOR}# 3. 【关键】等待新版本就绪带超时重试log⏳ 等待新版本服务就绪...MAX_RETRY30foriin$(seq1$MAX_RETRY);doifcurl-sSf${HEALTH_CHECK_URL}|grep-qstatus:UP;thenlog✅ 新版本健康检查通过breakfiif[$i-eq$MAX_RETRY];thenlog❌ 新版本启动超时回滚操作...sudosystemctl stop${APP_NAME}-${NEW_COLOR}exit1fisleep5done# 4. 【关键】原子性地切换流量Nginx Upstreamlog 切换 Nginx 流量...sudosed-is/localhost:${CURRENT_PORT}/localhost:${NEW_PORT}/g/etc/nginx/conf.d/${APP_NAME}.confsudonginx-tsudosystemctl reload nginx# 5. 【关键】优雅停机旧版本给旧应用30秒处理完存量请求log 等待${CURRENT_COLOR}处理完存量请求...sleep30log 停止旧版本${CURRENT_COLOR}...sudosystemctl stop${APP_NAME}-${CURRENT_COLOR}# 6. 记录状态echo${NEW_COLOR}${APP_HOME}/.active_colorlog 部署成功当前活跃版本:${NEW_COLOR}五、监控与探针Spring Boot Actuator 的“三把斧”部署完只是开始如何让运维/监控系统知道你“活得好不好”全靠Spring Boot Actuator。5.1 核心端点配置management:endpoints:web:exposure:include:health,info,metrics,prometheus# 只暴露必要的端点base-path:/ops# 自定义路径增加安全性endpoint:health:show-details:when-authorized# 保护敏感健康信息probes:enabled:true# 开启 K8s 探针支持server:port:9090# 使用独立端口与业务隔离5.2 理解三个关键“探针”在云原生Kubernetes环境中这三个探针是应用的生命线探针端点作用失败后果存活探针 (Liveness)/actuator/health/liveness应用是否活着非僵死状态。重启Pod。应用死锁时用它抢救。就绪探针 (Readiness)/actuator/health/readiness应用是否准备好接收流量。从服务发现摘除。启动时或依赖服务如DB不可用时不接收流量。启动探针 (Startup)/actuator/health/startup应用是否已启动完成。保护应用在启动慢时不被存活探针误杀。 生产提示永远不要用/actuator/health的“大盘”状态来做决策。比如Redis挂了并不影响应用处理HTTP请求此时就不该将应用从Nginx摘除。正确做法是K8s的就绪探针只检查readiness端点而该端点只检查HTTP服务自身状态。六、生产“三连坑”时区、文件、编码这三个问题100%的生产环境都会遇到你提前掌握就能在别人抓狂时轻松解决。6.1 时区数据混乱的元凶事故现场数据库里存的是2026-06-30 10:00:00代码查出来变成2026-06-30 02:00:00。根因服务器默认时区是UTC协调世界时而你的业务在中国UTC8。解决方案必须双管齐下操作系统sudo timedatectl set-timezone Asia/ShanghaiJVM在启动参数中强制指定防止OS时区被其他程序篡改。-Duser.timezoneAsia/ShanghaiJDBC连接串确保数据库连接也一致。jdbc:mysql://host/db?serverTimezoneAsia/Shanghai6.2 文件路径Permission Denied 的元凶事故现场上传文件功能报错java.io.IOException: Permission denied。根因你的应用是以myapp用户运行的但上传目录/opt/myapp/uploads的所有者是root。解决方案# 创建目录并赋予正确的用户和权限sudomkdir-p/opt/myapp/uploadssudochown-Rmyapp:myapp /opt/myapp/uploadssudochmod750/opt/myapp/uploads# 属主有全部权限同组可读执行其他无权限# 验证权限是否生效sudo-umyapptouch/opt/myapp/uploads/test.txtechoOK||echoFAIL6.3 字符编码中文乱码的元凶事故现场日志、HTTP响应中出现????或乱码。根因系统LANG环境变量不是UTF-8或者JVM默认使用了系统字符集。解决方案# 1. 系统层面echoLANGen_US.UTF-8|sudotee-a/etc/environment# 2. JVM层面双重保险-Dfile.encodingUTF-8七、应急处理端口被占用的快速排查当你部署时看到Port 8080 was already in use别慌按下面三步走# 1. 【查】谁占用了我的端口ss-tlnp|grep8080# 输出示例LISTEN 0 128 *:8080 *:* users:((java,pid12345,fd120))# 2. 【看】这个进程是不是之前的残留ps-p12345-f# 3. 【杀】如果是残留优雅地干掉它sudosystemctl stop myapp# 优先用systemctl# 或kill-1512345# SIGTERM触发优雅停机# 最后才考虑kill-912345# 强制杀死不到万不得已不要用八、常见问题精选FAQ❓ Q1spring.profiles.active应该写在哪里永远不要写在JAR包内的application.yml里这会让你的JAR包与环境强绑定。正确做法是在启动命令systemd或Dockerfile中通过环境变量SPRING_PROFILES_ACTIVEprod或JVM参数-Dspring.profiles.activeprod传入。这样才能实现“一次构建到处部署”。❓ Q2Actuator的/health显示DOWN但应用看起来正常怎么回事/health大盘状态是**所有依赖组件状态逻辑与**的结果。如果Redis连不上大盘就是DOWN。这很可能是误报。解决办法检查/health的详细输出需开启show-details看是哪个组件挂了。如果是非关键依赖可以禁用它的健康检查management.health.redis.enabledfalse。在生产环境请使用独立的/actuator/health/readiness端点来做服务摘除决策。❓ Q3蓝绿部署时数据库变更怎么办这是蓝绿部署最棘手的点。总原则数据库变更必须向前兼容。可安全操作新增表、新增列带默认值、增加索引。危险操作需要分阶段进行重命名列、修改列类型、删除列。最佳实践采用**“扩展-收缩”模式**。先发布一个新版本它同时兼容新旧两种数据结构扩展。等所有实例都升级到新版本后再执行一个清理任务删除旧的数据结构收缩。九、核心心法与检查清单心法部署即“换路”把你的部署过程理解为**“将车流用户流量从一条老路旧版本平稳引导到一条新路新版本”**。所有的配置、脚本、监控都是服务于这个目标的。Spring Boot 生产部署检查清单 ✅检查项状态配置分离生产密码、密钥使用${ENV}并通过EnvironmentFile加载☐JAR包不可变构建过程只产生JAR包不修改其内容☐时区统一OS、JVM、JDBC连接串全链路时区一致 (Asia/Shanghai)☐文件权限上传目录和应用日志目录属主与运行用户一致☐字符编码-Dfile.encodingUTF-8已设置☐优雅停机server.shutdowngraceful和spring.lifecycle.timeout-per-shutdown-phase30s已配置☐健康探针management.endpoint.health.probes.enabledtrue已开启☐蓝绿部署部署脚本支持流量切换和秒级回滚☐端口隔离Actuator使用独立端口(9090)与业务端口(8080)分离☐如果本文帮你避开了一个部署深坑请点赞 收藏 ⭐让这份检查清单帮助更多人下一篇我们将深入Linux 防火墙iptables/firewalld与云安全组策略学习如何为你的应用穿上“铠甲”。敬请期待