Files
jiu/docs/runbooks/ec2-to-ali-migration.html
T

219 lines
17 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EC2 → 阿里云 迁移计划(定稿)— 酒库管理系统</title>
<style>
:root{
--primary:#2563AC; --primary-dark:#154072; --danger:#D14343; --danger-bg:#FDECEC;
--success:#2E8B57; --success-bg:#E6F3EC; --warn:#B45309; --warn-bg:#FFF4E5; --accent:#8B2331;
--ink:#232934; --muted:#6E7888; --border:#DCE2EB; --paper:#F5F7FA; --head:#F0F4FF;
}
*{box-sizing:border-box;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
body{margin:0;background:var(--paper);color:var(--ink);padding:28px;line-height:1.65;}
h1{font-size:22px;margin:0 0 4px;}
h2{font-size:17px;margin:30px 0 12px;color:var(--primary-dark);border-bottom:2px solid var(--head);padding-bottom:6px;}
h3{font-size:14px;margin:18px 0 8px;color:var(--accent);}
.sub{color:var(--muted);font-size:13px;margin-bottom:18px;}
.card{background:#fff;border:1px solid var(--border);border-radius:10px;padding:16px 20px;margin:14px 0;}
.lead{font-size:13.5px;color:var(--ink);}
table{width:100%;border-collapse:collapse;font-size:13px;margin:8px 0;}
th{background:var(--head);color:var(--primary-dark);font-weight:600;font-size:12px;text-align:left;padding:9px 10px;border-bottom:1px solid var(--border);}
td{padding:8px 10px;border-bottom:1px solid #EEF1F5;vertical-align:top;}
.mono{font-family:ui-monospace,Menlo,monospace;font-size:12.5px;}
code{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#EEF2F8;padding:1px 5px;border-radius:4px;color:var(--primary-dark);}
pre{background:#1E2430;color:#D8E0EC;border-radius:8px;padding:12px 14px;overflow:auto;font-size:12px;line-height:1.55;}
pre .c{color:#7A8699;}
.tag{display:inline-block;font-size:11px;padding:1px 8px;border-radius:10px;font-weight:600;}
.tag.keep{background:var(--success-bg);color:var(--success);}
.tag.move{background:#E5EEF9;color:var(--primary);}
.tag.drop{background:#EEE;color:var(--muted);}
.tag.risk{background:var(--danger-bg);color:var(--danger);}
.tag.ok{background:var(--success-bg);color:var(--success);}
.callout{border-left:4px solid var(--primary);background:#F0F6FF;padding:10px 14px;border-radius:4px;margin:12px 0;font-size:13px;}
.callout.danger{border-color:var(--danger);background:var(--danger-bg);}
.callout.warn{border-color:var(--warn);background:var(--warn-bg);}
.callout.ok{border-color:var(--success);background:var(--success-bg);}
.callout b{color:var(--ink);}
ul,ol{margin:6px 0;padding-left:22px;}
li{margin:3px 0;font-size:13px;}
.phase{display:flex;align-items:center;gap:10px;margin:22px 0 6px;}
.phase .num{flex:0 0 auto;width:30px;height:30px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;}
.phase .num.gate{background:var(--danger);}
.phase h2{margin:0;border:0;padding:0;font-size:16px;}
.chk{list-style:none;padding-left:0;}
.chk li{padding-left:24px;position:relative;margin:5px 0;}
.chk li::before{content:"☐";position:absolute;left:0;color:var(--primary);font-size:14px;}
.meta{display:flex;gap:26px;flex-wrap:wrap;font-size:12px;color:var(--muted);margin-top:4px;}
.meta b{color:var(--ink);}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
@media(max-width:720px){.grid2{grid-template-columns:1fr;}}
</style>
</head>
<body>
<h1>EC2 → 阿里云 迁移计划 <span class="tag ok">定稿</span></h1>
<div class="sub">酒库管理系统(jiu)· 把 jiu 的服务 / 数据 / 文件 / DB / 域名解析 / CI 接入从 AWS EC2 整体切到阿里云 ECS。<b>共享机上的 pangolin / marzban / blog 原地不动</b>。核心策略:<b>DB 主从实时复制 + ICP 备案期间双轨部署,最后几秒提升从库为主 + 切 DNS</b></div>
<div class="meta">
<span><b></b> AWS EC2 · ap-southeast-1(新加坡)· Amazon Linux 2023 · <span class="mono">18.136.60.128</span></span>
<span><b>目标</b> 阿里云 ECS · 大陆地域(需备案)</span>
<span><b>切流停机</b> 仅几秒(提升从库)</span>
<span><b>定稿日</b> 2026-06-23</span>
</div>
<h3>已拍定的关键决策</h3>
<table>
<tr><th></th><th>定论</th></tr>
<tr><td>接入架构</td><td><b>阿里独立 nginx 直连 443</b>,不复刻 pangolinpangolin 后续单独迁走)</td></tr>
<tr><td>证书</td><td><b>certbot DNS-01 + Cloudflare API</b> 预签(与机器 IP/端口/解析解耦,切流前即可备好)</td></tr>
<tr><td>数据库切换</td><td><b>MySQL 主从复制</b>(EC2 主 → 阿里从,实时同步),切流时提升从库为主,写冻结仅几秒</td></tr>
<tr><td>CI/CD</td><td><b>双轨</b>:编译/测试/发布不变,Deploy 拆成 EC2 + Ali 两步并存,迁移期每次发版同步两边</td></tr>
<tr><td>域名</td><td>注册商先从 Cloudflare 转入阿里万网(备案前置),<b>转移不影响解析</b>,全程仍指 EC2,备案过后再切 A 记录</td></tr>
</table>
<div class="callout danger">
<b>⚠ 头号前置 — 域名注册商转移 + ICP 备案:</b>
<code>51yanmei.com</code> 现注册在 <b>Cloudflare(海外)</b>,不在工信部名单 → 大陆备案前必须把<b>注册商转入阿里万网</b>(转出流程 5–7 天,期间解析不中断)。
备案本身 13 周。<b>这两件事决定整个时间表,且 EC2 在此期间照常对外服务。</b>
注意:<b>注册商转移 ≠ 改解析</b>——转入阿里万网后 A 记录照样指 EC2,线上零影响。
</div>
<!-- ============ 1. 迁移盘点 ============ -->
<h2>一、迁移盘点(搬什么 / 不搬什么)</h2>
<p class="lead">EC2 是<b>多服务共享机</b>,本次只迁 jiu。</p>
<h3>① 要迁移的 jiu 资产 <span class="tag move">MOVE</span></h3>
<table>
<tr><th>类别</th><th>内容</th><th>位置 / 体量</th><th>迁移方式</th></tr>
<tr><td>后端服务</td><td>systemd <code>jiu.service</code><span class="mono">jiu-server</span>+ <span class="mono">jiu-gencode</span></td><td><span class="mono">/opt/jiu/backend</span> · 64M</td><td>CI 双轨重新部署</td></tr>
<tr><td>数据库</td><td>MySQL 8.0 容器 <code>jiu_mysql</code> / 库 <code>jiu_db</code></td><td>35.3 MB</td><td><b>主从复制实时同步</b>(非一次性 dump</td></tr>
<tr><td>配置</td><td><span class="mono">production.env</span>(含密钥)、<span class="mono">version.yaml</span></td><td><span class="mono">/opt/jiu/config</span></td><td>scp 拷贝;域名不变 → 内容照搬</td></tr>
<tr><td>上传文件</td><td>商品图片</td><td><span class="mono">/opt/jiu/images</span> · 2M</td><td><code>rsync</code><b>不在 DB 复制范围</b>,切流前再补一次)</td></tr>
<tr><td>前端 / 官网 / 安装包</td><td>Flutter web、Eleventy 站、apk/zip/exe</td><td><span class="mono">/opt/jiu/{web,marketing,downloads}</span></td><td>CI 双轨下发 / rsync</td></tr>
<tr><td>反代配置</td><td>nginx <code>jiu.conf</code></td><td>pangolin-edge 容器内</td><td>改造为<b>独立 nginxlisten 443</b></td></tr>
<tr><td>TLS 证书</td><td><code>jiu.51yanmei.com</code></td><td>EC2 letsencrypt</td><td>阿里机 <b>DNS-01 预签</b></td></tr>
<tr><td>域名解析</td><td><code>jiu.51yanmei.com</code> A 记录</td><td>Cloudflare → <span class="mono">18.136.60.128</span> · 未代理</td><td>备案后切到阿里 IP(唯一一条 DNS 改动)</td></tr>
</table>
<h3>② 留在 EC2 / 不动 <span class="tag keep">KEEP</span></h3>
<p class="lead">pangolin-edge / pangolin-singbox(共享反代,后续单独迁)、marzban、blog 全家桶(blog/umami/postgres)、blog/vpn 证书、blog/pay/vpn 解析。</p>
<h3>③ NAS(群晖 <span class="mono">192.168.3.200</span>)不搬,仅改配置 <span class="tag keep">KEEP</span></h3>
<div class="callout ok">
Forgejo<code>git.51yanmei.com:2222</code>+ act_runner 在<b>内网群晖</b><code>git.51yanmei.com</code> 解析的是<b>内网 IP</b>,与 EC2 无关。迁移只需:① 加 <span class="mono">ALI_*</span> Secrets;② 改 <code>deploy-server.sh</code> 的 nginx reload 命令。
</div>
<h3>④ 顺手清理 <span class="tag drop">DROP</span></h3>
<p class="lead"><span class="mono">/opt/jiu/web-old</span><span class="mono">/opt/jiu/web_old</span> —— 历史残留,不迁。</p>
<!-- ============ 2. 数据库主从 ============ -->
<h2>二、数据库主从复制(方案核心)</h2>
<div class="callout ok"><b>EC2 主库已就绪、零重启:</b><span class="mono">log_bin=ON</span> · <span class="mono">binlog_format=ROW</span> · <span class="mono">server_id=1</span> · <span class="mono">gtid=OFF</span> · <span class="mono">binlog 保留 30 天</span>。→ 用<b>经典 binlog 位点复制</b>,不碰主库、不停现网;隧道断几天也能靠 30 天 binlog 自动追上。</div>
<h3>网络通路:SSH 隧道(不暴露 DB 公网)</h3>
<pre><span class="c"># 阿里机用 autossh 保活隧道,从库连本机 13306 → EC2 的 jiu_mysql:3306</span>
autossh -M 0 -fN -L 13306:127.0.0.1:3306 ec2 <span class="c"># systemd 托管,断线自动重连</span></pre>
<h3>搭建(EC2 纯新增,不影响现网)</h3>
<pre><span class="c"># 1) EC2 主库建复制账号</span>
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '&lt;pass&gt;';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
<span class="c"># 2) 带位点快照 → 导入阿里从库</span>
docker exec jiu_mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD \
--single-transaction --master-data=2 jiu_db' | gzip &gt; snap.sql.gz
<span class="c"># 3) 阿里从库(容器 command 加 --server-id=2 --read-only)挂主库开追</span>
CHANGE REPLICATION SOURCE TO SOURCE_HOST='127.0.0.1', SOURCE_PORT=13306,
SOURCE_USER='repl', SOURCE_PASSWORD='&lt;pass&gt;',
SOURCE_LOG_FILE='&lt;dump里的file&gt;', SOURCE_LOG_POS=&lt;dump里的pos&gt;;
START REPLICA; <span class="c"># SHOW REPLICA STATUS\G → Seconds_Behind_Source=0 即追平</span></pre>
<!-- ============ 3. 分阶段步骤 ============ -->
<h2>三、迁移步骤(分阶段)</h2>
<div class="phase"><div class="num">0</div><h2>P0 · 域名注册商转移 + 提交备案(并行,EC2 照常服务)</h2></div>
<ul class="chk">
<li>买阿里 ECS(大陆,公网 IP,满足备案对实例时长要求),记 <span class="mono">&lt;ALI_IP&gt;</span></li>
<li>Cloudflare 解锁 <code>51yanmei.com</code> → 取 auth code → 阿里万网发起<b>注册商转入</b>57 天,解析不中断)</li>
<li>决定 DNS 托管:继续留 Cloudflare(保持 NS)或搬阿里云 DNS(先抄全部记录再切 NS)</li>
<li>提交 <b>ICP 备案</b>(阿里接入),等 1–3 周;EC2 全程照常对外</li>
</ul>
<div class="phase"><div class="num">1</div><h2>P1 · 阿里机基础搭建(无停机)</h2></div>
<ul class="chk">
<li>安全组放行 22/80/443<b>3306 不对公网</b>;装 docker / docker-compose / nginx / certbotcloudflare 插件)</li>
<li><span class="mono">/opt/jiu/{backend,config,images,downloads,web,marketing}</span>;生成<b>新部署 SSH 密钥对</b></li>
<li>scp <code>production.env</code> / <code>version.yaml</code>rsync <code>images/</code> <code>downloads/</code></li>
</ul>
<div class="phase"><div class="num">2</div><h2>P2 · 起从库实时同步(见第二节)</h2></div>
<ul class="chk">
<li><code>jiu_mysql</code><span class="mono">--server-id=2 --read-only</span>)→ autossh 隧道 → 导入带位点快照 → <code>START REPLICA</code></li>
<li>确认 <span class="mono">Seconds_Behind_Source=0</span>,挂个延迟监控告警</li>
</ul>
<div class="phase"><div class="num">3</div><h2>P3 · nginx + 证书 + 后端(仍未切流,--resolve 自测)</h2></div>
<ul class="chk">
<li>certbot <b>DNS-01</b><code>jiu.51yanmei.com</code> 预签证书(无需解析指过来)</li>
<li>落地改造版 nginx<span class="mono">listen 443 ssl</span> 直连),<code>nginx -t</code> 通过</li>
<li><code>jiu.service</code>;用 <code>curl --resolve jiu.51yanmei.com:443:&lt;ALI_IP&gt;</code> 验证 health/version/官网/静态</li>
<li class=""><b>后端跑在只读副本上</b>AutoMigrate 无 schema 变更时是 no-op 能过;写链路(登录/出入库)要等切流提升为主后才完整可测</li>
</ul>
<div class="phase"><div class="num">4</div><h2>P4 · CI/CD 双轨</h2></div>
<table>
<tr><th>对象</th><th>改动</th></tr>
<tr><td>Secrets</td><td>新增 <span class="mono">ALI_HOST / ALI_SSH_KEY / ALI_USER</span>EC2_* 保留不动)</td></tr>
<tr><td>deploy-server/site/client.yml</td><td>Deploy 拆两步:<b>「Deploy → EC2」在前,「Deploy → Ali」在后</b>,均 <code>if: always()</code>(一边失败不挡发版)</td></tr>
<tr><td><span class="mono">deploy-server.sh</span></td><td>参数化目标 host/key + nginx reload 方式:EC2 走 <code>docker exec pangolin-edge</code>Ali 走独立 <code>nginx -s reload</code>(配置落 <span class="mono">/etc/nginx/conf.d/jiu.conf</span></td></tr>
<tr><td><span class="mono">deploy/nginx-jiu.conf</span></td><td><span class="mono">listen 127.0.0.1:8445 ssl</span><span class="mono">listen 443 ssl</span></td></tr>
</table>
<div class="callout warn"><b>Schema 变更版本的顺序:</b>带新列/索引的发版,靠「EC2 先部署 → DDL 经复制流到达阿里副本 → Ali 再部署即 no-op」化解。workflow 里 EC2 步骤排在 Ali 之前,天然满足。</div>
<div class="phase"><div class="num gate">5</div><h2>P5 · 【备案通过后】切流:提升从库为主 + 切 DNS(写冻结仅几秒)</h2></div>
<pre>0. 切前两边各全备一份(提升后 EC2 库即过时,留底兜底)
1. 提前把 jiu.51yanmei.com TTL 降到 60120s
2. 最后一次 rsync EC2:/opt/jiu/images → Ali <span class="c"># 文件不走复制</span>
3. EC2: systemctl stop jiu <span class="c"># 停写</span>
4. Ali: 确认 Seconds_Behind_Source=0(追平最后一笔)
5. Ali: STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=OFF; <span class="c"># 转独立主库</span>
6. Ali: systemctl restart jiu <span class="c"># AutoMigrate 跑在可写库,no-op</span>
7. Cloudflare: A 记录 18.136.60.128 → &lt;ALI_IP&gt;</pre>
<div class="phase"><div class="num">6</div><h2>P6 · 公网验证</h2></div>
<ul class="chk">
<li>/health、/version、HTTPS 证书;登录 + 出入库审核(写链路);传图 → /images 可访问</li>
<li>官网 / 下载 / <span class="mono">/app</span> / 扫码 <span class="mono">/product/:id</span> OG 标签</li>
<li>触发一次 <code>backup-db</code> workflow 确认打到阿里机</li>
</ul>
<div class="phase"><div class="num">7</div><h2>P7 · 观察后拆 EC2</h2></div>
<ul class="chk">
<li>观察 3–7 天无异常 → EC2:删 <code>jiu.service</code>、停删 <code>jiu_mysql</code>+volume、删 <span class="mono">/opt/jiu</span>、删 pangolin <span class="mono">conf.d/jiu.conf</span></li>
<li>删 EC2 上 jiu 证书;TTL 调回;CI 删掉「Deploy → EC2」一轨</li>
<li>更新 <span class="mono">docs/deployment-nas-gitea.md</span> 与 CLAUDE.md 中部署主机描述</li>
</ul>
<!-- ============ 4. 风险与回滚 ============ -->
<h2>四、风险与回滚</h2>
<table>
<tr><th>风险</th><th>等级</th><th>对策</th></tr>
<tr><td>注册商转移 + 备案周期长</td><td><span class="tag risk"></span></td><td>EC2 全程照常服务;阿里机后台先搭好、双轨同步,备案不阻塞日常发版</td></tr>
<tr><td>图片文件不在 DB 复制范围</td><td><span class="tag risk"></span></td><td>P5 切流前最后 rsync 一次 <code>images/</code>;切流后新图直接落阿里</td></tr>
<tr><td>提升从库即 DB 不可逆</td><td><span class="tag risk"></span></td><td>P5 第 0 步两边全备;低 TTL,出问题 A 记录改回 EC2 + 拿备份恢复</td></tr>
<tr><td>autossh 隧道断 → 复制暂停</td><td><span class="tag risk"></span></td><td>binlog 留 30 天,重连自动追上;挂延迟告警</td></tr>
<tr><td>schema 变更版本在副本上 AutoMigrate 失败</td><td><span class="tag risk"></span></td><td>双轨 EC2 先于 Ali,DDL 先复制到位</td></tr>
</table>
<!-- ============ 5. 剩余小决策 ============ -->
<h2>五、剩余小决策</h2>
<table>
<tr><th>#</th><th>决策点</th><th>推荐</th></tr>
<tr><td></td><td>DNS 托管:留 Cloudflare / 搬阿里云 DNS</td><td>留 Cloudflare(记录已在此,改动最小)</td></tr>
<tr><td></td><td>P5 切流时段</td><td>夜间低峰(写冻结仅几秒,影响极小)</td></tr>
</table>
<hr style="border:0;border-top:1px solid var(--border);margin:28px 0 10px;">
<div class="sub">文档:<span class="mono">/Users/wangjia/code/jiu/docs/runbooks/ec2-to-ali-migration.html</span> · 已登记 <span class="mono">docs/index.html</span></div>
</body>
</html>