我把下载站迁到OBS后连踩三坑

举报
yd_272744321 发表于 2026/10/24 10:27:40 2026/10/24
【摘要】 我把一个月访问约20万次的前端下载站从ECS挪到OBS静态托管,连续遇到桶权限403、生命周期误删线上文件、跨区复制漏历史对象三个问题。这里记下我实际用过的命令、配置、恢复过程,以及迁移前后账单变化。

迁移当晚和 403 排查

8 月 12 日 20:03 我在值班群里发了“开始切流”,把补丁下载页从 ECS 挪到 OBS 静态网站托管。老机器是华为云 s6.large.2,2 vCPU / 4 GiB,40GB 高 I/O 系统盘,EIP 固定带宽 5Mbps,站点是 Vue 3.4.27 打包产物,dist 一共 24MB、187 个文件。过去三个月 PV 在 20 万到 23 万之间,7 月 ECS 账单 207.6 元,带宽单项 96 元,我就是想把固定支出压到更低,不然活动周我得盯着余额曲线看半天。

构建跑在 c6.large.2,EulerOS 2.0 SP11,Node.js 18.19.1,pnpm 8.15.6,obsutil 5.5.12。预演全流程 6 分 42 秒,真切流拖到 43 分钟。20:08 到 20:34 我一直怀疑工具链,先查 obsutil 版本兼容,再看 lockfileVersion=9 会不会和 pnpm 版本冲突,还把同一 commit 重打包两次。两次产物的 `sha256sum dist/assets/index-9f3a7.js` 完全一样,这时我才承认自己方向错了。

obsutil version
# obsutil version: 5.5.12
node -v   # v18.19.1
pnpm -v   # 8.15.6

obsutil mb obs://team-portal-prod -location=cn-north-4 -storageClass=STANDARD
obsutil chattri obs://team-portal-prod -website=index.html,error.html
obsutil sync ./dist obs://team-portal-prod/ -delete=false -f -j=8 -p=8

DNS 在 20:17 切到 `team-portal-prod.obs-website.cn-north-4.myhuaweicloud.com` 后,首页直接 403。我用公司出口、手机 4G、家宽都复现,排除了缓存。`curl -i` 的返回很干脆,`x-obs-request-id` 每次都变,但错误码稳定是 AccessDenied。

curl -i http://team-portal-prod.obs-website.cn-north-4.myhuaweicloud.com/
HTTP/1.1 403 Forbidden
x-obs-request-id: 00000191A7C57D6D7C1A2B8E7D3F11A0
Content-Type: application/xml

<Error>
  <Code>AccessDenied</Code>
  <Message>Access denied</Message>
</Error>

这个 403 完全是我自己写出来的。

桶 ACL 我设成 private 本来没问题,坑在策略 Action 写成了 `GetObjectAcl`,匿名请求自然读不到对象。我当时还做了个失败尝试,把 ACL 临时改成 `public-read`,安全同事 30 秒内就在群里让我改回去,这个来回多耗了 11 分钟。真正需要改的只有一行,把 Action 调整为 `GetObject`,其余配置不动。20:54 再次 `curl` 返回 200,页面 3 分钟内恢复。

# 错误策略
{
  "Version": "1.1",
  "Statement": [{
    "Sid": "WrongAction",
    "Effect": "Allow",
    "Principal": {"ID":["*"]},
    "Action": ["GetObjectAcl"],
    "Resource": ["obs:team-portal-prod/*"]
  }]
}

# 修正后
{
  "Version": "1.1",
  "Statement": [{
    "Sid": "AllowPublicReadOnly",
    "Effect": "Allow",
    "Principal": {"ID":["*"]},
    "Action": ["GetObject"],
    "Resource": ["obs:team-portal-prod/*"]
  }]
}

我那 26 分钟都在怀疑 npm 和 obsutil,回头看全是误判。

生命周期误删、跨区回填和脚本返工

第 8 天 09:43 前端同事在群里贴白屏,我打开控制台就看到 `GET https://static.example.com/assets/index-9f3a7.js 404`。我先查 CodeArts 任务 #842,状态 success,耗时 4m19s,上传步骤记录 `Total file count: 187`,构建链路看起来正常。再看桶事件,09:00 到 09:06 出现 417 条 `DeleteObject`,操作者是 `LifecycleService`,这时定位就很明确了,是生命周期规则删了线上对象。

