d5b11c0943
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
219 lines
17 KiB
HTML
219 lines
17 KiB
HTML
<!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>,不复刻 pangolin(pangolin 后续单独迁走)</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 天,期间解析不中断)。
|
||
备案本身 1–3 周。<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>独立 nginx(listen 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 '<pass>';
|
||
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 > 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='<pass>',
|
||
SOURCE_LOG_FILE='<dump里的file>', SOURCE_LOG_POS=<dump里的pos>;
|
||
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"><ALI_IP></span></li>
|
||
<li>Cloudflare 解锁 <code>51yanmei.com</code> → 取 auth code → 阿里万网发起<b>注册商转入</b>(5–7 天,解析不中断)</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 / certbot(cloudflare 插件)</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:<ALI_IP></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 降到 60–120s
|
||
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 → <ALI_IP></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>
|