phase 5 part 4: java-spring awareness, k8s basics with validated manifests, run_examples supports single-file Java
This commit is contained in:
1 parent
075872e31e
commit
f695bbd7cc
18 files changed
+1192
-23
No files matched your search
@@ -0,0 +1,89 @@
|
||||
---
|
||||
id: devops.k8s.scenario.agent-rollout
|
||||
title: AI Agent 服务上 K8s:发布时流式回答不中断、慢启动、密钥管理和按负载扩容
|
||||
type: scenario
|
||||
domain: devops
|
||||
stack: [k8s, docker, nginx, cicd]
|
||||
tags: [滚动更新, 优雅退出, SSE, 探针, HPA, Secret, 回滚]
|
||||
level: intermediate
|
||||
prerequisites: [devops.k8s.core-objects, devops.docker.image-dockerfile]
|
||||
est_hours: 2.5
|
||||
related: [devops.k8s.interview, devops.cicd.release-rollback]
|
||||
week: [14]
|
||||
---
|
||||
|
||||
# AI Agent 服务上 K8s:发布时流式回答不中断、慢启动、密钥管理和按负载扩容
|
||||
|
||||
## 业务背景与问题
|
||||
|
||||
Agent API 从虚拟机迁移到公司的 K8s 集群后:
|
||||
|
||||
1. 每次发布,正在进行中的流式回答被切断,用户看到“网络错误”;
|
||||
2. 新版本启动需要预热(加载提示词模板、建立连接池),偶尔被重启循环;
|
||||
3. 有人把模型 API Key 直接写在 Deployment 的环境变量里提交到了仓库;
|
||||
4. 高峰期响应变慢,但 CPU 使用率并不高,HPA 没有扩容。
|
||||
|
||||
## 约束
|
||||
|
||||
- 集群由平台团队维护,应用团队只能修改自己的清单。
|
||||
- 回答可能持续 60 秒以上。
|
||||
|
||||
## 方案设计
|
||||
|
||||
1. **优雅退出**:
|
||||
- 应用收到 SIGTERM 后:就绪检查立即返回失败、不再接受新请求、等待进行中的流结束(最长 80 秒)后退出;
|
||||
- `preStop` 短暂等待,给 Ingress / Service 摘除端点的时间;
|
||||
- `terminationGracePeriodSeconds: 90` 大于最长回答时间;
|
||||
- 滚动更新 `maxUnavailable: 0`。
|
||||
2. **慢启动**:startupProbe 给足启动时间;就绪探针在预热完成后才返回成功。
|
||||
3. **密钥**:轮换已泄露的 Key;清单中只引用 Secret;Secret 由密钥管理系统同步(或平台提供的方案);CI 中加密钥扫描。
|
||||
4. **扩容指标**:AI 服务主要在等待模型返回,CPU 低但并发连接数高。改用“每个 Pod 的进行中请求数”作为自定义指标(需要平台支持指标适配器),或者先按经验提高副本下限。
|
||||
5. **发布与回滚**:镜像固定版本;CI 中先 `kubeconform` + 规范测试;发布后观察错误率和首 token 延迟,异常时 `kubectl rollout undo`。
|
||||
|
||||
## 关键代码
|
||||
|
||||
FastAPI 应用的优雅退出(示意):
|
||||
|
||||
```python
|
||||
import asyncio, signal
|
||||
|
||||
active_streams = 0
|
||||
shutting_down = False
|
||||
|
||||
@app.get("/readyz")
|
||||
async def readyz():
|
||||
if shutting_down:
|
||||
raise HTTPException(503) # 让 K8s 尽快摘除这个 Pod
|
||||
return {"ok": True}
|
||||
|
||||
async def drain(timeout: float = 80):
|
||||
global shutting_down
|
||||
shutting_down = True
|
||||
loop = asyncio.get_running_loop()
|
||||
deadline = loop.time() + timeout
|
||||
while active_streams > 0 and loop.time() < deadline:
|
||||
await asyncio.sleep(0.5) # 等待进行中的流式回答结束
|
||||
```
|
||||
|
||||
(Uvicorn 自身也会在收到 SIGTERM 后停止接收新连接并等待请求完成,具体参数如 `--timeout-graceful-shutdown` 以官方文档为准。)
|
||||
|
||||
完整清单:[examples/agent-api](../examples/agent-api/)。SSE 在 Nginx 层的配置见 [AI 网关场景](../../nginx/scenarios/ai-gateway-sse-canary.md)。
|
||||
|
||||
## 取舍
|
||||
|
||||
| 选择 | 优点 | 缺点 |
|
||||
| --- | --- | --- |
|
||||
| **优雅退出 + 长宽限期(本方案)** | 用户无感知 | 发布时间变长 |
|
||||
| 前端断线自动续传(从断点继续) | 对任何中断都有效 | 后端要保存生成进度,复杂 |
|
||||
| 发布时间放在低峰期 | 简单 | 治标不治本 |
|
||||
|
||||
## 易错点
|
||||
|
||||
- 宽限期比最长回答时间短,SIGKILL 依然切断。
|
||||
- 应用作为 shell 脚本的子进程启动,收不到 SIGTERM(使用 exec 形式的启动命令)。
|
||||
- 就绪探针在退出阶段仍然返回成功,新请求继续进来。
|
||||
- 以为 Secret 是加密的。
|
||||
|
||||
## 面试怎么讲
|
||||
|
||||
> “迁移到 K8s 后发布会切断流式回答。我做了优雅退出:收到 SIGTERM 后就绪探针立即失败,停止接新请求,等进行中的流结束;preStop 留出摘除端点的时间,宽限期设为 90 秒,大于最长回答;滚动更新不减少可用副本。慢启动用 startupProbe。泄露的 Key 立即轮换,清单只引用 Secret,CI 加密钥扫描。扩容方面,AI 服务 CPU 不高但并发高,改用进行中请求数作为指标。”
|
||||
Reference in new issue
Block a user