当前位置: 首页 > news >正文

Spring AI部署到Kubernetes后频繁重启和503?Liveness、Readiness、启动探针与优雅停机完整排查

文章摘要

Spring AI应用部署到Kubernetes后,经常出现一组看似矛盾的问题:Pod明明已经启动,Ingress却持续返回503;模型Provider短暂抖动后,Kubernetes开始反复重启应用;知识索引加载需要两分钟,Pod还没准备好就被Liveness杀死;滚动发布期间旧Pod收到SIGTERM,但仍有流式回答、长任务和Tool Calling正在执行,结果被强制中断。

根因通常不是Kubernetes不稳定,而是健康检查把“进程是否活着”“实例能否接收新流量”“依赖是否暂时可用”“启动是否完成”混成了一个/actuator/health。对于AI应用,模型Provider、向量库、Reranker和对象存储都可能短暂不可用,但这些外部依赖不应该轻易进入Liveness,否则一次第三方故障会触发所有Pod同时重启,形成重启风暴。

Spring Boot Actuator提供Liveness和Readiness状态,并能在启动和优雅停机阶段反映应用可用性;Kubernetes则分别使用Startup、Liveness和Readiness Probe控制启动保护、重启和流量摘除。本文给出AI应用的探针分层、依赖健康策略、冷启动、连接池预热、流式请求排空、长任务租约释放、preStopterminationGracePeriodSeconds和滚动发布参数的完整配置。

一、先看一个典型事故

Deployment配置:

livenessProbe:httpGet:path:/actuator/healthport:8080periodSeconds:10readinessProbe:httpGet:path:/actuator/healthport:8080periodSeconds:5

/actuator/health中包含:

  • PostgreSQL;
  • Redis;
  • VectorStore;
  • 模型Provider;
  • Reranker;
  • 对象存储。

某个模型Provider发生30秒抖动。

结果:

/actuator/health = DOWN ↓ Liveness失败 ↓ Kubernetes重启Pod ↓ 所有Pod同时重新建立连接 ↓ 冷启动和依赖压力升高 ↓ 更多探针失败 ↓ 服务持续503

真正应该发生的是:

Provider暂时不可用 → 路由到备用模型 或 → Readiness按策略拒绝部分新请求 或 → 返回可解释降级 而不是 → 重启整个JVM

二、四种状态必须分开

1. Process Alive

JVM和事件循环仍然工作,应用没有进入不可恢复死锁。

对应:

Liveness

2. Startup Completed

应用是否完成初始化:

  • 配置加载;
  • Bean创建;
  • 数据库迁移;
  • 必要模型Profile加载;
  • 本地词典或规则加载;
  • 连接池预热。

对应:

Startup Probe

3. Ready for New Traffic

实例是否可以接受新的业务请求。

对应:

Readiness

4. Business Dependency Capability

某个具体能力是否可用:

  • 主模型可用;
  • 备用模型可用;
  • 向量检索可用;
  • Tool Gateway可用;
  • 异步队列可用。

这不一定映射到Pod级探针,更适合能力矩阵、业务健康端点、指标和降级策略。

三、Liveness应该检查什么

Liveness的目标是回答:

重启这个进程 是否可能恢复?

适合进入Liveness的情况:

  • JVM不可响应;
  • 事件循环严重阻塞;
  • 核心线程死锁;
  • 应用内部状态不可恢复;
  • 必需本地资源损坏;
  • 自检确认必须重启。

不适合:

  • 模型Provider 5xx;
  • Redis短暂超时;
  • VectorStore延迟升高;
  • 数据库主从切换;
  • Reranker限流;
  • 外部Tool不可用。

外部依赖故障通常不会因为重启Pod而恢复。

四、Readiness应该检查什么

Readiness回答:

这个实例现在是否应该接收新流量?

适合考虑:

  • 应用是否完成启动;
  • 本实例是否正在优雅停机;
  • 核心配置是否加载;
  • 必需数据库是否可用;
  • 当前实例是否具备至少一个可用模型路由;
  • 是否达到严重过载阈值;
  • 是否正在执行不可兼容迁移。

但也不能把所有依赖直接加入Readiness。若所有实例同时Unready,Service将无法接收新连接,因此外部依赖是否进入Readiness,需要结合系统的降级能力判断。

五、AI应用的推荐健康分层

Liveness: JVM内部可恢复性 Readiness: 当前实例是否接受新的AI请求 Capability Health: chat/rag/tool/embedding等能力矩阵 Dependency Metrics: Provider、VectorStore、Redis、DB详细指标

能力状态:

