diff --git a/docs/plans/2026-09-24-正式环境双地域部署.md b/docs/plans/2026-09-24-正式环境双地域部署.md new file mode 100644 index 0000000..7ae2441 --- /dev/null +++ b/docs/plans/2026-09-24-正式环境双地域部署.md @@ -0,0 +1,173 @@ +# 2026-09-24 正式环境双地域部署(P5 生产上线) + +> 状态:已执行完毕(2026-09-24,验收记录见 progress/正式环境双地域部署-验收记录.md)。 +> 输入:用户 2026-09-24 指令;记忆双地域方案(跨境 HMAC 同步 + 安全组互白名单 + +> 全新空库 + 域名证书);G4 验收记录 §4.6/§4.8 全部坑位经验。 +> 用户已拍板(2026-09-24):**hnxinyouhe.com 已备案**(cn 直接域名+HTTPS); +> **不接 CDN**(A25「图片经全球 CDN」暂不满足,如实标注);**OSS 复用现有桶** +> (G1 凭证,从测试服务器 env 取)。 + +## 0. 现状侦察结论(2026-09-24) + +### SSH 通道 + +| 机 | IP | 通道 | 状态 | +| --- | --- | --- | --- | +| ov 境外 | 47.254.176.10 | 本机 → ProxyJump `root@8.152.213.143`(跳板在 ov 安全组白名单)→ root,钥匙 `~/.ssh/id_ed25519`(即 cms-cj-deploy,ov 机已装过) | ✅ 已通 | +| cn 国内 | 101.37.116.184 | 本机直连 root + 同一把钥匙 | ⏳ 等公钥装入 authorized_keys(用户进行中) | +| 跳板(老国内机) | 8.152.213.143 | 本机直连 root + 同钥匙 | ✅ 已通 | + +写入 `~/.ssh/config`:`photo-ov`(带 ProxyJump)/ `photo-cn` 两个 Host 条目。 + +### ov 机探测(已完成) + +- Debian 13 / 2C / 7.4G / 40G 盘 **83% 用**(剩 6.5G + `docker builder prune` 可回收 ~2G → 部署前清一次,够用) +- Docker 29.4 + Compose v5.1.3;**Docker Hub 直连正常**(registry-1.docker.io 401=通) +- **NTP 已同步**(阿里云内部 NTP,Stratum 3)——HMAC ±300s 防重放硬前提 ✓ +- 出网:aliyun intl / docker hub / **gitea:25311(0.58s)** / oss-hangzhou 全通 +- **跨境:ov→cn 22/443 TCP 可达** ✓ +- DNS:inside→cn、outside→ov 解析正确 ✓ +- 机上已有:1Panel 全家桶(OpenResty 占 **80/443**、MySQL **5.7.44**@127.0.0.1:3306、Redis 8.6.2、**phpMyAdmin 占 8089**、php-fpm)+ tracker / cc-ai-kefu / cms-cj 等项目 +- 本机命令样例:`ssh photo-ov '…'` / `scp … photo-ov:/opt/…` + +### 坑位(G4 §4.6/§4.8 经验,全部纳入流程) + +1. **web 容器随 api 容器重建后必须一并 restart**(nginx 缓存旧上游容器 IP → 502)。 +2. **`nginx:alpine` 最新 digest 的 10-listen-on-ipv6 entrypoint 卡死**——本次 T0 把补丁行正式进仓库 Dockerfile。 +3. 本机 Docker Hub 抽风预案:**构建挪服务器**(ov 直连 Hub;cn 若不通走 1Panel 加速器,探测时确认 `/etc/docker/daemon.json`)。 +4. MySQL 8.4 `trigger`/`cursor` 为保留字,手工 SQL 全部反引号。 +5. `.env.production-*` chmod 600、不入库不入镜像。 + +## 1. 目标形态 + +``` +cn 101.37.116.184 (live-album.inside.hnxinyouhe.com, 已备案) + └ photo-live-cn 栈:api-cn / worker / sync / web + 自管 mysql:8.4 + redis:7 +ov 47.254.176.10 (live-album.outside.hnxinyouhe.com) + └ photo-live-ov 栈:api-ov / web-ov + 自管 mysql:8.4 + redis:7 +跨境链路: + sync 推送/对账:cn sync → https://live-album.outside.hnxinyouhe.com(HMAC) + 统计回传读库:cn sync → 47.254.176.10:13306(ov mysql,安全组仅放行 cn IP) +入口:两机各自 1Panel OpenResty 建站点 → 反代 127.0.0.1 回环端口 → Let's Encrypt 证书 +``` + +## 2. 任务序 + +### T0 仓库准备(本机,先行) + +- `docker/web.Dockerfile`:`FROM nginx:alpine` 后加 + `RUN rm -f /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh`(§4.8 事件 B 补丁正式化,钉版本列为后续待办)。 +- 服务器 compose 适配(两台各自 `/opt/photo-live/deploy/`,不进仓库):基于 + `infra/deploy/docker-compose-ov.yml` / `-cn.yml` 改三点—— + ① web 主机端口:ov `127.0.0.1:18089`(8089 被 phpMyAdmin 占),cn `127.0.0.1:18088`(避冲突统一 18xxx); + ② 增自管服务 `mysql`(mysql:8.4 + utf8mb4_0900_bin + named volume)与 `redis`(redis:7-alpine); + ③ 网络改独立桥 `photo-net`(不挂 1panel-network——不复用 1Panel 的 MySQL5.7/Redis)。 + ov mysql 额外映射 `0.0.0.0:13306:3306`(跨境 sync 读库,安全组仅放行 cn IP)。 +- commit + push gitea(两台服务器 `git pull` 用)。 +- 验收:flutter/后端零改动,`docker build` 本机两镜像可构建(顺带验证 Hub 状态)。 + +### T1 cn 环境探测(公钥装入后立即) + +- 跑与 ov 同一份探测脚本(.tmp-probe.sh):规格/系统/Docker/Compose/加速器/MySQL/NTP/时区/端口占用/出网(Hub、gitea、uniservice、oss)/跨境 cn→ov TCP。 +- 决定 cn 构建路径(本机构建传输 vs 服务器构建)与磁盘是否需清理。 +- 验收:探测记录回填本文档 §0;cn 18088/13306 无端口冲突或改用其它高位端口。 + +### T2 ov 栈部署(先 ov——cn 的 sync 依赖 ov API 可达) + +1. `mkdir -p /opt/photo-live/{src,deploy}`;clone gitea(凭证用令牌,clone 后 remote 去掉令牌或用 deploy 方式)。 +2. `docker builder prune -f`(回收 ~2G)。 +3. 写 `.env.production-ov`(600,全新值): + - `DATABASE_URL_OV=mysql://photo_ov:<新强密码>@mysql:3306/photo_ov?connection_limit=10` + - `REDIS_URL_OV=redis://redis:6379` + - S3 四元组:从测试服务器 `/opt/photo-live/deploy/.env.production-cn` 抄 G1 真实值(同桶同凭证,ov 只读现签) + - `CORS_ORIGINS=https://live-album.outside.hnxinyouhe.com` + - `SYNC_INTERNAL_KEY=` + - 互动/限速参数按模板缺省 +4. mysql 容器起后建库建账号: + `CREATE DATABASE photo_ov CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin;` + `CREATE USER 'photo_ov'@'%' …GRANT ON photo_ov.*`(安全组收口;sync 专用账号 + `photo_ov_sync`@`101.37.116.184` 后续可拆,首期单账号+% 靠安全组白名单)。 +5. 构建/导入镜像(按 T0 验证结果选路径): + - 路径 A:本机 `docker build` → save/gzip → `scp -J root@8.152.213.143` 直达 ov → `docker load` + - 路径 B(本机 Hub 抽风):源码已在 `/opt/photo-live/src`(git pull)→ ov 服务器 `docker build`(Hub 直连已验证 OK) + - tag 约定 `-20260924`(当前 d331d55,若 T0 有新提交以新 sha 为准);web ov 版带 `VITE_VIEWER_BASE=https://live-album.outside.hnxinyouhe.com` +6. `docker compose … run --rm migrate-deploy ov`(迁移先于 API)。 +7. `SERVER_IMAGE=… WEB_IMAGE=… docker compose -f docker-compose-ov.yml up -d`;health `{"status":"ok"}`。 +8. 1Panel 建网站 `live-album.outside.hnxinyouhe.com` → 反代 `127.0.0.1:18089` → + Let's Encrypt 证书(HTTP-01,80 已开)→ 强制 HTTPS;**反代配置确认 WebSocket + 透传**(/v1/realtime Upgrade 头;1Panel 反代开 websocket 选项或手补 + `proxy_set_header Upgrade/Connection`)。 +9. 外网验证:`https://live-album.outside.hnxinyouhe.com/v1/health` + 观众页 404 兜底正常。 +- 验收:HTTPS 入口通、api-ov healthy、(无数据期)公开端点空态正常。 +- 回滚:`docker compose down`;全新空库无数据风险;1Panel 站点删除。 + +### T3 cn 栈部署 + +1. 同 T2 建目录/clone/探测磁盘。 +2. `.env.production-cn`(600,全新值): + - `DATABASE_URL_CN`/`REDIS_URL_CN` → 自管容器 + - `DATABASE_URL_OV=mysql://photo_ov:@47.254.176.10:13306/photo_ov?connection_limit=10`(**跨公网直连 ov mysql**) + - S3 同桶同凭证;`CORS_ORIGINS=https://live-album.inside.hnxinyouhe.com` + - `SYNC_INTERNAL_KEY` 同 ov;`CN_API_URL=http://api-cn:8080`;`OV_API_URL=https://live-album.outside.hnxinyouhe.com` + - `USER_SYNC_SOURCE=http` + uniservice URL/KEY(从测试服务器 env 抄真实值) + - `SYNC_USERS_INTERVAL_MS=43200000`(12h,用户既定指令) + - `ADMIN_ACCOUNT=admin` + `ADMIN_PASSWORD=<新强密码>`(seed 后交付用户,建议首次登录即改) +3. mysql 建库 photo_cn(同 collate)+ 账号。 +4. 镜像同 T2(web cn 版 `VITE_VIEWER_BASE=https://live-album.inside.hnxinyouhe.com`)。 +5. `migrate-deploy cn` → `seed-admin` → `up -d`(api-cn/worker/sync/web)。 +6. sync 日志核对:推送循环启动、连 ov inbox 通(若 ov API 443 未就绪会重试——T2 已先行则应直通)。 +7. 1Panel 建站点 `live-album.inside.hnxinyouhe.com` → 反代 `127.0.0.1:18088` → 证书(已备案,HTTP-01 可过)→ 强制 HTTPS + WS 透传。 +8. **重建 api 后 restart web 容器**(坑位 ①)。 +- 验收:后台可登录(新密码)、health OK、sync 无连接错误。 +- 回滚:同 T2;RUNBOOK 换旧 tag。 + +### T4 跨境链路验证(依赖用户安全组操作) + +**用户在阿里云控制台操作(唯一外部依赖)**: +- ov 安全组:入方向放行 `TCP/13306 ← 101.37.116.184`(sync 读 ov 库); + 建议加 `TCP/22 ← 101.37.116.184`(cn→ov 备用 SSH 通道)。 +- cn 安全组:确认 80/443 公网放行(站点要通)。 + +验证序: +1. cn 容器内 `mysql -h 47.254.176.10 -P 13306` 连通(或 nc 探测)。 +2. **全链路**:后台建相册→上传测试照片→worker 出变体→outbox→sync 推送(~2s)→ + ov 公开页 `https://live-album.outside…/a/` 看到照片、图片 presigned OSS URL 可加载。 +3. 反向:ov 页点赞→统计回传 60s→cn 库计数增长。 +4. 对账循环日志(5min 周期无 diff)。 +5. `scripts/prod-smoke.sh` 四点冒烟 + CORS 收窄 curl 验证(第三方 Origin 拒)。 +6. uniservice 定时同步:首批手动触发(12h 间隔下重启后首批要等——用后台「手动同步」按钮)。 + +### T5 收尾 + +- 验收记录 `docs/plans/progress/` 落档(含坑位新增、A25 CDN 标注、跨境外网实测数据)。 +- 密钥交付:`.env.production-*` 真实文件留存两台服务器(600)+ 本机工作区(.gitignore 已排除);ADMIN 密码/DB 密码清单单独交用户保管。 +- 监控:`infra/deploy/monitoring.md` 告警项按现状裁剪接入(可后置)。 + +## 3. 密钥与凭据清单(全部新生成,唯一复用 = OSS/uniservice) + +| 项 | 来源 | 存放 | +| --- | --- | --- | +| SYNC_INTERNAL_KEY | `openssl rand -hex 32` 新生成 | 两端 .env(600) | +| mysql root / photo_cn / photo_ov 应用账号密码 | 新生成 | 各机 .env + mysql | +| ADMIN_PASSWORD | 新强密码 | cn .env + 交付用户 | +| S3 四元组(同桶) | 测试服务器 env 抄 | 两端 .env | +| OPEN_API_USERS_URL/KEY | 测试服务器 env 抄 | cn .env | + +## 4. 风险与预案 + +| 风险 | 预案 | +| --- | --- | +| 本机 Docker Hub 抽风(§4.8 事件 A 复发) | 路径 B 服务器构建(ov 已验证 Hub 直连;cn 走加速器) | +| ov 磁盘 6.5G 紧张 | 部署前 builder prune(~2G);mysql/redis/server/web 镜像+库合计估 ~3G,余量足够;超预期则报告用户扩容 | +| 1Panel 反代丢 WS 头 | 手补 Upgrade/Connection 两行;实测 /v1/realtime 握手 | +| 跨境 13306 被扫 | 高位端口 + 安全组单 IP 白名单 + 账号强密码;后续可加 REQUIRE SSL | +| cn 公钥迟迟未装 | T1-T3 的 cn 部分阻塞,但 T2 ov 全部可先行(本计划顺序已兼容) | +| 测试服务器不受影响 | 全新机器全新库,零接触;gitea 仅读 | + +## 5. 与准备件的偏差登记(部署后回填 RUNBOOK/README) + +1. compose 自管 mysql:8.4/redis(1Panel MySQL 是 5.7.44 不满足 utf8mb4_0900_bin/8.4 基线),独立 photo-net 网络。 +2. web 回环端口 18089/18088(8089 与 phpMyAdmin 冲突;18xxx 统一)。 +3. ov mysql 公网映射 13306(模板未含,跨境 sync 读库需要——白名单+SSL 待实测定案项落地)。 +4. `docker/web.Dockerfile` entrypoint 补丁正式化(钉 nginx 版本仍为待办)。 +5. CDN 不接(用户拍板);OSS 复用 G1 桶(用户拍板)。 diff --git a/docs/plans/progress/正式环境双地域部署-验收记录.md b/docs/plans/progress/正式环境双地域部署-验收记录.md new file mode 100644 index 0000000..adad808 --- /dev/null +++ b/docs/plans/progress/正式环境双地域部署-验收记录.md @@ -0,0 +1,62 @@ +# 正式环境双地域部署 验收记录(2026-09-24) + +> 对应计划:docs/plans/2026-09-24-正式环境双地域部署.md(用户确认后执行)。 +> 版本:`photo-live-server:30a3520-20260924`(commit 30a3520,含 nginx entrypoint 补丁正式化); +> web 双版本 `photo-live-web:30a3520-20260924-ov` / `-cn`(VITE_VIEWER_BASE 分域构建)。 + +## 1. 部署形态(结果) + +| 机 | 域名 | 栈 | 外网验证 | +| --- | --- | --- | --- | +| cn 101.37.116.184(Rocky 9.6, 2C/3.5G) | live-album.inside.hnxinyouhe.com | photo-live-cn:api-cn/worker/sync/web + 自管 mysql:8.4(调优)/redis:7,web@127.0.0.1:18088 | `/v1/health` ok;`/admin/` 200 | +| ov 47.254.176.10(Debian 13, 2C/7.4G) | live-album.outside.hnxinyouhe.com | photo-live-ov:api-ov/web-ov + 自管 mysql:8.4/redis:7,web@127.0.0.1:18089,mysql 公网映射 13306(等安全组) | `/v1/health` ok;根 302 | + +入口:两机 1Panel OpenResty 站点(用户自建)+ Let's Encrypt 证书 + 强制 HTTPS; +反代配置确认含 Upgrade/Connection 头。SSH 通道:`photo-ov` 走 ProxyJump 跳板 +8.152.213.143(ov 安全组白名单了它;cn→ov 22/443 亦可达);`photo-cn` 直连。 +均用 `~/.ssh/id_ed25519`(cms-cj-deploy)。 + +## 2. 验证结果(全部通过) + +- **prod-smoke 四点冒烟 9/9 PASS**:双端 health、真实密码登录、建时间戳相册 + → OSS presign PUT(3118B 真实上传,G1 桶凭证复用)→ complete → worker 处理 → + **cn 公开 0s 可见 → ov 公开 0s 可见(跨境 HMAC 推送端到端)**;清理软删 ✓。 +- WS:`/v1/realtime` 经 OpenResty→web→api-ov 升级 101 ✓。 +- CORS:第三方 Origin 无 Access-Control-Allow-Origin(收窄生效)✓。 +- sync 日志:outbox batch pushed(acceptedEventIds 落 ov inbox)✓。 +- 迁移:cn/ov 两库 migrate deploy 全量应用;seed-admin created admin (active) ✓。 +- NTP:两机均同步(阿里云内部 NTP,HMAC ±300s 前提)✓。 + +## 3. 与准备件的偏差(实际落地) + +1. compose 自管 mysql:8.4 + redis:7(两机 1Panel MySQL 均为 5.7.44 不满足基线), + 独立桥网络 photo-net,与机上既有项目(tracker/cc-ai-kefu/cms-cj/loan-reminder 等)零接触。 +2. web 回环端口 18089(ov)/18088(cn)——8089 两机都被 1Panel phpMyAdmin 占用。 +3. cn mysql 调优:performance-schema=OFF + innodb-buffer-pool-size=128M + (cn 仅 3.5G 内存,栈占用实测 ~495M,available ~1.0G)。 +4. ov mysql 公网映射 0.0.0.0:13306(sync 直连读库:对账+统计回传;**安全组未放行前 + 这两循环持续幂等重试**,内容推送走 API 443 不受影响)。 +5. `docker/web.Dockerfile` 正式化 entrypoint ipv6 脚本移除(commit 30a3520;钉版本仍待办)。 + +## 4. 遗留与待办 + +- **[用户操作] ov 安全组放行 TCP/13306 ← 101.37.116.184**——放行后 sync 的 + reconcile(5min 对账)与 stat return(60s 统计回传)即自愈(幂等重试中)。 +- **[用户决策] cn 机 2G swap**:机器级改动经用户确认后再做(当前 1.0G available + 无 swap,worker sharp 峰值有 OOM 风险;建议做)。 +- **[用户操作] uniservice 首批用户同步**:12h 间隔下重启后首批要等——后台「手动同步」 + 按钮触发(或等定时)。 +- gitea push 待令牌(本会话推送认证失败;commit 30a3520 未推送,镜像已直传服务器)。 +- 服务器无源码 clone(部署走镜像传输);后续更新流程待 gitea 令牌后补 `git pull` 工作流。 +- 磁盘两机均 ~85%(cn 5.6G/ov 5.7G 余)——长期扩容建议已登记。 +- 既有风险提醒(未动):cn 机 1Panel MySQL 5.7 绑 0.0.0.0:3306 公网暴露,建议收口。 +- 密钥管理:全部新生成(SYNC_INTERNAL_KEY/mysql×4/ADMIN_PASSWORD),本地 + `.env.prod-secrets/`(gitignore)+ 两机 `.env*`(600)。ADMIN 密码建议首次登录后 + 经后台修改(env 值仅 seed 用)。 + +## 5. 运维备忘(本次新增坑位) + +- cn/ov 入口均为 1Panel OpenResty 站点(用户面板管理,续期由 1Panel 负责); + 容器操作走 `docker compose -f /opt/photo-live/deploy/docker-compose-{cn,ov}.yml`。 +- 本机→ov 文件传输走 `scp photo-ov:`(自动过跳板);226MB 镜像包约 4 分钟。 +- 重建 api 容器后 web 需一并 restart(既有坑位,本次未触发——首装 up 一次成型)。