EC2 → 阿里云 迁移计划 定稿
酒库管理系统(jiu)· 把 jiu 的服务 / 数据 / 文件 / DB / 域名解析 / CI 接入从 AWS EC2 整体切到阿里云 ECS。共享机上的 pangolin / marzban / blog 原地不动。核心策略:DB 主从实时复制 + ICP 备案期间双轨部署,最后几秒提升从库为主 + 切 DNS。
源 AWS EC2 · ap-southeast-1(新加坡)· Amazon Linux 2023 · 18.136.60.128
目标 阿里云 ECS · 大陆地域(需备案)
切流停机 仅几秒(提升从库)
定稿日 2026-06-23
已拍定的关键决策
| 项 | 定论 |
| 接入架构 | 阿里独立 nginx 直连 443,不复刻 pangolin(pangolin 后续单独迁走) |
| 证书 | certbot DNS-01 + Cloudflare API 预签(与机器 IP/端口/解析解耦,切流前即可备好) |
| 数据库切换 | MySQL 主从复制(EC2 主 → 阿里从,实时同步),切流时提升从库为主,写冻结仅几秒 |
| CI/CD | 双轨:编译/测试/发布不变,Deploy 拆成 EC2 + Ali 两步并存,迁移期每次发版同步两边 |
| 域名 | 注册商先从 Cloudflare 转入阿里万网(备案前置),转移不影响解析,全程仍指 EC2,备案过后再切 A 记录 |
⚠ 头号前置 — 域名注册商转移 + ICP 备案:
51yanmei.com 现注册在 Cloudflare(海外),不在工信部名单 → 大陆备案前必须把注册商转入阿里万网(转出流程 5–7 天,期间解析不中断)。
备案本身 1–3 周。这两件事决定整个时间表,且 EC2 在此期间照常对外服务。
注意:注册商转移 ≠ 改解析——转入阿里万网后 A 记录照样指 EC2,线上零影响。
一、迁移盘点(搬什么 / 不搬什么)
EC2 是多服务共享机,本次只迁 jiu。
① 要迁移的 jiu 资产 MOVE
| 类别 | 内容 | 位置 / 体量 | 迁移方式 |
| 后端服务 | systemd jiu.service → jiu-server(+ jiu-gencode) | /opt/jiu/backend · 64M | CI 双轨重新部署 |
| 数据库 | MySQL 8.0 容器 jiu_mysql / 库 jiu_db | 35.3 MB | 主从复制实时同步(非一次性 dump) |
| 配置 | production.env(含密钥)、version.yaml | /opt/jiu/config | scp 拷贝;域名不变 → 内容照搬 |
| 上传文件 | 商品图片 | /opt/jiu/images · 2M | rsync(不在 DB 复制范围,切流前再补一次) |
| 前端 / 官网 / 安装包 | Flutter web、Eleventy 站、apk/zip/exe | /opt/jiu/{web,marketing,downloads} | CI 双轨下发 / rsync |
| 反代配置 | nginx jiu.conf | pangolin-edge 容器内 | 改造为独立 nginx(listen 443) |
| TLS 证书 | jiu.51yanmei.com | EC2 letsencrypt | 阿里机 DNS-01 预签 |
| 域名解析 | jiu.51yanmei.com A 记录 | Cloudflare → 18.136.60.128 · 未代理 | 备案后切到阿里 IP(唯一一条 DNS 改动) |
② 留在 EC2 / 不动 KEEP
pangolin-edge / pangolin-singbox(共享反代,后续单独迁)、marzban、blog 全家桶(blog/umami/postgres)、blog/vpn 证书、blog/pay/vpn 解析。
③ NAS(群晖 192.168.3.200)不搬,仅改配置 KEEP
Forgejo(git.51yanmei.com:2222)+ act_runner 在内网群晖,git.51yanmei.com 解析的是内网 IP,与 EC2 无关。迁移只需:① 加 ALI_* Secrets;② 改 deploy-server.sh 的 nginx reload 命令。
④ 顺手清理 DROP
/opt/jiu/web-old、/opt/jiu/web_old —— 历史残留,不迁。
二、数据库主从复制(方案核心)
EC2 主库已就绪、零重启:log_bin=ON · binlog_format=ROW · server_id=1 · gtid=OFF · binlog 保留 30 天。→ 用经典 binlog 位点复制,不碰主库、不停现网;隧道断几天也能靠 30 天 binlog 自动追上。
网络通路:SSH 隧道(不暴露 DB 公网)
# 阿里机用 autossh 保活隧道,从库连本机 13306 → EC2 的 jiu_mysql:3306
autossh -M 0 -fN -L 13306:127.0.0.1:3306 ec2 # systemd 托管,断线自动重连
搭建(EC2 纯新增,不影响现网)
# 1) EC2 主库建复制账号
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '<pass>';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
# 2) 带位点快照 → 导入阿里从库
docker exec jiu_mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD \
--single-transaction --master-data=2 jiu_db' | gzip > snap.sql.gz
# 3) 阿里从库(容器 command 加 --server-id=2 --read-only)挂主库开追
CHANGE REPLICATION SOURCE TO SOURCE_HOST='127.0.0.1', SOURCE_PORT=13306,
SOURCE_USER='repl', SOURCE_PASSWORD='<pass>',
SOURCE_LOG_FILE='<dump里的file>', SOURCE_LOG_POS=<dump里的pos>;
START REPLICA; # SHOW REPLICA STATUS\G → Seconds_Behind_Source=0 即追平
三、迁移步骤(分阶段)
0
P0 · 域名注册商转移 + 提交备案(并行,EC2 照常服务)
- 买阿里 ECS(大陆,公网 IP,满足备案对实例时长要求),记 <ALI_IP>
- Cloudflare 解锁
51yanmei.com → 取 auth code → 阿里万网发起注册商转入(5–7 天,解析不中断)
- 决定 DNS 托管:继续留 Cloudflare(保持 NS)或搬阿里云 DNS(先抄全部记录再切 NS)
- 提交 ICP 备案(阿里接入),等 1–3 周;EC2 全程照常对外
- 安全组放行 22/80/443,3306 不对公网;装 docker / docker-compose / nginx / certbot(cloudflare 插件)
- 建 /opt/jiu/{backend,config,images,downloads,web,marketing};生成新部署 SSH 密钥对
- scp
production.env / version.yaml;rsync images/ downloads/
- 起
jiu_mysql(--server-id=2 --read-only)→ autossh 隧道 → 导入带位点快照 → START REPLICA
- 确认 Seconds_Behind_Source=0,挂个延迟监控告警
3
P3 · nginx + 证书 + 后端(仍未切流,--resolve 自测)
- certbot DNS-01 为
jiu.51yanmei.com 预签证书(无需解析指过来)
- 落地改造版 nginx(listen 443 ssl 直连),
nginx -t 通过
- 装
jiu.service;用 curl --resolve jiu.51yanmei.com:443:<ALI_IP> 验证 health/version/官网/静态
- ⚠ 后端跑在只读副本上:AutoMigrate 无 schema 变更时是 no-op 能过;写链路(登录/出入库)要等切流提升为主后才完整可测
| 对象 | 改动 |
| Secrets | 新增 ALI_HOST / ALI_SSH_KEY / ALI_USER(EC2_* 保留不动) |
| deploy-server/site/client.yml | Deploy 拆两步:「Deploy → EC2」在前,「Deploy → Ali」在后,均 if: always()(一边失败不挡发版) |
| deploy-server.sh | 参数化目标 host/key + nginx reload 方式:EC2 走 docker exec pangolin-edge,Ali 走独立 nginx -s reload(配置落 /etc/nginx/conf.d/jiu.conf) |
| deploy/nginx-jiu.conf | listen 127.0.0.1:8445 ssl → listen 443 ssl |
Schema 变更版本的顺序:带新列/索引的发版,靠「EC2 先部署 → DDL 经复制流到达阿里副本 → Ali 再部署即 no-op」化解。workflow 里 EC2 步骤排在 Ali 之前,天然满足。
5
P5 · 【备案通过后】切流:提升从库为主 + 切 DNS(写冻结仅几秒)
0. 切前两边各全备一份(提升后 EC2 库即过时,留底兜底)
1. 提前把 jiu.51yanmei.com TTL 降到 60–120s
2. 最后一次 rsync EC2:/opt/jiu/images → Ali # 文件不走复制
3. EC2: systemctl stop jiu # 停写
4. Ali: 确认 Seconds_Behind_Source=0(追平最后一笔)
5. Ali: STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=OFF; # 转独立主库
6. Ali: systemctl restart jiu # AutoMigrate 跑在可写库,no-op
7. Cloudflare: A 记录 18.136.60.128 → <ALI_IP>
- /health、/version、HTTPS 证书;登录 + 出入库审核(写链路);传图 → /images 可访问
- 官网 / 下载 / /app / 扫码 /product/:id OG 标签
- 触发一次
backup-db workflow 确认打到阿里机
- 观察 3–7 天无异常 → EC2:删
jiu.service、停删 jiu_mysql+volume、删 /opt/jiu、删 pangolin conf.d/jiu.conf
- 删 EC2 上 jiu 证书;TTL 调回;CI 删掉「Deploy → EC2」一轨
- 更新 docs/deployment-nas-gitea.md 与 CLAUDE.md 中部署主机描述
四、风险与回滚
| 风险 | 等级 | 对策 |
| 注册商转移 + 备案周期长 | 高 | EC2 全程照常服务;阿里机后台先搭好、双轨同步,备案不阻塞日常发版 |
| 图片文件不在 DB 复制范围 | 中 | P5 切流前最后 rsync 一次 images/;切流后新图直接落阿里 |
| 提升从库即 DB 不可逆 | 中 | P5 第 0 步两边全备;低 TTL,出问题 A 记录改回 EC2 + 拿备份恢复 |
| autossh 隧道断 → 复制暂停 | 低 | binlog 留 30 天,重连自动追上;挂延迟告警 |
| schema 变更版本在副本上 AutoMigrate 失败 | 低 | 双轨 EC2 先于 Ali,DDL 先复制到位 |
五、剩余小决策
| # | 决策点 | 推荐 |
| ① | DNS 托管:留 Cloudflare / 搬阿里云 DNS | 留 Cloudflare(记录已在此,改动最小) |
| ② | P5 切流时段 | 夜间低峰(写冻结仅几秒,影响极小) |
文档:/Users/wangjia/code/jiu/docs/runbooks/ec2-to-ali-migration.html · 已登记 docs/index.html