Java 基础体系 · 第 100/100 篇。示例统一以 Java 25 LTS 为语言和 JVM 基线;框架示例使用与其兼容的现代稳定版本。
Java 服务上 Kubernetes:资源、探针、JVM 容器感知和优雅关闭
Kubernetes 并不是“把 JAR 放进容器再启动”的进程管理器。它会根据资源声明决定 Pod 能否被调度,根据探针决定流量是否进入 Pod、是否重启容器,根据终止流程决定服务能否在发布或缩容时完成请求。与此同时,JVM 还要把容器的 CPU 和内存限制转换为堆大小、GC 线程数和编译线程数等运行参数。
因此,一个 Java 服务在 Kubernetes 上是否稳定,至少取决于四个相互影响的机制:
- Kubernetes 如何计算和执行 CPU、内存资源约束;
- 存活、就绪、启动探针如何影响流量和重启;
- JVM 如何感知 cgroup 容器边界;
- Pod 终止时,Kubernetes、容器进程和 Spring 应用如何协同完成关闭。
一、先建立运行模型:Pod、容器和 Java 进程分别负责什么
Pod 是 Kubernetes 调度和网络的基本单位。一个 Pod 可以包含一个或多个容器,但本文以“一个 Pod 中运行一个 Java 应用容器”为主。
容器是由容器运行时隔离出来的进程集合。它不是虚拟机,容器中的 Java 进程仍然共享宿主机内核,并通过 Linux cgroup 接收 CPU、内存等资源限制。
Java 进程是最终执行应用代码的进程。JVM 并不知道 Kubernetes 的 YAML;它只能通过操作系统提供的 cgroup 信息判断可用 CPU 和内存,并据此进行一部分自动调优。
一次请求的大致路径如下:
flowchart LR
C[客户端] --> S[Service]
S --> E[EndpointSlice]
E --> P[Ready Pod]
P --> K[Kubelet]
K --> J[Java 进程]
J --> A[Spring 应用]
A --> D[数据库/缓存/下游服务]
K -->|liveness 失败| R[重启容器]
K -->|readiness 失败| N[移出 Service 流量]
K -->|Pod 删除| T[终止流程]
T --> J
这里有三个容易混淆的事实:
- Service 通常只把流量转发到通过 readiness probe 的 Pod;
- liveness probe 失败通常会导致容器重启,但不会直接表示“暂时不要接流量”;
- Pod 被删除时,readiness 变化和应用收到终止信号之间存在分布式传播延迟,不能假设所有代理会在同一时刻停止发送请求。
二、资源:request 是调度约束,limit 是运行时上限
Kubernetes 容器资源声明主要包含:
requests.cpu:调度器为容器寻找节点时考虑的 CPU 预留量;requests.memory:调度器为容器寻找节点时考虑的内存预留量;limits.cpu:容器可使用的 CPU 上限;limits.memory:容器可使用的内存上限。
例如:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "768Mi"
其中:
1个 CPU 表示一个逻辑 CPU 的计算容量;500m表示0.5个 CPU;Mi是 字节;MB通常表示十进制兆字节,不能与Mi混用。
1. 调度如何使用 request
假设节点可分配资源为:
CPU:4
内存:8 GiB
节点上已有 Pod 的 CPU request 总和为 3.6,内存 request 总和为 6 GiB。此时:
剩余 CPU request = 4 - 3.6 = 0.4
剩余内存 request = 8 GiB - 6 GiB = 2 GiB
对于前面的新容器:
CPU request = 0.5
内存 request = 512 MiB
即使节点此刻实际空闲 2 个 CPU,只要调度器计算的 CPU request 剩余量只有 0.4,这个 Pod 也可能无法调度到该节点。调度阶段主要依据声明的 request,而不是瞬时 kubectl top 利用率。
因此,request 不是“应用平时应该使用多少”的纯统计字段,而是调度器用来做容量承诺的输入。
2. CPU limit 的执行方式
CPU 是可压缩资源。一个容器超过 CPU limit 时,通常不会被直接杀死,而是受到 cgroup CPU 配额限制,也就是 throttling,CPU 限流。
例如:
limits:
cpu: "1"
表示在一个调度周期内,容器平均最多获得约一个 CPU 的时间。具体调度行为由 Linux cgroup 和内核版本实现,但结果通常表现为:
- Java 线程 runnable,却得不到足够 CPU;
- GC、JIT 编译和业务线程互相争抢 CPU;
- 请求延迟升高;
- 探针因为超时而失败。
这解释了一个常见现象:CPU 使用率看起来没有“打满”,但延迟和探针超时已经恶化。容器可能正在被 cgroup 限流,而不是没有 CPU 压力。
cpu request 还会影响一些上层机制。例如 HPA 按 CPU 利用率扩缩容时,常见计算形式近似为:
同样的实际使用量,request 越小,利用率百分比越高。因此 request 不能随意设置得极小,否则会同时影响调度密度和扩缩容信号。
3. 内存 limit 的执行方式
内存是不可压缩资源。容器超过 memory limit 时,内核可能触发 cgroup OOM,杀死容器中的进程。Kubernetes 随后通常会记录类似:
Reason: OOMKilled
Exit Code: 137
137 常见于进程收到 SIGKILL,但它不是“Java 一定抛出了 OutOfMemoryError”的证明。必须区分:
- JVM 堆耗尽:通常可看到
java.lang.OutOfMemoryError: Java heap space; - Metaspace 耗尽:可能是
OutOfMemoryError: Metaspace; - 直接内存耗尽:可能是
Direct buffer memory; - cgroup 级别 OOM:进程可能来不及打印任何 Java 异常,直接被内核杀掉。
可使用以下命令确认:
kubectl describe pod <pod-name>
kubectl get pod <pod-name> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{" reason="}{.lastState.terminated.reason}{" exit="}{.lastState.terminated.exitCode}{"\n"}{end}'
kubectl logs <pod-name> --previous
kubectl describe pod 中的 Last State、Reason 和事件是判断重启原因的第一手信息;不能仅凭 Java 日志判断所有内存问题。
4. QoS 类别和取舍
Kubernetes 根据 request 和 limit 为 Pod 分配 QoS 类别:
- Guaranteed:每个容器的 CPU、内存 request 和 limit 都设置,且对应值相等;
- Burstable:至少设置了部分资源,但不满足 Guaranteed;
- BestEffort:没有设置资源 request 和 limit。
这不是性能等级,而是节点内存压力下的驱逐优先级等行为输入。生产 Java 服务通常至少需要内存 request;完全不声明资源会让调度和故障诊断失去基础。
三、JVM 容器感知:Java 看到的不是宿主机全部资源
JVM 的容器感知是指 JVM 使用容器的 cgroup CPU 和内存限制,而不是盲目使用宿主机资源。
现代 JDK,包括 Java 25,默认启用容器支持;相关行为可以通过:
java -XX:+PrintFlagsFinal -version | grep -E \
'UseContainerSupport|MaxRAMPercentage|InitialRAMPercentage|ActiveProcessorCount'
查看。UseContainerSupport 通常默认为启用,但生产环境仍应以实际 JDK 构建版本的输出为准。
1. JVM 内存不是只有 Java heap
容器内存上限 需要容纳整个 Java 进程,而不是只有 -Xmx 指定的堆:
其中:
- :Java heap,Java 堆;
- :Metaspace 和类元数据;
- :Code Cache,JIT 编译代码缓存;
- :线程栈;
- :Direct Buffer 等堆外缓冲区;
- :GC 数据结构;
- :JNI、动态库和 JVM 本身的 native 内存;
- :其他内存映射、运行库和应用 native 分配。
-Xmx 只约束 ,不约束整个左侧表达式。
假设:
limits:
memory: "512Mi"
如果 JVM 按 25% 的最大堆比例计算最大堆,则最大堆大约为:
512 MiB × 25% = 128 MiB
但这不代表进程只会占用 128 MiB,也不代表剩余的 384 MiB 一定足够。假设应用有:
Java heap 128 MiB
线程栈:80 个×1 MiB 80 MiB
Metaspace 100 MiB
Direct Buffer 64 MiB
JVM/GC/Code Cache 90 MiB
其他 70 MiB
--------------------------------
合计 532 MiB
即使堆没有溢出,整个 cgroup 仍可能超过 512Mi,最后被内核 OOMKill。
这里的 25% 只是某些服务器型 JVM 的默认人体工学参数示例,不应把它当成所有 JDK、所有机器和所有模式下的固定规范。应直接查看实际值:
java -XX:+PrintFlagsFinal -version 2>&1 \
| grep -E 'MaxHeapSize|MaxRAMPercentage|InitialRAMPercentage'
2. 显式设置堆上限
在资源稳定、应用内存模型已测量的服务中,可以显式设置:
env:
- name: JAVA_TOOL_OPTIONS
value: >-
-XX:MaxRAMPercentage=60
-XX:InitialRAMPercentage=20
-XX:MaxDirectMemorySize=64m
这种配置表达的是:
- 最大堆按容器可见内存的一定比例计算;
- 初始堆不要一开始就占满;
- 直接内存设置额外上限。
但 MaxDirectMemorySize 不能覆盖所有 native 内存,线程栈、Metaspace、JNI 分配仍然存在。比例也不是通用答案,必须结合线程数、框架、缓存、消息客户端和负载测试确定。
如果直接指定:
-Xms256m -Xmx256m
那么 heap 上限更明确,但应用仍然需要为 heap 之外的内存留下余量。对于 512Mi 的容器,-Xmx512m 几乎没有给 JVM 和 native 内存留下合理空间,是典型反例。
3. CPU 感知与并行度
JVM 会根据容器可见 CPU 估算:
- GC 并行线程;
- JIT 编译线程;
ForkJoinPool等默认并行度;- 部分 Java 库中的处理并行度。
当 Pod 设置:
limits:
cpu: "500m"
应用不应假设自己拥有宿主机的全部 CPU。容器感知可以避免 JVM 把宿主机几十个 CPU 当成自己的资源,但 CPU limit 很小且线程数量很多时,仍可能出现上下文切换和调度延迟。
-XX:ActiveProcessorCount=N 可以覆盖 JVM 使用的处理器数量估计:
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:ActiveProcessorCount=2"
它不会改变 cgroup 的实际 CPU 配额,只影响 JVM 和依赖库的并行度判断。因此,不能用它“伪造更多 CPU”;设置过大只会让线程竞争更加严重。
4. JVM 诊断工具也必须容器化地理解
建议在问题现场同时查看 Kubernetes 视角和 JVM 视角:
kubectl exec -it <pod-name> -- sh
# 查看 Java 进程及启动参数
ps -ef
jcmd 1 VM.command_line
jcmd 1 GC.heap_info
如果启动时加入:
-XX:NativeMemoryTracking=summary
可以进一步执行:
jcmd 1 VM.native_memory summary
Native Memory Tracking 会增加一定开销,通常只在需要定位 native 内存问题时开启,而不是无条件地作为性能配置。
四、探针:启动、存活、就绪解决的是三个不同问题
Kubernetes 常用三类探针:
- startup probe:应用是否完成启动;
- liveness probe:进程是否陷入无法自愈的状态;
- readiness probe:当前是否应该接收业务流量。
它们不是三个不同 URL 的机械复制,而是三个不同的状态判断。
1. startup probe 解决“启动慢”
如果配置了 startupProbe,在它成功以前,Kubernetes 不会执行 liveness 和 readiness 探针。设:
startupProbe:
periodSeconds: 5
failureThreshold: 60
则启动探针最多允许大约:
这里是按周期粗略估算;实际还会受首次执行时机、超时和 kubelet 调度影响。
没有 startup probe 时,下面这种配置可能杀死一个正常但启动较慢的 Java 应用:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
如果应用需要 30 秒启动,10 秒后连续三次失败,容器就可能在真正启动完成前被重启。
2. liveness 不应检查所有依赖
liveness 的问题是:“这个进程是否已经无法通过重启自愈?”
如果 liveness 同时检查数据库、Redis 和第三方支付服务,那么下游故障会导致所有 Pod 一起重启:
数据库故障
-> liveness 失败
-> 所有 Pod 重启
-> 连接池和线程重新建立
-> 数据库恢复压力更大
-> 形成重启风暴
因此,liveness 一般检查:
- HTTP 服务线程是否仍能处理请求;
- 应用内部是否进入明确的不可恢复状态;
- 必要的核心运行状态是否正常。
外部依赖暂时不可用,更适合影响 readiness,而不是 liveness。这样 Pod 会停止接收新流量,但不会通过重启放大故障。
3. readiness 是流量资格,不是进程健康的同义词
readiness 失败后,Service 通常会逐步停止把新请求发往该 Pod,但已经建立的连接、代理缓存和正在执行的请求不会瞬间消失。
readiness 可以因为以下原因失败:
- 应用刚启动,缓存尚未加载;
- 数据库连接池尚未准备好;
- 正在执行优雅关闭;
- 依赖服务不可用,当前 Pod 不应再接收新请求;
- 应用过载,需要暂时摘流。
Spring Boot 提供了 Kubernetes 常用的健康组端点:
/actuator/health/liveness
/actuator/health/readiness
使用前需要满足两个条件:
- 引入 Actuator;
- 该端点已暴露并受正确的安全策略保护。
一个常见配置如下:
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
具体默认行为可能随 Spring Boot 版本和 Kubernetes 环境检测而变化,生产配置应显式确认。Spring Boot 的 LivenessState 和 ReadinessState 由应用生命周期状态驱动,不能把它们当成数据库探测器的自动替代品。
如果 Actuator 端点需要认证,Kubernetes probe 也必须能完成认证;更常见的做法是只暴露健康组所需的最小端点,并通过网络策略、管理端口或应用安全配置限制访问范围。不能为了让探针通过而把全部管理端点公开到公网。
4. 一份可运行的探针配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
replicas: 2
selector:
matchLabels:
app: orders
template:
metadata:
labels:
app: orders
spec:
terminationGracePeriodSeconds: 40
containers:
- name: orders
image: example.com/orders:1.0.0
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "768Mi"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 60
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
其逻辑是:
- 最多允许约 5 分钟完成初始启动;
- 启动完成后,每 10 秒判断一次是否需要重启;
- 每 5 秒判断一次是否继续接收流量;
- readiness 连续两次失败后,通常会被摘出可用端点;
- liveness 连续三次失败后,kubelet 可能重启容器。
探针本身也会消耗 CPU 和连接资源。如果检查接口执行复杂 SQL、访问多个下游或等待锁,那么探针可能反过来放大故障。健康检查应尽量短、确定、低依赖。
五、优雅关闭:从删除 Pod 到 Java 线程结束
优雅关闭是指应用在收到终止信号后:
- 停止接收新的工作;
- 让已开始的请求、消息或任务完成,或在期限内取消;
- 关闭监听器、线程池、连接池等资源;
- 在超时前退出进程。
Kubernetes 的典型终止路径如下:
sequenceDiagram
participant C as 控制器
participant API as API Server
participant K as Kubelet
participant E as EndpointSlice/代理
participant J as Java 进程
participant S as Spring 生命周期
C->>API: 删除 Pod 或滚动更新
API-->>E: Pod 进入终止状态,逐步停止新流量
API-->>K: 发送 Pod 终止信息
K->>J: 执行 preStop(如配置)
K->>J: 发送 SIGTERM
J->>S: Spring 开始关闭
S->>J: 停止接收新请求,等待活动请求
S-->>J: 关闭完成
K->>J: 超过 grace period,发送 SIGKILL
这张图需要补充两个重要边界:
terminationGracePeriodSeconds的倒计时从终止流程开始,不是从SIGTERM到达后才开始;- EndpointSlice 更新、Service 代理和客户端连接池都有传播延迟,不能把“删除端点”和“应用收到 SIGTERM”理解为严格同步事件。
1. Java 进程必须正确接收 SIGTERM
容器镜像的启动命令应让 Java 成为可接收信号的主进程。Dockerfile 推荐:
FROM eclipse-temurin:25-jre
WORKDIR /app
COPY target/orders.jar /app/orders.jar
USER 10001
ENTRYPOINT ["java", "-jar", "/app/orders.jar"]
JSON 数组形式是 exec form,Java 进程通常直接成为容器主进程。应避免:
ENTRYPOINT ["sh", "-c", "java -jar /app/orders.jar"]
除非明确处理信号转发。否则 shell 可能成为 PID 1,SIGTERM 未必按预期传给 Java,导致应用没有机会执行关闭逻辑,最终被 SIGKILL 强制杀死。
2. Spring Boot 的关闭生命周期
Spring Boot 支持 graceful shutdown。常见配置:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
server.shutdown=graceful 的作用是让内嵌 Web 服务器进入关闭流程,停止接收新的请求,并等待活动请求结束。timeout-per-shutdown-phase 是 Spring 生命周期阶段等待的时间,不等于 Kubernetes 的全部终止预算。
对于 terminationGracePeriodSeconds: 40,可以粗略分配:
Kubernetes 终止总预算 40 秒
preStop 或等待流量摘除 5 秒
Spring Web 优雅关闭 30 秒
剩余缓冲 5 秒
这不是 Kubernetes 的强制内部划分,而是设计预算。实际必须满足:
如果 Spring 配置等待 60 秒,而 Pod grace period 只有 30 秒,Spring 的配置不会延长 Kubernetes 的生命周期;30 秒到期后,kubelet 仍可能发送 SIGKILL。
3. 长请求、异步任务和消息消费的边界
假设一个 HTTP 请求已经执行了 35 秒,Pod 的 grace period 只有 30 秒。即使 Spring 正确进入优雅关闭,该请求也不可能保证完成。优雅关闭不是无限等待,而是在有限期限内降低中断概率。
异步任务还要额外处理:
- 停止接收新任务;
- 等待线程池中的任务;
- 为任务设置最大执行时间;
- 对不可重试操作设计幂等性;
- 对消息消费者先停止拉取,再处理已取得的消息;
- 超时后让任务回到可重试或补偿路径。
如果只配置 HTTP 服务器 graceful shutdown,却没有关闭自建线程池、定时任务和消息消费者,应用仍可能在退出时丢失任务或拖过终止期限。
4. preStop 的使用风险
可以配置:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
这类延迟有时用于等待流量摘除,但它只是经验性的缓冲,不是对所有网络代理的严格保证。它还会消耗 termination grace period,并可能在镜像没有 /bin/sh 时直接失败。
更可靠的方式是让应用自身在收到 SIGTERM 后立即进入拒绝新流量状态,并依靠 readiness 反映这一状态;preStop 只在经过验证确实需要时使用,而不是用固定睡眠掩盖关闭时序问题。
六、把资源、探针和关闭参数放在同一个部署中
下面是一份简化但完整的示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: orders
template:
metadata:
labels:
app: orders
spec:
terminationGracePeriodSeconds: 40
containers:
- name: orders
image: example.com/orders:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: >-
-XX:MaxRAMPercentage=60
-XX:InitialRAMPercentage=20
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "768Mi"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 60
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
对应的 Spring 配置:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
部署和验证:
kubectl apply -f deployment.yaml
kubectl rollout status deployment/orders
kubectl get pods -l app=orders -w
kubectl describe pod -l app=orders
预期观察到:
- Pod 先处于未就绪状态;
- startup probe 成功后,readiness 才能决定是否接流量;
- 发布新版本时,旧 Pod 进入终止流程;
- 新 Pod 通过 readiness 后,滚动更新继续推进;
- 旧 Pod 如果在 grace period 内完成关闭,应正常退出,而不是出现
OOMKilled或强制终止。
如果发布卡住,应先区分是:
kubectl get pods -l app=orders
kubectl describe pod <pod-name>
kubectl logs <pod-name>
kubectl get events --sort-by=.lastTimestamp
CrashLoopBackOff:重点看进程退出、启动异常和 liveness;0/1 Ready:重点看 readiness、端口、Actuator 暴露和依赖初始化;Pending:重点看 request、节点容量、污点和亲和性;OOMKilled:重点看 cgroup 内存、堆外内存和 limit;- 终止时无关闭日志:重点看 PID 1、信号转发和 grace period。
七、常见错误及其因果关系
错误一:把 -Xmx 设置成 memory limit
limits:
memory: "1Gi"
env:
- name: JAVA_TOOL_OPTIONS
value: "-Xmx1g"
错误原因是把“堆上限”等同于“进程上限”。Metaspace、线程栈、Direct Buffer 和 GC 元数据会继续消耗 cgroup 内存,最终可能由内核直接杀进程。
错误二:readiness 和 liveness 使用同一个复杂检查
下游数据库短暂不可用时:
数据库不可用
-> liveness 失败
-> Pod 重启
-> 所有连接池重新连接
-> 下游恢复更慢
更合理的语义通常是:数据库不可用时 readiness 失败,Pod 暂停接收新流量;只有应用自身进入无法通过重启恢复的状态时,才让 liveness 失败。
错误三:用 initialDelaySeconds 代替 startup probe
固定延迟只能覆盖已知的启动时间。启动时间受类加载、JIT、容器 CPU、远程配置和缓存影响,可能在不同节点上变化。startup probe 能把“启动期”与“稳定运行期”分开,避免 liveness 在启动阶段误杀。
错误四:grace period 小于最大请求时间,却要求请求必然完成
如果最大业务请求耗时为 45 秒,而终止预算只有 30 秒,就不存在“所有请求都优雅完成”的配置解。必须缩短请求、增加预算、采用异步化,或设计可恢复的重试与幂等机制。
错误五:探针通过了,就认为服务可用
HTTP 端点返回 200 只能证明该次探针请求满足端点逻辑。它不代表:
- 所有业务接口都正常;
- 数据库拥有足够连接;
- 下游服务未超时;
- 应用没有被 CPU throttling;
- 当前 Pod 能承受新的流量。
探针应回答明确问题,监控、日志、业务指标和端到端检查负责回答其他问题。
八、生产验证应验证“失败路径”,而不只是启动成功
资源和生命周期配置不能只通过一次 kubectl apply 验证。至少应观察以下场景:
- 启动慢:人为增加初始化耗时,确认 startup probe 不会触发 liveness 重启;
- 下游不可用:确认 readiness 失败而不是所有 Pod 被 liveness 重启;
- 内存接近上限:观察
kubectl top pod、JVM heap、native memory 和 OOM 事件; - CPU 受限:检查延迟、GC 时间和 cgroup throttling 指标;
- 滚动更新:发送持续请求,确认旧 Pod 不再接收新流量,并让已有请求完成;
- 强制超时:让一个任务超过 grace period,确认它能被安全取消、重试或补偿。
资源配置的核心不是“给一个看起来合理的数字”,而是让以下关系在真实负载下成立:
当这三个条件都被测量和验证时,Kubernetes 的资源管理、探针决策、JVM 容器感知和 Spring 优雅关闭才形成一个完整的运行闭环,而不是四组彼此独立的 YAML 参数。
系列导航与关联阅读
官方资料
本文依据 Java、Spring 与相关项目官方文档重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论