GET https://static.example.com/assets/index-9f3a7.js 404 (Not Found)

<Error>
  <Code>NoSuchKey</Code>
  <Message>The specified key does not exist.</Message>
  <RequestId>00000191B2A34E6CBB55A9A3D96D7783</RequestId>
</Error>

EventName=DeleteObject
UserIdentity=LifecycleService
Count(09:00-09:06)=417

这次真的是手抖。

我原本只想清理 `history/` 下 7 天前的旧包,结果 Prefix 留空,命中了全桶当前版本对象。那时没开版本控制,删掉后桶里没有可回滚版本。我先做了个无效补救,用本地最新 dist 回传:

obsutil sync ./dist obs://team-portal-prod/ -f -j=8 -p=8
# Succeed count: 187
# Failed count: 0

首页恢复了,历史补丁链接还是 404,因为 `history/20240805`、`history/20240809` 这些目录本地没有。可用方案是去 CodeArts 制品库回捞,我下载了 8 月 5 日、9 日、12 日三次归档包,每份约 280MB,下载 18 分钟,再用 `obsutil cp -u` 补传,避免覆盖同名新文件。恢复窗口是 09:51 到 12:07,总计 2 小时 16 分钟,客服侧收到 34 条下载失败工单。

obsutil cp ./artifacts/20240805/ obs://team-portal-prod/ -r -f -u -j=8 -p=8
obsutil cp ./artifacts/20240809/ obs://team-portal-prod/ -r -f -u -j=8 -p=8
obsutil cp ./artifacts/20240812/ obs://team-portal-prod/ -r -f -u -j=8 -p=8

同一天我把版本控制打开,把生命周期规则改成只匹配 `history/`,规则 JSON 收进仓库。现在改这个文件必须提 PR,CI 用 ajv 校验 schema,字段错一个就合不进去,我也把控制台手改权限从团队角色里收掉了。

CRR 也踩了坑。主桶在 cn-north-4,灾备桶在 cn-east-3,9 月演练时灾备桶少 312 个对象,缺的全是复制规则生效前的老文件。我还试过把复制规则 disable 再 enable,盼着触发一次全量回填,14 分钟过去对象数没变化,只能手工跑 backfill,11 分钟结束,`backfill.log` 里 success 312、failed 0,额外产生 1.74 元跨区复制流量费。

obsutil ls obs://team-portal-prod -s
# Total size of bucket is: 8.7 GB
# Total number is: 1342

obsutil ls obs://team-portal-dr -s
# Total size of bucket is: 6.9 GB
# Total number is: 1030

obsutil cp obs://team-portal-prod/ obs://team-portal-dr/ -r -u -f -j=8 -p=8 -o=./backfill.log

发布脚本我改了三处,都是硬限制,不是口头“改了一下”。上传前统计文件数,小于 150 直接 `exit 12`,这个阈值按常态 187 留了容错;上传后把 `index.html` 强制成 `Cache-Control:no-cache`,避免入口页缓存老 hash;`assets/` 下 hash 文件统一 `max-age=31536000`,减少重复拉取。最近一个完整月账单 38.65 元,标准存储 1.04 元、请求 3.87 元、公网下行 32.00 元、跨区复制 1.74 元,对比 ECS 的 207.6 元,每月少 168.95 元,这个数字我和财务核过两次。

FILE_COUNT=$(find ./dist -type f | wc -l)
if [ $FILE_COUNT -lt 150 ]; then
  echo "dist file count abnormal: $FILE_COUNT"
  exit 12
fi
obsutil sync ./dist obs://team-portal-prod/ -delete=false -f -j=8 -p=8
obsutil setmeta obs://team-portal-prod/index.html -meta='Cache-Control:no-cache'
obsutil setmeta obs://team-portal-prod/assets/* -r -meta='Cache-Control:max-age=31536000'

现在还没解决的是 CRR 延迟可观测,我在演练里见过 8 到 15 分钟延迟,下一步准备做 5 分钟一次定时任务,拿主桶和灾备桶最新对象时间戳做差,推到 CES 自定义指标,告警阈值先设 600 秒。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。