Files
easyai-ai-gateway/docs/operations/k3s-ha-runbook.md
T
wangbo d3b36cf63d fix(cluster): 保留深圳原有 UFW 防火墙
移除会与 UFW 冲突的 iptables-persistent 安装,改由 UFW 持久化 WireGuard、kubelet、Flannel 和 NodePort 规则。\n\n预检新增 UFW 已安装且处于 active 的硬门禁,避免包管理器切换防火墙后保留 DROP 默认策略并中断远程接入。\n\n已通过 bash -n、ShellCheck 和差异检查。
2026-08-01 09:51:03 +08:00

174 lines
8.2 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` 污点。
深圳扩展节点是纯 K3s agent,不加入 etcd 或 CloudNativePG。它使用节点名
`easyai-shenzhen`、WireGuard 地址 `10.77.0.4`,并归入逻辑 `hongkong` Worker 池。
`easyai.io/worker-only=true:NoSchedule` 污点确保除 Worker 外的业务 Pod 不会调度到该机;
香港池副本通过 hostname topology spread 分散到香港、深圳。接入使用:
```bash
./scripts/cluster/add-shenzhen-worker-node.sh all
```
香港—深圳公网直连实测无法满足生产链路门禁,因此两地 `10.77.0.2/10.77.0.4` 数据前缀
固定经宁波 WireGuard 中继;香港—深圳直接 peer 只保留加密心跳。巡检仍以两端实际数据路径
的 10 包丢包率低于 1%、平均 RTT 低于 80 ms 为硬门禁,禁止自动回退到劣化的公网直连。
本地 `.env.local` 只保存 `AI_GATEWAY_SHENZHEN_HOST` 和 SSH 接入密码,必须保持 `0600`
不得进入 Git。深圳已有宿主机服务时,接入脚本保留其公开端口,只封锁 K3s API、kubelet、
VXLAN 和除既有 `31058` 外的 NodePort 公网入口。深圳防火墙继续由原有 UFW 管理,接入脚本
禁止安装与 UFW 冲突的 `iptables-persistent`;UFW 未安装或未启用时必须在预检阶段停止。
生产上线前的双节点高媒体负载、Worker 强杀和 P24/P28/P32 容量搜索见
[生产同构验收模式与高媒体压力测试](production-acceptance.md)。
- 健康双库:同步复制,RPO=0。
- 单库降级:`dataDurability: preferred` 保持写入,`archive_timeout=5min`,灾难 RPO 目标不超过 5 分钟。生产 WAL 段为 16 MiB;禁止把该值降到 60 秒,否则仅空闲切段就要求跨地域链路持续承载至少 2.24 Mbit/s,会使同步副本在当前带宽下永久追不平。
- 数据库故障 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 -x -P . 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/harden-node-network.sh
./scripts/cluster/bootstrap-k3s.sh install
./scripts/cluster/verify-k3s-quorum.sh --execute
./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。
K3s containerd 同时配置 Docker Hub、GHCR 与 Quay 的镜像端点;已有集群需要滚动刷新时执行:
```bash
./scripts/cluster/configure-k3s-registries.sh
```
`verify-k3s-quorum.sh` 会依次停止并恢复洛杉矶、香港、宁波 K3s,确认每次剩余两个
server 都保持 etcd quorum。宁波停机可能令香港数据库副本晋升;首次切换前如需恢复宁波
初始主库,显式执行 `promote-cnpg-primary.sh`,故障恢复流程不得自动回切。
### 3. 平台和入口预置
```bash
./scripts/cluster/bootstrap-platform.sh prepare dist/releases/<SHA>.json
./scripts/cluster/install-cnpg-plugin.sh
./scripts/cluster/install-etcdutl.sh
./scripts/cluster/install-nginx-edges.sh prepare
./scripts/cluster/install-certificate-sync.sh
./scripts/cluster/install-legacy-cleaner.sh
```
`prepare` 将应用副本保持在 0,但启动 CNPG 双实例、WAL 归档和每日备份。OSS 的 Barman
链路使用 `s3.oss-cn-shanghai.aliyuncs.com`、virtual-hosted addressing 和 boto3
Signature V2`AWS_CONFIG_FILE` 通过 CNPG projected ConfigMap 同时挂载到主库、副本及恢复
集群。预检会强制产生并归档一个新 WAL,并要求 48 小时内至少存在一份完成的全量备份。
SecretStore
的 4 个值从旧 volume 直接导入 Kubernetes Secret,脚本只校验数量与总字节数,不打印值。
首次切换前如滚动安装令主库落在香港,可显式切回宁波。故障晋升后禁止自动执行该命令:
```bash
./scripts/cluster/promote-cnpg-primary.sh easyai-postgres-1
```
### 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。脚本等待切换后的 CNPG 全量备份状态变为 `completed` 后,才停止旧 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。恢复后不自动回切;确认连接和任务稳定后再择时切回。