publicrecordAiCapabilityStatus(booleanchatAvailable,booleanragAvailable,booleantoolAvailable,booleanembeddingAvailable,Set<String>availableModelProfiles,Set<String>degradedReasons){}

六、Spring Boot探针配置

management:endpoint:health:probes:enabled:trueshow-details:neverhealth:livenessstate:enabled:truereadinessstate:enabled:trueserver:shutdown:gracefulspring:lifecycle:timeout-per-shutdown-phase:45s

典型端点:

/actuator/health/liveness /actuator/health/readiness

管理端口可独立:

management:server:port:9090

但管理端口健康不代表业务端口一定可用。生产中应验证两者共享的关键线程池、网络栈和进程状态。

七、Startup Probe解决什么

AI应用冷启动可能很慢:

  • 大量Bean;
  • 数据源初始化;
  • 本地模型或Tokenizer加载;
  • Prompt模板编译;
  • 知识库元数据缓存;
  • 证书加载;
  • 远程配置;
  • 连接预热。

如果只设置Liveness:

应用尚未启动完成 → Liveness失败 → 重启 → 永远无法启动

Startup Probe在成功前保护应用,不执行普通Liveness判定。

startupProbe:httpGet:path:/actuator/health/livenessport:9090periodSeconds:5failureThreshold:36

最大启动窗口:

5秒 × 36 = 180秒

窗口应来自真实冷启动P99,而不是凭经验随意设置。

八、推荐Kubernetes探针

startupProbe:httpGet:path:/actuator/health/livenessport:9090periodSeconds:5timeoutSeconds:2failureThreshold:36livenessProbe:httpGet:path:/actuator/health/livenessport:9090periodSeconds:10timeoutSeconds:2failureThreshold:3readinessProbe:httpGet:path:/actuator/health/readinessport:9090periodSeconds:5timeoutSeconds:2failureThreshold:2successThreshold:1

不要把timeoutSeconds设得比健康检查内部调用的依赖超时还短,否则会制造随机失败。

九、不要在探针里调用真实模型

错误实现:

@ComponentpublicclassModelHealthIndicatorimplementsHealthIndicator{publicHealthhealth(){Stringanswer=chatClient.prompt().user("ping").call().content();returnanswer!=null?Health.up().build():Health.down().build();}}

问题:

  • 每几秒产生Token费用;
  • Provider限流时探针失败;
  • 模型延迟拖慢探针;
  • 所有Pod同时发请求;
  • 健康检查增加生产负载;
  • 真实Prompt可能进入日志。

更合理的方式:

  • 后台主动探测并缓存结果;
  • 检查路由器是否至少有一个可用Profile;
  • 探针只读本地聚合状态;
  • Provider探测使用短TTL和熔断;
  • 业务流量仍执行独立重试和Fallback。

十、自定义Readiness策略

@ComponentpublicclassAiReadinessPolicy{privatefinalProviderCapabilityRegistryproviderRegistry;privatefinalOverloadGuardoverloadGuard;privatefinalMigrationStatemigrationState;publicReadinessDecisionevaluate(){if(migrationState.blocksTraffic()){returnReadinessDecision.refuse("MIGRATION");}if(overloadGuard.isSeverelyOverloaded()){returnReadinessDecision.refuse("OVERLOAD");}if(!providerRegistry.hasAnyUsableChatProfile()){returnReadinessDecision.refuse("NO_CHAT_MODEL");}returnReadinessDecision.accept();}}

通过ApplicationAvailability发布状态:

@ComponentpublicclassAiReadinessPublisher{privatefinalApplicationEventPublisherpublisher;privatefinalAiReadinessPolicypolicy;@Scheduled(fixedDelay=5000)publicvoidrefresh(){ReadinessDecisiondecision=policy.evaluate();AvailabilityChangeEvent.publish(publisher,this,decision.accepting()?ReadinessState.ACCEPTING_TRAFFIC:ReadinessState.REFUSING_TRAFFIC);}}

十一、Readiness抖动治理

外部依赖可能一秒好、一秒坏。若每次都立即切换Readiness,Endpoint会频繁加入和移出Service。

publicfinalclassHysteresisHealthGate{privatefinalintfailureThreshold;privatefinalintsuccessThreshold;privateintconsecutiveFailures;privateintconsecutiveSuccesses;privatebooleanready=true;publicsynchronizedbooleanupdate(booleancurrentHealthy){if(currentHealthy){consecutiveSuccesses++;consecutiveFailures=0;if(!ready&&consecutiveSuccesses>=successThreshold){ready=true;}}else{consecutiveFailures++;consecutiveSuccesses=0;if(ready&&consecutiveFailures>=failureThreshold){ready=false;}}returnready;}}

