OBS静态文档站上线后的返工记录
迁移当天和两次故障
9 月 3 日 20:14,我在华为云控制台把文档站从 ECS 手工发布切到 OBS。旧环境是 ECS s6.medium.2,2 vCPU、4 GiB,Ubuntu 22.04.3 LTS,系统盘 40GB ESSD,Nginx 1.24.0,ELB 日志里 2026-06 到 2026-08 平均 2.1 万请求每天,周三发版后峰值 3.4 万。流量没把机器压垮,折腾人的是发布链路,每次都要堡垒机跳转,rsync 上传后再 ssh 改软链,慢的时候 11 分钟 08 秒,快的时候 7 分钟 19 秒,我还得盯着 CES 的 404 探针,怕切换窗口里有人点进来。
rsync -avz --delete ./dist/ deploy@10.23.7.14:/data/docsite/releases/2026-09-03-2010/
ssh deploy@10.23.7.14 'ln -sfn /data/docsite/releases/2026-09-03-2010 /usr/share/nginx/html && nginx -s reload'
我一开始真不想迁。
账单其实不痛,8 月 ECS + EIP + 系统盘 219.6 元,真正让我下决心的是重复劳动一周要吞掉我 40 到 60 分钟,还容易因为路径写错返工。迁移那天我建了桶 doc-portal-prod,区域 cn-north-4,静态网站托管填 index.html 和 404.html。构建跑在 CodeArts Runner ubuntu-20.04,Node 18.19.1,pnpm 8.15.4,obsutil 5.5.12。流水线里我把命令从 obsutil sync 改成 obsutil cp -r -f -u -j=5 -parallel=5,原因是我想保留 release 历史目录,不让同步把旧版本删掉。Nginx 原先对 js/css 回的头是 Cache-Control: public,max-age=2592000,immutable,迁到 OBS 我在上传阶段改成 max-age=300,命令是给 dist/assets 统一写元数据,打算先把回滚风险压住。
obsutil cp -r ./dist obs://doc-portal-prod/ -f -u -j=5 -parallel=5
obsutil setmeta obs://doc-portal-prod/assets/* Cache-Control:public,max-age=300 -r -f
21:07 流水线显示 Success,日志里 Upload objects: 16384,Uploaded size: 406.7MB,我当时以为可以收工。21:11 业务同事在群里丢了根路径,浏览器直接 403,我用 curl 复现,连 index.html 都拿不到,返回体是 AccessDenied。这里是我自己判断错了,我把“开了静态网站托管”理解成“匿名可读”,实际上桶还是私有。
curl -i http://doc-portal-prod.obs-website.cn-north-4.myhuaweicloud.com/index.html
HTTP/1.1 403 Forbidden
Server: OBS
x-obs-request-id: 00000192B7F6B2A4D6C9A7F12E3B4C11
Content-Type: application/xml
<Error><Code>AccessDenied</Code><Message>Access denied</Message></Error>
我当晚先走了个偷懒方案,只给 index.html 加 ACL:obsutil chattri obs://doc-portal-prod/index.html -acl=public-read。首页能 200,但页面白屏,Chrome 控制台刷了 27 条资源 403,像 /assets/app.8f3a1c.js、/assets/vendor.2cd91a.css 全挂。我又试过对 assets/ 目录递归改 ACL,命令跑了 8 分钟 46 秒,结果下一次发版 hash 变了还是会漏,等于每次都要重复这套动作,我自己都嫌蠢。
改成桶策略才算走正路。第一次策略提交还写错了 Resource,我写成 obs:doc-portal-prod,OBS 控制台回 MalformedPolicy,报 line 8, column 18。后来改成 obs:doc-portal-prod/* 才保存成功;同时我加了 Deny 约束,PutObject 和 DeleteObject 只放行 CI 委托角色 *ci-deploy-role*。从 21:11 报障到 21:48 全量恢复,37 分钟里有 20 分钟都耗在策略语法来回试。
第二次翻车比第一次疼。9 月 10 日 18:26 我想做 release 目录过期清理,计划是删 14 天前对象,控制台里 Lifecycle Rule 的 Prefix 我漏填了,实际效果就是整桶生效。9 月 24 日 09:12,CES 健康检查连续 3 次 404,短信把我叫醒,日志里是 NoSuchKey。我一眼看过去还以为是 DNS 缓存或者 CDN 脏数据,结果进桶一看,index.html 和 assets 已经被生命周期任务硬删。
{'time':'2026-09-24T09:12:21+08:00','status':404,'path':'/index.html','code':'NoSuchKey','request_id':'00000192C021A5D947EE2A7B61D9AA02'}
这次我确实慌了。
我还做了个失败尝试,想着从删除标记恢复,跑 obsutil ls -versions obs://doc-portal-prod/,输出是 Total 0 Versions。那一刻才反应过来版本控制根本没开,生命周期删掉就是直接消失,这 12 分钟是纯空转。恢复动作只能从 CodeArts 前一晚 23:40 的构建产物重传,包 418MB,16392 个对象,耗时 16 分 34 秒,站点在 09:39 恢复可访问。
返工后的配置和还没做完的事
这次返工我把“改了一下”都写进流水线脚本,避免下次靠记忆。版本控制我用命令开:obsutil setbucketversioning obs://doc-portal-prod Enabled。生命周期我不再点控制台,改成 XML 文件走 MR 审核,再由 CI 执行 obsutil setlifecycle。为了卡住空 Prefix,我加了 pre-check,脚本里只做两件校验,文件里必须有 <Prefix> 且值不能是空字符串,任一不满足就 exit 1,发布分支直接失败。
<LifecycleConfiguration>
<Rule>
<ID>expire-release-14d</ID>
<Prefix>release/</Prefix>
<Status>Enabled</Status>
<Expiration><Days>14</Days></Expiration>
<NoncurrentVersionExpiration><NoncurrentDays>30</NoncurrentDays></NoncurrentVersionExpiration>
</Rule>
</LifecycleConfiguration>
9 月底我把 cn-north-4 复制到 cn-east-3,目标桶 doc-portal-dr。初次复制 16432 个对象,12.7GB,控制台显示 27 分钟完成。这里又踩坑,我第一次规则里少给委托账号 obs:ReplicateObject,任务状态一直 Failed,错误原文是 AccessDenied: Insufficient permissions for destination bucket。补完权限后重新触发才过。还有一个细节我当时没想到,跨区复制只带对象和元数据,静态网站托管配置不会跟过去,所以灾备桶我又手工填了一次 index.html 和 404.html,不填的话切换后根路径仍是 404。
费用我按月对过,8 月迁移前 219.6 元,9 月 OBS 总账单 32.51 元,明细是存储 1.09 元、请求 7.32 元、下行流量 17.30 元、跨区复制 6.80 元。钱省了,但两次故障人工时间并不轻,37 分钟 + 2 小时 14 分,后者基本吃掉我一个周六下午。
还没收尾的是 HTTPS 自定义域名。我现在在做 CDN 接入前压测,wrk 4.2.0 参数固定 -t4 -c200 -d60s,最近 5 轮回源命中率在 93.6% 到 95.1% 间波动,没有到我给自己定的 97%。流水线也还偏慢,每次全量上传 1.6 万对象,平均 4 分 50 秒,我准备拿 manifest.json 的 hash 做增量,先在预发桶跑 obsutil sync ./dist obs://doc-portal-pre/ -delete=false 观察一周,再决定要不要切到生产桶。
- 点赞
- 收藏
- 关注作者
评论(0)