Files
story/docs/kubernetes-deployment.md
2026-08-29 21:56:51 +08:00

244 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kubernetes部署手册
## 1. 适用范围
本手册用于把数据采集终端—云端系统部署到预生产或生产Kubernetes集群。仓库提供Kustomize基线,不在集群内自建PostgreSQL、Redis、对象存储、OIDC、OpenTelemetry Collector或外部模型/语音服务;这些依赖应使用已备份、可监控且具备生产SLA的托管或平台服务。
清单只定义应用工作负载,不包含任何真实Secret。`deploy/kubernetes/base/configmap.yaml`中的`.example.invalid`、24位全零RTC App ID和`replace-with-*`是不可用占位值。生产服务的启动门禁会拒绝这些值,避免占位配置被误发布。
## 2. 目录与发布单元
| 目录 | 用途 | 是否可重复应用 |
|---|---|---|
| `deploy/kubernetes/base` | 四个应用Deployment、Service、Ingress、PDB、HPA和NetworkPolicy | 是 |
| `deploy/kubernetes/overlays/staging` | 预生产环境覆盖层,`APP_ENV=stage` | 是 |
| `deploy/kubernetes/overlays/production` | 生产环境覆盖层,`APP_ENV=prod` | 是 |
| `deploy/kubernetes/overlays/production-csi` | 生产环境覆盖层,并以Secrets Store CSI替代应用Kubernetes Secret卷 | 是 |
| `deploy/kubernetes/migration` | 数据库向前迁移一次性Job | 每次发布前执行并等待完成 |
| `deploy/kubernetes/migration-csi` | 从Secrets Store CSI文件读取数据库连接串的迁移Job | 每次发布前执行并等待完成 |
API、Callbacks和Worker共用一个后端镜像,通过容器命令分别启动`/app/api`、`/app/callbacks`和`/app/worker`。Web使用独立Nginx镜像。迁移镜像只包含固定版本的`migrate`程序和当前仓库`migrations`目录。
## 3. 集群前置条件
部署前应具备:
1. Kubernetes 1.27或更高版本,并启用支持`autoscaling/v2`的Metrics Server。
2. Nginx Ingress Controller;基线假定控制器命名空间为`ingress-nginx`。
3. 证书控制器或人工创建的TLS Secret `voice-platform-tls`。
4. 监控系统;基线假定Prometheus位于`monitoring`命名空间。
5. 支持pgvector的PostgreSQL、启用认证的Redis、TLS对象存储和HTTPS OIDC。
6. 火山RTC/豆包语音、火山方舟内容模型和1536维Embedding模型的真实账号与已开通资源。
7. 推荐使用External Secrets Operator或云KMS Secrets Store CSI Driver。若平台采用不同的Ingress或监控命名空间,必须同步修改NetworkPolicy。
生产数据库连接必须启用TLS,例如`sslmode=verify-full`;对象存储必须启用TLS;OIDC Issuer和JWKS必须为HTTPS。数据库迁移账号可与运行账号分离,但迁移Job使用的账号必须具有执行当前迁移所需的DDL权限。
## 4. 构建和推送镜像
为同一次发布使用不可变版本号或Git提交摘要,不要复用`latest`:
```powershell
$Version = "2026.08.23-<commit>"
$Registry = "registry.company.example/voice-platform"
docker build -f deploy/backend.Dockerfile -t "$Registry/backend:$Version" .
docker build -f deploy/web.Dockerfile -t "$Registry/web:$Version" .
docker build -f deploy/migrate.Dockerfile -t "$Registry/migrate:$Version" .
docker push "$Registry/backend:$Version"
docker push "$Registry/web:$Version"
docker push "$Registry/migrate:$Version"
```
在目标覆盖层的`kustomization.yaml`中把三个镜像名称和版本改成上述不可变地址。建议由流水线写入镜像摘要,并在发布记录中保存后端、Web和迁移镜像的对应关系。
## 5. 非敏感环境配置
不要直接修改`base`作为某个现场的长期配置。复制环境覆盖层或增加一个现场补丁,至少替换下列字段:
| 配置 | 说明 |
|---|---|
| `REDIS_ADDR` | Redis主机与端口,不含密码 |
| `AUTH_ISSUER_URL`、`AUTH_JWKS_URL` | HTTPS OIDC地址 |
| `AUTH_AUDIENCE` | Web/API共同使用的Audience |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP gRPC地址 |
| `EXTERNAL_PROBE_INTERVAL` | OIDC、方舟和RTC外部探针周期,默认30秒 |
| `RTC_APP_ID` | 真实24字符火山RTC App ID |
| `OBJECT_STORE_ENDPOINT` | 应用访问对象存储的TLS端点 |
| `OBJECT_STORE_PUBLIC_ENDPOINT` | 浏览器短期下载地址使用的公网TLS端点 |
| `OBJECT_STORE_EXPORT_BUCKET`、`OBJECT_STORE_REGION` | 私有导出桶和区域 |
| `ARK_CONTENT_MODEL` | 已开通的方舟内容模型Endpoint ID |
| `ARK_EMBEDDING_MODEL` | 已开通且输出1536维向量的Endpoint ID |
| `CONTENT_SAFETY_PROVIDER`、`CONTENT_SAFETY_ENDPOINT` | `managed_http`和正式HTTPS分类端点 |
| `CONTENT_SAFETY_POLICY_REF`、`CONTENT_SAFETY_TIMEOUT` | 已批准且不可变的策略版本和调用超时 |
| `WEB_OIDC_AUTHORITY`、`WEB_OIDC_CLIENT_ID` | 浏览器可见的OIDC公开配置 |
| Ingress `host`和TLS Secret | 正式域名与证书Secret |
`APP_ENV=prod`会启用严格生产门禁。不要通过改回`stage`绕过配置错误;应修正真实配置后再发布。
## 6. Secret清单
下列Secret必须在`voice-platform`命名空间预先存在。文件名就是Secret的key,Secret内容不得提交到Git或写入普通ConfigMap。
| Secret名称 | 必需key | 使用者 |
|---|---|---|
| `voice-platform-common-secrets` | `database-url`、`redis-password` | API、Callbacks、Worker、迁移Job |
| `voice-platform-api-secrets` | `device-credential-master-key`、`rtc-app-key`、`cursor-signing-secret`、`share-token-signing-secret` | API |
| `voice-platform-callbacks-secrets` | `provider-callback-secret`、`platform-health-callback-secret` | Callbacks |
| `voice-platform-worker-secrets` | `volc-access-key-id`、`volc-secret-access-key`、`voice-chat-template.json` | Worker |
| `voice-platform-model-storage-secrets` | `object-store-access-key`、`object-store-secret-key`、`ark-api-key`、`content-safety-api-key`、`memory-tool-signing-secret` | API、Worker;内容安全Key仅由Worker引用,记忆工具签名密钥由两者共享 |
| `voice-platform-device-secrets` | 每台设备一个key,例如`terminal-001` | API |
| `voice-platform-tls` | `tls.crt`、`tls.key` | Ingress |
游标签名密钥、分享签名密钥和通话内记忆工具签名密钥必须各不少于32字节且互不相同。供应商回调密钥不少于32字节。数据库连接串和Redis密码由应用通过`*_FILE`读取;应用错误日志不会输出Secret内容。
如启用可选的供应商原生记忆,把`PROVIDER_MEMORY_MODE`设为`managed_http`,在环境专属ConfigMap中配置正式HTTPS `PROVIDER_MEMORY_BASE_URL`,并通过仅挂载给API和Worker的Secret文件注入`PROVIDER_MEMORY_API_KEY_FILE`。默认`disabled`不会向供应商复制任何已确认记忆。通话内工具还需要在环境专属ConfigMap中设置供应商可访问的HTTPS `MEMORY_TOOL_BASE_URL`;不要填写集群内地址,除非供应商执行端确实运行在同一受控网络。
每台量产设备在数据库中的`credential_ref`应写为`device-secret://terminal-001`,对应`voice-platform-device-secrets`中的`terminal-001`文件。名称只允许安全的单层文件名。Kubernetes Secret或CSI卷原子更新后,API会在下一次设备验签时读取新值,无需重启。
`voice-chat-template.json`可从`deploy/volcengine/voice-chat-template.example.json`复制结构。生产文件必须使用`voice-chat-template-bundle-v1`并同时包含`s2s_pure`、`s2s_hybrid`和`cascade`三个模板;示例中的`TemplateBodyMustBeReplaced`表示整个`Config`对象都应按现场已开通的官方语音能力替换,不是一个可直接提交给供应商的字段。平台按通话冻结模式选中模板后覆盖AppId、RoomId、TaskId、设备用户、Agent用户和授权背景,模板中不要固定这些请求级字段。若现场模型或音色字段需要由已发布Agent组合动态提供,应在官方字段对应位置填写`{{MODEL_RESOURCE_REF}}`或`{{VOICE_RESOURCE_REF}}`;还可用`{{AGENT_BUNDLE_VERSION}}`、`{{PERSONA_VERSION}}`、`{{VOICE_VERSION}}`、`{{MODEL_VERSION}}`和`{{ROUTING_VERSION}}`写入供应商允许的追踪字段。未使用的占位符无需添加。
如使用火山临时凭据,可在Worker Secret增加`volc-session-token`,并通过现场补丁增加`VOLC_SESSION_TOKEN_FILE=/var/run/secrets/voice-platform/worker/volc-session-token`。如启用告警Webhook,同样应以现场补丁配置HTTPS `ALERT_WEBHOOK_URL`,并通过Secret文件注入`ALERT_WEBHOOK_SECRET_FILE`。
首次创建命名空间后再由密钥平台同步Secret:
```powershell
kubectl apply -f deploy/kubernetes/base/namespace.yaml
kubectl -n voice-platform get secret
```
不建议在命令行直接传入密钥明文。短期人工联调只能使用`kubectl create secret generic --from-file=...`从受控临时文件创建,并在完成后安全清理临时文件和终端历史。
### 6.1 Secrets Store CSI模式
生产环境推荐使用`deploy/kubernetes/overlays/production-csi`和`deploy/kubernetes/migration-csi`。这两个覆盖层保持原有容器内文件路径不变,只把卷来源从Kubernetes Secret替换为`secrets-store.csi.k8s.io`内联卷。
集群平台需先安装Secrets Store CSI Driver和对应云密钥提供器,并启用工作负载身份。`SecretProviderClass`必须创建在`voice-platform`命名空间;仓库提供[参数化示例](../deploy/kubernetes/examples/secret-provider-classes.example.yaml),但`provider`和`parameters`必须严格按现场云厂商或Vault提供器文档填写,不能直接应用示例占位值。
必须创建以下六个`SecretProviderClass`:
| SecretProviderClass | 必须挂载的文件名 |
|---|---|
| `voice-platform-common` | `database-url`、`redis-password` |
| `voice-platform-api` | `device-credential-master-key`、`rtc-app-key`、`cursor-signing-secret`、`share-token-signing-secret` |
| `voice-platform-callbacks` | `provider-callback-secret`、`platform-health-callback-secret` |
| `voice-platform-worker` | `volc-access-key-id`、`volc-secret-access-key`、`voice-chat-template.json` |
| `voice-platform-model-storage` | `object-store-access-key`、`object-store-secret-key`、`ark-api-key`、`content-safety-api-key`、`memory-tool-signing-secret` |
| `voice-platform-devices` | 每台设备一个文件,例如`terminal-001` |
提供器对象应通过`objectAlias`或等价字段映射为上述准确文件名。Deployment挂载整个目录,不使用`subPath`,以便CSI原子轮换能够更新容器看到的符号链接。ServiceAccount已按API、Callbacks、Worker和Web拆分;现场应只给前三个需要密钥的ServiceAccount附加相应工作负载身份,并按最小权限授予密钥读取能力。
自动轮换必须在CSI Driver和所选提供器均支持时启用。轮换后:
- `voice-platform-devices`中的逐设备密钥由API每次请求重新读取,原子更新后下一次验签即可使用新值;轮换协议仍应保留受控双密钥过渡或设备同步窗口。
- 数据库、Redis、RTC、供应商、对象存储、方舟和签名密钥在进程启动时读取。CSI可以更新文件,但不会主动重启Pod;这些密钥轮换后必须由部署流水线执行受控滚动重启,并验证新Pod就绪后再终止旧Pod。
- 不应依赖`secretObjects`同步为Kubernetes Secret。CSI迁移Job直接读取`voice-platform-common/database-url`文件,避免数据库连接串落入额外Secret副本。
检查CSI对象和已挂载版本:
```powershell
kubectl get secretproviderclass -n voice-platform
kubectl get secretproviderclasspodstatus -n voice-platform
kubectl describe pod -n voice-platform -l app.kubernetes.io/name=voice-platform-api
```
不要通过`kubectl exec ... cat`打印Secret内容。验收只检查文件存在、权限、版本编号、Pod就绪和实际认证结果。
目标环境完成动态读取和滚动重启两类轮换后,按[云KMS/HSM密钥轮换一致性证据](kms-hsm-rotation-evidence.md)导出不含密钥正文的版本、探针和审计引用,并使用`cmd/kms-rotation-conformance`生成候选报告。仅检查SecretProviderClass存在或本地文件更新不能满足`KMS_ROTATION`发布门禁。
## 7. 发布前静态检查
静态渲染不连接集群,适合本地和CI的轻量门禁:
```powershell
kubectl kustomize deploy/kubernetes/overlays/production > $null
kubectl kustomize deploy/kubernetes/migration > $null
kubectl kustomize deploy/kubernetes/overlays/production-csi > $null
kubectl kustomize deploy/kubernetes/migration-csi > $null
```
连接目标集群后执行服务端预检和差异检查:
```powershell
kubectl apply --server-side --dry-run=server -k deploy/kubernetes/overlays/production
kubectl diff -k deploy/kubernetes/overlays/production
```
重点确认镜像摘要、生产域名、OIDC、RTC App ID、方舟模型、对象存储、Secret齐备情况和NetworkPolicy命名空间。此处只做结构和部署配置检查,不替代真实供应商、真机和恢复演练。
七类生产基础设施完成现场核对后,按[生产基础设施一致性证据](production-infrastructure-evidence.md)保存脱敏探针、配置版本和审计引用,并使用`cmd/infrastructure-conformance`生成同候选报告。静态Kustomize渲染或本地Compose报告不能满足`PRODUCTION_INFRASTRUCTURE`发布门禁。
## 8. 数据库迁移
应用发布前先执行向前迁移,并等待Job成功:
```powershell
kubectl delete job voice-platform-migrate -n voice-platform --ignore-not-found
kubectl apply -k deploy/kubernetes/migration
kubectl wait --for=condition=complete job/voice-platform-migrate -n voice-platform --timeout=10m
kubectl logs job/voice-platform-migrate -n voice-platform
```
使用CSI模式时,把迁移命令中的目录替换为`deploy/kubernetes/migration-csi`。该Job直接挂载`voice-platform-common`,不会读取`voice-platform-common-secrets`。
迁移失败时停止应用发布,检查Job日志、数据库TLS、权限和当前schema版本。不要自动执行`down`;若迁移包含不兼容变更,应按发布方案先完成备份和向前修复脚本评审。
## 9. 应用发布
迁移成功后应用覆盖层:
```powershell
kubectl apply -k deploy/kubernetes/overlays/production
kubectl rollout status deployment/api -n voice-platform --timeout=10m
kubectl rollout status deployment/callbacks -n voice-platform --timeout=10m
kubectl rollout status deployment/worker -n voice-platform --timeout=10m
kubectl rollout status deployment/web -n voice-platform --timeout=10m
```
使用CSI模式时,应用目录替换为`deploy/kubernetes/overlays/production-csi`。如果Pod停留在`ContainerCreating`,优先检查CSI Driver、提供器DaemonSet、工作负载身份、`SecretProviderClassPodStatus`和对象别名,不要通过放宽应用权限绕过挂载错误。
Deployment使用`maxUnavailable=0`、`maxSurge=1`滚动更新;每类服务默认至少两个副本,并受PDB保护。HPA根据CPU在2—10个副本范围内扩缩,真实生产应在观察一段时间的CPU、内存、请求率、Outbox积压和供应商配额后调整阈值。
## 10. 发布后轻量验收
按照“功能可用优先、避免重复高强度测试”的原则,生产发布只执行下列关键检查:
1. 四个Deployment全部Available,Pod无重启循环。
2. API、Callbacks和Worker的`/health/ready`在集群内返回200。
3. Web的`/runtime-config.js`包含当前OIDC公开配置,登录和家庭导航可用。
4. 使用专用验收设备完成一次签名握手、配置读取、通话启动、入房确认、心跳和停止。
5. 确认火山任务进入ACTIVE后正常完成,最终字幕可见,内容候选生成并可人工确认。
6. 确认一条已确认记忆能在下一次通话中按授权召回。
7. 检查最近错误日志、Prometheus目标、Outbox open/dead数量和告警接收器。
8. 确认`oidc_jwks`、`ark_tls`、`volc_rtc_tls`和`content_safety_tls`外部依赖指标为1;该检查只证明网络、证书及JWKS结构,不替代真实账号业务调用或内容安全六类质量评测。
不在每次小版本发布后重复全量压测、全库恢复或完整浏览器矩阵;这些演练按重大变更、基础设施变更或既定周期执行。若上述关键链路任一失败,停止放量并回滚应用镜像。
## 11. 网络与观测说明
API、Callbacks和Worker Pod均带有`prometheus.io/scrape`、`path`和`port`发现注解。若集群Prometheus启用了基于Pod注解的服务发现,可直接抓取;使用Prometheus Operator时应创建等价的PodMonitor/ServiceMonitor,并保留相同指标路径和端口。
基线NetworkPolicy默认拒绝命名空间内所有Pod的入口流量,仅允许:
- `ingress-nginx`命名空间访问API、Callbacks和Web业务端口;
- `monitoring`命名空间访问API、Callbacks和Worker指标端口。
基线暂不限制出口,因为数据库、Redis、OIDC、对象存储、OTLP、方舟和火山RTC可能位于不同网络。正式环境应根据实际CNI、域名解析和固定网段逐步增加出口白名单。`/metrics`不通过Ingress暴露,只允许监控网络访问。
## 12. 回滚
优先回滚应用镜像,不回滚已经成功执行的数据库迁移:
```powershell
kubectl rollout undo deployment/api -n voice-platform
kubectl rollout undo deployment/callbacks -n voice-platform
kubectl rollout undo deployment/worker -n voice-platform
kubectl rollout undo deployment/web -n voice-platform
```
回滚后再次执行第10节的关键链路检查。若新迁移与旧应用不兼容,应使用事先评审的向前兼容修复版本,而不是在生产自动降级schema。
## 13. 尚需现场完成的事项
仓库清单完成的是可部署结构,不代表已经通过目标集群验收。上线前仍需业务方或平台方提供并验证:真实域名和证书、镜像仓库、托管数据库/Redis/对象存储、OIDC客户端、KMS/CSI工作负载身份、火山RTC/豆包语音、方舟模型、量产设备密钥、告警接收器,以及真机通话、外部故障、备份恢复和灰度回滚结果。