还可以增加最短保持时间,避免秒级来回切换。

十二、数据库应该进入Readiness吗

强依赖数据库

如果每个请求都必须完成鉴权、读取租户配置、写审计和扣预算,数据库不可用时无法服务,可以进入Readiness。

可短时降级

如果只读缓存仍能回答部分FAQ,可以保持Ready但标记Capability降级。

不要把“依赖Down”机械等价为“整个Pod Unready”。

十三、模型Provider故障的正确策略

Primary失败 ↓ 路由Fallback ↓ Fallback也失败 ↓ 按任务类型: 返回降级 转异步 拒绝新请求

只有所有关键Profile不可用且没有可接受降级时,才考虑Readiness拒绝AI流量。

Provider故障更适合进入:

provider_availability provider_error_rate routing_fallback_rate

而不是Liveness。

十四、向量库故障怎么处理

对于RAG专用接口:

VectorStore不可用 → RAG Capability不可用

对于同时支持非RAG任务的应用:

Chat仍可用 → 整个Pod不必Unready

可以按路径拆分服务,也可以由API Gateway根据能力状态路由。

十五、滚动发布期间为什么会503

常见时序:

新Pod启动 ↓ Readiness过早变为Ready ↓ 流量进入 ↓ Prompt、连接池、索引元数据尚未预热 ↓ 超时与503

另一个方向:

旧Pod收到SIGTERM ↓ 仍保持Ready数秒 ↓ 继续收到新请求 ↓ 容器被终止 ↓ 流式回答中断

需要同时处理新Pod进入与旧Pod退出。

十六、预热与Readiness

启动完成不等于业务已预热。

可以预热:

  • 数据库连接;
  • Redis连接;
  • 模型路由配置;
  • Prompt模板;
  • Tokenizer;
  • 向量库Collection元数据;
  • HTTP连接池和DNS;
  • 必需证书。
@ComponentpublicclassAiWarmupRunnerimplementsApplicationRunner{privatefinalWarmupCoordinatorwarmupCoordinator;privatefinalApplicationEventPublisherpublisher;@Overridepublicvoidrun(ApplicationArgumentsargs){warmupCoordinator.warmup();AvailabilityChangeEvent.publish(publisher,this,ReadinessState.ACCEPTING_TRAFFIC);}}

预热不要调用高费用的真实生成,可使用Metadata接口、低成本连接测试和专用测试Profile。

十七、优雅停机的正确时序

Pod进入Terminating ↓ preStop执行 ↓ 应用切换Readiness=REFUSING_TRAFFIC ↓ Endpoint从Service摘除 ↓ 等待流量传播 ↓ SIGTERM ↓ Spring Boot Graceful Shutdown ↓ 停止接收新请求 ↓ 排空短请求与流式请求 ↓ 释放任务租约 ↓ 退出

Spring Boot在优雅停机阶段拒绝新流量,并允许在途请求在配置窗口内完成。

十八、preStop

lifecycle:preStop:exec:command:-/bin/sh--c->wget --post-data='' -qO- http://127.0.0.1:9090/internal/drain || true; sleep 10

/internal/drain应:

  • 只允许本机或管理网络;
  • 切换Readiness;
  • 禁止新长任务;
  • 通知Worker停止拉取;
  • 不暴露公网。

十九、Drain Manager

@ServicepublicclassDrainManager{privatefinalApplicationEventPublisherpublisher;privatefinalAtomicBooleandraining=newAtomicBoolean();publicvoidbegin(){if(!draining.compareAndSet(false,true)){return;}AvailabilityChangeEvent.publish(publisher,this,ReadinessState.REFUSING_TRAFFIC);}publicbooleanisDraining(){returndraining.get();}}

新任务入口先检查:

if(drainManager.isDraining()){thrownewServiceDrainingException();}

二十、异步长任务如何停机

AI任务中心不能依赖HTTP请求完成。Pod停机时:

  1. Worker停止领取新任务;
  2. 当前步骤尽量Checkpoint;
  3. 不可快速完成时停止续租;
  4. 让租约到期;
  5. 新Worker接管;
  6. 不要直接把任务标记FAILED。
@EventListenerpublicvoidonShutdown(ContextClosedEventevent){workerAdmission.stopAccepting();taskCheckpointCoordinator.flushInProgress();leaseManager.stopRenewingAfterCheckpoint();}

二十一、流式回答如何排空

SSE或Flux流可能持续几分钟。

spring:lifecycle:timeout-per-shutdown-phase:60s

Kubernetes:

terminationGracePeriodSeconds:75

