Files
easyai-ai-gateway/docs/operations/k3s-ha-runbook.md
T
wangbo 971540a2a4 feat(deploy): 增加三节点 K3s 高可用迁移能力
新增 WireGuard 全互联、三 server embedded-etcd K3s、CloudNativePG 双实例、Barman OSS 备份、双 NGINX、Kubernetes Secret/RBAC 与本地旧文件按严格 24 小时清理。

新增维护窗口数据迁移、digest 固定滚动发布、应用回滚、跨节点文件 E2E、节点和数据库故障演练、CNPG 恢复与 etcd 快照验收脚本;洛杉矶仅作为带 NoSchedule 污点的仲裁节点。

所有生产 Secret 只在执行时从 0600 本地环境和旧生产容器导入,仓库不保存凭据;公网入口保持人工 DNS 故障切换边界。

验证:bash -n、ShellCheck、kubectl kustomize、Node 语法检查、Secret 扫描、OSS put/head/delete 实测。
2026-07-28 04:37:31 +08:00

130 lines
5.4 KiB
Markdown
Raw 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.
# EasyAI Gateway 三节点 K3s 高可用运行手册
## 范围与目标
本手册覆盖宁波、香港、洛杉矶三节点迁移。宁波和香港承载 API、Web、River Worker 与
CloudNativePG;洛杉矶只作为 K3s server/etcd 仲裁节点,并带
`easyai.io/witness=true:NoSchedule` 污点。
- 健康双库:同步复制,RPO=0。
- 单库降级:`dataDurability: preferred` 保持写入,`archive_timeout=60s`,灾难 RPO 目标不超过 5 分钟。
- 数据库故障 RTO:不超过 5 分钟。
- 应用发布:香港验收后再滚动宁波,计划内零停机。
- 公网入口:双 NGINX 加人工 AliDNS 切换。接入带健康检查的 GTM 前,不宣称公网入口自动高可用。
所有命令从仓库根目录执行。生产凭据仅从 `0600``.env.local`
`.local-secrets/cluster/` 读取,不进入 Git、发布 manifest 或验收输出。
## 实施顺序
### 1. 源码与镜像门禁
```bash
cd apps/api && env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1
cd apps/api && go vet ./...
pnpm install --frozen-lockfile
pnpm lint
pnpm test
pnpm build
shellcheck scripts/cluster/*.sh deploy/kubernetes/easyai-ai-gateway-cluster-release
kubectl kustomize deploy/kubernetes/production >/dev/null
./tests/ci/migrations-test.sh
node scripts/ci-validate-migrations.mjs <当前线上完整GitSHA>
```
代码提交并推送到 `origin/main` 后,发布 digest 固定镜像:
```bash
./scripts/publish-release-images.sh --components auto
```
### 2. WireGuard 与 K3s
```bash
./scripts/cluster/bootstrap-wireguard.sh
./scripts/cluster/bootstrap-k3s.sh backup
./scripts/cluster/bootstrap-k3s.sh install
./scripts/cluster/harden-node-network.sh
./scripts/cluster/verify-cluster.sh precutover
```
`backup` 会在卸载旧 K3s 前,将 SQLite、manifests 和 staging 资源归档并校验后上传
OSS `easyai-ai-gateway/production/pre-ha/``install` 固定
`v1.36.2+k3s1`,使用 embedded etcd、静态 Secret 加密、500ms heartbeat 和 5s election timeout。
### 3. 平台和入口预置
```bash
./scripts/cluster/bootstrap-platform.sh prepare dist/releases/<SHA>.json
./scripts/cluster/install-nginx-edges.sh prepare
./scripts/cluster/install-certificate-sync.sh
./scripts/cluster/install-legacy-cleaner.sh
```
`prepare` 将应用副本保持在 0,但启动 CNPG 双实例、WAL 归档和每日备份。SecretStore
的 4 个值从旧 volume 直接导入 Kubernetes Secret,脚本只校验数量与总字节数,不打印值。
### 4. 02:0004:00 切换
先执行只读预检:
```bash
./scripts/cluster/migrate-production.sh preflight dist/releases/<SHA>.json
```
进入北京时间窗口后执行:
```bash
./scripts/cluster/migrate-production.sh cutover dist/releases/<SHA>.json
```
脚本依次启用维护页、停止旧 API、完整备份旧 PostgreSQL、上传 OSS、恢复 CNPG、比对数据库
版本/扩展/catalog/表数/序列/关键行数/抽样 ID、运行 digest 固定 migrator、冻结 Worker 启动
两地应用、做 NodePort 验收,再切换双 NGINX。公开流量提交后,先启用香港 Worker,再启用宁波
Worker。旧 Docker PostgreSQL 和数据卷停止但保留,不自动删除。
在公共流量提交前,任一步失败或耗时超过 10 分钟都会恢复旧 Docker 与旧 NGINX。公共流量
提交后禁止自动切回旧数据库,避免双写分叉。
## 上线验收
```bash
./scripts/cluster/verify-cluster.sh postcutover
./scripts/cluster/run-cross-node-file-e2e.sh
./scripts/cluster/failure-drill.sh api --execute
./scripts/cluster/failure-drill.sh database --execute
./scripts/cluster/failure-drill.sh witness --execute
./scripts/cluster/restore-backup-drill.sh --execute
./scripts/cluster/verify-etcd-backup.sh
```
跨节点 E2E 通过宁波私有 NGINX 固定进入宁波 API,关闭宁波 River Worker,由香港 Worker
执行真实 multipart 图片编辑任务;校验输入和输出 URL 在两节点下载的长度与 SHA-256
一致,并检查 `river_job.attempted_by`、唯一成功 attempt、唯一结算/扣费及无重复回调。
数据库故障演练会强制删除当前主 Pod,确认 5 分钟内晋升;在副本恢复前写入一条专用
`system_settings` 探针,恢复同步后删除。备份恢复演练会从 OSS 创建一个独立 CNPG Cluster
验证数据库登录、schema、行数和抽样值后删除临时 Cluster 与 PVC。
## 发布与回滚
切换成功后,本地环境自动设置 `AI_GATEWAY_DEPLOY_MODE=kubernetes`。日常发布仍使用:
```bash
./scripts/deploy-production-release.sh dist/releases/<SHA>.json
./scripts/deploy-production-release.sh --status
./scripts/deploy-production-release.sh --rollback <历史完整SHA>
```
集群 helper 从 `easyai-gateway-release` ConfigMap 读取当前 release,数据库迁移前先等待一次
CNPG 备份,然后香港滚动、验收,再滚动宁波。`--rollback` 只恢复应用 digest,绝不逆向执行迁移。
## 旧文件与公网故障
宁波每小时执行一次清理,以当前时间减文件 `mtime` 严格大于 86,400 秒为删除条件。未过期
文件由宁波只读 NGINX 提供,香港通过 WireGuard 回源。清理结果持续为 0 个存量文件后,删除
两个 legacy static include 和宁波 18080 origin;历史过期 URL 返回 410。
公网入口故障时,先使用 `curl --resolve` 确认香港入口健康,再人工将 `ai.51easyai.com`
AliDNS A 记录切到香港 IP。恢复后不自动回切;确认连接和任务稳定后再择时切回。