云原生与高效部署
本教程共 48 篇 · 第 45 篇 · 更新于 2026-08-13 · 约 5 分钟阅读
本节目标:掌握 Buildpacks 免 Dockerfile 构建镜像、优雅停机配置、容器内存优化,了解常见的生产部署策略。
云原生要解决什么
应用进了容器,只是第一步。生产环境还要回答一堆问题:
- 镜像怎么构建?谁来维护 Dockerfile?
- 发布时怎么不丢请求?
- 内存怎么配,容器不 OOM?
- 升级怎么平滑,故障怎么恢复?
这章把这些「最后一公里」问题逐个拆开。
Buildpacks:不用写 Dockerfile
上一节手写 Dockerfile 是通用方案。Spring Boot 还有官方推荐的捷径:Cloud Native Buildpacks。
Buildpacks 的思路:你不用描述「怎么构建」,只要告诉它「我要什么」。构建器(Builder)自动探测项目类型、选基础镜像、装运行时、配置启动命令,全程零 Dockerfile。
一条命令出镜像:
mvn spring-boot:build-image
执行时自动做几件事:
- 用 Paketo 构建器识别 Spring Boot 应用。
- 自动选 JDK 版本、配置内存参数。
- 生成带层缓存的镜像,直接进本地 Docker 守护进程。
- 镜像名默认
项目名:版本号,可用-Dspring-boot.build-image.imageName=registry/demo:1.0指定。
# 指定镜像名
mvn spring-boot:build-image -Dspring-boot.build-image.imageName=myregistry/demo:1.0
Buildpacks 的镜像开箱即用:非 root 用户、合理的内存设置、健康检查支持都配好了。不想维护 Dockerfile 的团队,这是最省事的路。
NoteBuildpacks 支持
layers.idx分层索引,上一节的分层优化在 Buildpacks 里同样生效。生成的镜像层和分层 Jar 的层一一对应,缓存复用效果一致。
优雅停机:不丢一个请求
发布时最怕什么?旧进程被直接杀死,正在处理的请求中断,用户看到 502。
优雅停机(Graceful Shutdown):收到停止信号后,先停止接收新请求,等正在处理的请求完成,再关进程。
Spring Boot 4.x 中,三个内嵌服务器(Tomcat、Jetty、Reactor Netty)默认启用优雅停机。收到 SIGTERM 信号后:
- 网络层停止接收新连接。
- 正在处理的请求继续执行。
- 全部结束后,关闭应用上下文。
超时时间可以配置:
spring:
lifecycle:
timeout-per-shutdown-phase: "20s"
20 秒内没处理完的请求会被强制结束。时间给太长,发布流程会拖;给太短,慢请求会被掐断,按业务情况调。
Note版本差异:Spring Boot 3.x 要显式配置
server.shutdown: graceful才启用。4.x 默认就是优雅停机,server.shutdown的默认值从immediate变成了graceful。看到旧教程教配置这个属性,是 3.x 的写法。
配合 Kubernetes 的流程:K8s 先发 SIGTERM,应用优雅收尾;terminationGracePeriodSeconds 给足超时时间,两边要匹配。
健康检查:K8s 的生命线
Kubernetes 靠探针判断应用死活:
- 就绪探针:应用能接流量了吗?不通过就摘流量。
- 存活探针:应用还活着吗?不通过就重启。
Spring Boot Actuator 的 /actuator/health 就是现成的探针端点。加上依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
K8s 里这样配:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
Spring Boot 4.x 提供了独立的 liveness 和 readiness 端点,语义和 K8s 探针对齐。就绪探针没通过时,流量不会进来,配合优雅停机,发布基本无损。
容器里的 JVM:内存怎么给
容器有内存上限,JVM 却默认按宿主机内存算。最经典的故障:容器限了 512M,JVM 堆按宿主机 32G 的 1/4 算,一压测就 OOM。
JVM 10+ 默认支持容器感知(UseContainerSupport),但堆大小仍按容器内存的一定比例算,具体比例不一定合适。生产建议显式控制:
java -XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -jar app.jar
-XX:MaxRAMPercentage=75:堆最大占容器内存的 75%。JVM 自身、线程栈、Metaspace 还需要空间,别给满。-XX:InitialRAMPercentage=50:启动时先分配一半,避免一次性占满。
两个参数配合 -Xmx 固定值选一个用。-Xmx512m 写死简单,但换容器规格就要改配置;百分比方案自动跟随容器内存,更云原生。
Warning只设
-Xmx不设-XX:MaxRAMPercentage也可以,但别忘了容器里还有 JVM 自身开销。堆给到 100% 内存,一样会 OOM。
高效部署:解压运行与 AOT 缓存
第 42 章讲过,胖 Jar 运行时从嵌套 Jar 加载类有少量开销。生产环境两种提速姿势:
解压运行(42 章已讲):
java -Djarmode=tools -jar demo.jar extract --destination application
java -jar application/demo.jar
AOT 缓存 / CDS(44 章已讲):训练一次生成缓存,启动时直接加载,启动时间能再砍一截。Java 25+ 用 AOT 缓存,旧版本用 CDS;21 LTS 可用,官方示例为 25。
两者叠加效果最好:解压形态 + AOT 缓存,是官方推荐的容器内 JVM 运行组合。第 43 章的 Dockerfile 里,RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar 就是在构建镜像时做训练。
部署策略:上线不背锅
镜像和配置都齐了,怎么发布?常见三种策略:
滚动更新(Rolling Update):逐个替换旧实例,新实例就绪后才继续替换下一个。K8s 默认行为,配好就绪探针就安全。发布期间新旧版本共存,注意数据库兼容:新代码要兼容旧数据结构。
蓝绿部署(Blue-Green):一套旧环境(蓝)、一套新环境(绿),全部部署完验证通过,一次性切流量。回滚就是切回蓝环境,秒级完成。成本是两套环境同时运行。
金丝雀发布(Canary):先放 5% 流量到新版本,观察指标正常再逐步放量。风险最小,但需要流量治理能力(Istio 这类服务网格常见)。
选型参考:小团队起步用滚动更新 + 就绪探针;对稳定性要求高、有预算跑双环境,用蓝绿;流量大、追求极致安全,上金丝雀。
生产检查清单
上线前对着过一遍:
- 镜像:分层构建、非 root 用户、内存百分比参数。
- 探针:
/actuator/health/readiness与/actuator/health/liveness配置到位。 - 停机:优雅停机默认开启,
timeout-per-shutdown-phase按业务调。 - 日志:输出到标准输出(stdout),容器平台统一收集。
- 配置:敏感信息走环境变量或密钥管理,不写进镜像。
小结
mvn spring-boot:build-image用 Buildpacks 免 Dockerfile 出镜像。- 4.x 默认优雅停机,
spring.lifecycle.timeout-per-shutdown-phase调超时。 - Actuator 的 liveness / readiness 端点对接 K8s 探针。
- JVM 内存用
-XX:MaxRAMPercentage跟随容器限制。 - 发布策略:滚动更新起步,蓝绿、金丝雀按需升级。