Kubernetes窗口应大于应用优雅停机窗口,并预留preStop时间。

若允许无限长流,任何固定窗口都可能截断请求。产品应提供Response ID、断线重连、结果持久化或异步任务模式。

二十二、Tool Calling停机风险

工具可能正在:

  • 发邮件;
  • 提交订单;
  • 发权益;
  • 写ERP。

SIGTERM后不能盲目重试。工具执行记录必须包含:

  • toolCallId
  • 幂等键;
  • 状态;
  • 外部操作ID;
  • UNKNOWN状态。

新Pod恢复时先查询外部结果,不要直接重做。

二十三、Deployment滚动参数

strategy:type:RollingUpdaterollingUpdate:maxUnavailable:0maxSurge:1minReadySeconds:20progressDeadlineSeconds:600revisionHistoryLimit:10

含义:

  • maxUnavailable: 0:尽量不减少现有可用实例;
  • maxSurge: 1:额外启动一个新Pod;
  • minReadySeconds:新Pod持续Ready一段时间才视为可用;
  • progressDeadlineSeconds:发布长期无进展时标记失败;
  • revisionHistoryLimit:保留旧ReplicaSet用于回滚。

资源紧张时要确认集群能容纳Surge。

二十四、PodDisruptionBudget

apiVersion:policy/v1kind:PodDisruptionBudgetmetadata:name:spring-ai-apispec:minAvailable:2selector:matchLabels:app:spring-ai-api

PDB主要约束自愿中断,不替代副本数、探针和发布策略。

二十五、资源配置

AI编排应用常见瓶颈:

  • HTTP连接;
  • 内存中的Prompt;
  • 流式连接;
  • 线程池;
  • JSON序列化;
  • 本地Tokenizer;
  • Tool并发。
resources:requests:cpu:"500m"memory:"1Gi"limits:cpu:"2"memory:"2Gi"

CPU Limit过低时,冷启动和GC可能导致探针超时。探针失败应同时排查CPU throttling、GC pause、线程池饱和、连接池和DNS。

二十六、探针端点安全

Actuator不要全部公网暴露。

management:endpoints:web:exposure:include:-health-prometheus

详细健康信息可能泄露数据库、Provider、区域和内部版本。公网只返回最小状态,详细信息放内部监控。

二十七、监控指标

kube_pod_container_status_restarts_total kube_pod_status_ready spring_ai_readiness_transition_total{ reason } spring_ai_drain_in_progress spring_ai_inflight_requests spring_ai_streaming_requests spring_ai_worker_active_tasks spring_ai_task_lease_released_total spring_ai_provider_available{ profile } spring_ai_routing_fallback_total spring_ai_startup_duration_seconds spring_ai_graceful_shutdown_duration_seconds spring_ai_forced_termination_total

二十八、告警

Pod重启率突然上升 Readiness频繁抖动 新版本Ready后错误率升高 Terminating Pod仍有大量新请求 强制终止流式请求 租约过期任务上升 Provider故障导致全Pod重启

二十九、自动化测试

1. Provider故障不影响Liveness

Fake Provider返回503,断言:

liveness=UP

2. 无可用模型时Readiness拒绝

主备Profile全部不可用,断言Readiness拒绝流量。

3. Startup保护

Warmup未完成时不接收业务请求,但不会被Liveness重启。

4. Drain

调用Drain后:

  • Readiness拒绝;
  • 新长任务返回503或429;
  • 在途短请求继续;
  • Worker停止领取任务。

5. SIGTERM

发送SIGTERM,验证Checkpoint和租约状态。

6. Rolling Update

发布新镜像,确认:

  • 可用副本不低于目标;
  • 无请求丢失;
  • 旧Pod排空;
  • 新Pod满足minReadySeconds

三十、完整Deployment示例

apiVersion:apps/v1kind:Deploymentmetadata:name:spring-ai-apispec:replicas:3revisionHistoryLimit:10minReadySeconds:20progressDeadlineSeconds:600strategy:type:RollingUpdaterollingUpdate:maxUnavailable:0maxSurge:1selector:matchLabels:app:spring-ai-apitemplate:metadata:labels:app:spring-ai-apispec:terminationGracePeriodSeconds:75containers:-name:appimage:registry.example.com/spring-ai-api:release-v42ports:-name:httpcontainerPort:8080-name:managementcontainerPort:9090startupProbe:httpGet:path:/actuator/health/livenessport:managementperiodSeconds:5timeoutSeconds:2failureThreshold:36livenessProbe:httpGet:path:/actuator/health/livenessport:managementperiodSeconds:10timeoutSeconds:2failureThreshold:3readinessProbe:httpGet:path:/actuator/health/readinessport:managementperiodSeconds:5timeoutSeconds:2failureThreshold:2lifecycle:preStop:exec:command:-/bin/sh--c->wget --post-data='' -qO- http://127.0.0.1:9090/internal/drain || true; sleep 10resources:requests:cpu:"500m"memory:"1Gi"limits:cpu:"2"memory:"2Gi"

