Files

244 lines
18 KiB
Markdown
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
# 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 |
2026-08-29 21:56:51 +08:00
| `voice-platform-api-secrets` | `device-credential-master-key`、`rtc-app-key`、`cursor-signing-secret`、`share-token-signing-secret` | API |
2026-08-26 21:15:03 +08:00
| `voice-platform-callbacks-secrets` | `provider-callback-secret`、`platform-health-callback-secret` | Callbacks |
2026-08-26 06:41:07 +08:00
| `voice-platform-worker-secrets` | `volc-access-key-id`、`volc-secret-access-key`、`voice-chat-template.json` | Worker |
2026-08-29 21:56:51 +08:00
| `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引用,记忆工具签名密钥由两者共享 |
2026-08-26 06:41:07 +08:00
| `voice-platform-device-secrets` | 每台设备一个key,例如`terminal-001` | API |
| `voice-platform-tls` | `tls.crt`、`tls.key` | Ingress |
2026-08-29 21:56:51 +08:00
游标签名密钥、分享签名密钥和通话内记忆工具签名密钥必须各不少于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`;不要填写集群内地址,除非供应商执行端确实运行在同一受控网络。
2026-08-26 06:41:07 +08:00
每台量产设备在数据库中的`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` |
2026-08-29 21:56:51 +08:00
| `voice-platform-api` | `device-credential-master-key`、`rtc-app-key`、`cursor-signing-secret`、`share-token-signing-secret` |
2026-08-26 21:15:03 +08:00
| `voice-platform-callbacks` | `provider-callback-secret`、`platform-health-callback-secret` |
2026-08-26 06:41:07 +08:00
| `voice-platform-worker` | `volc-access-key-id`、`volc-secret-access-key`、`voice-chat-template.json` |
2026-08-29 21:56:51 +08:00
| `voice-platform-model-storage` | `object-store-access-key`、`object-store-secret-key`、`ark-api-key`、`content-safety-api-key`、`memory-tool-signing-secret` |
2026-08-26 06:41:07 +08:00
| `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/豆包语音、方舟模型、量产设备密钥、告警接收器,以及真机通话、外部故障、备份恢复和灰度回滚结果。