三十一、最终排查清单

□ Startup、Liveness和Readiness已分离 □ 外部模型故障不会直接触发Liveness失败 □ 探针不会调用真实付费模型 □ Readiness使用本地聚合能力状态 □ Readiness具有失败和恢复滞回 □ 冷启动P99用于计算Startup窗口 □ 新Pod完成必要预热后才Ready □ 管理端口与业务端口可用性关系已验证 □ server.shutdown=graceful □ preStop先切换Readiness并等待流量摘除 □ terminationGracePeriod大于preStop+停机窗口 □ Worker停机前Checkpoint并停止续租 □ 流式回答具备断线恢复策略 □ Tool副作用具备幂等和UNKNOWN状态 □ RollingUpdate设置合理的Surge和Unavailable □ minReadySeconds和progressDeadline已配置 □ 详细Actuator信息不对公网暴露 □ 已进行Provider故障、SIGTERM和滚动发布测试

总结

Spring AI应用在Kubernetes中频繁重启和503,根本原因通常不是“探针不够多”,而是健康语义设计错误。

正确分层是:

Startup 保护慢启动 Liveness 只判断进程是否需要重启 Readiness 决定是否接收新流量 Capability Health 表达模型、RAG和工具的细粒度能力

再配合预热、Readiness滞回、优雅停机、流量排空、任务Checkpoint和工具幂等,才能避免外部Provider故障触发重启风暴,也避免滚动发布中断流式回答和异步任务。

http://www.jsqmd.com/news/1351988/

相关文章:

  • 铜仁本地防水补漏哪家专业?屋顶、卫生间、外墙、地下室、阳台漏水师傅测评(2026年8月新) - 北京金修达天津维修部
  • 【工业仿真应用实战】第11篇:平板层流流动分析:两个平板之间的“绅士“流动
  • PCB布线实战指南:从信号完整性与电源完整性到DDR与射频设计
  • 冷链设备质量追溯怎么做:围绕序列号组织QMS与BI数据
  • ArkClaw:从信息过载到高效创作,AI如何重构内容生产流水线
  • 基于YOLO26铝片表面缺陷检测系统1:铝片表面缺陷检测数据集说明(含下载链接)
  • 飘窗内开窗全解析:从密封保温到安装验收的完整指南
  • 如何在5分钟内创建专业级EPUB电子书:EPubBuilder完全指南
  • 济宁网站建设那家好:揭秘专业团队背后的真相与选型指南
  • Windows CMD指令详解与实用技巧
  • ViGEmBus虚拟游戏手柄驱动:Windows游戏控制器兼容性终极解决方案
  • 重庆能源工业学校公办学校----------学费全免 - 学习招生
  • 从玄学调参到数据驱动:系统辨识与PID自动整定实战指南
  • 基于FM的MovieLens评分预测
  • 2320、51单片机火灾温度烟雾人体检测防火防盗报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+器件清单等)
  • AI CLI工具安全架构:指令拦截与沙盒机制深度解析
  • 阳东区大沟镇阳台下水道疏通避坑指南干货总结,教你选专业团队,高口碑更好值得推荐 - 同城资讯
  • Claude Code的LSP性能优化与Token消耗降低策略
  • Unity ARFoundation手势旋转3D模型:从原理到C#实现与优化
  • 三极管工作原理、选型与经典电路设计实战指南
  • 如何让外语游戏秒变中文:XUnity.AutoTranslator完整使用指南
  • 前缀和算法差分算法(4)——习题简述(1)
  • 3分钟快速上手:ncmdump终极指南 - 轻松解密网易云音乐NCM格式
  • 昆明武术学校家长择校攻略:招生条件及报名流程详细说明 - 圣龙武术朱老师
  • AI辅助CSS调试实战:用Gemini 3.5精准定位Flexbox、层叠上下文等五大难题
  • GitHub中文插件:3分钟告别英文界面的终极解决方案
  • 3种方法完美优化你的Windows任务栏视觉体验
  • 深入解析Cache地址映射:从直接映射到组相联,提升程序性能的关键
  • 重庆江津区江南职教中心2026年招生简章——实习期间就被企业直接留用的专业 - 学习招生
  • 技术架构解析:VideoDownloadHelper 浏览器视频下载方案实现