部署包从 65M 涨到 4.1G:一次被安装包拖垮的部署排障
部署 tar 悄悄从 65MB 涨到 4.1GB,磁盘被撑满 100%,上传中断留下半截 standalone,服务 502。根因是历史安装包在 public 目录无限累积被塞进部署包。记录恢复过程与部署脚本改进。
部署包从 65M 涨到 4.1G:一次被安装包拖垮的部署排障
一个再平常不过的部署动作,最后变成了一场"磁盘写满 → 解压中断 → 服务恢复 → 重构部署脚本"的多轮拉锯。根因却简单得让人想拍桌子:历史安装包在 public 目录里无限累积,每次部署都把它们原样塞进 tar 包。
现象
部署脚本跑起来,然后……卡住了。20 分钟超时被杀。第二次再跑,服务器端报:
gzip: stdin: unexpected end of file
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now
解压到一半失败,standalone/ 成了半成品。更要命的是服务还"在线"——PM2 显示 online,curl localhost:3000 返回 200,但访问任何页面都 502。因为进程内存里跑的是旧代码,磁盘上的文件树却已经被拆了一半。
第一步:磁盘满了,为什么?
/dev/vda3 40G 38G 0 100% /
/ 直接满到 0 可用。查空间才发现元凶:
| 位置 | 占用 |
|---|---|
/tmp/eztor-deploy-*.tar.gz |
4.1G(单个部署包!) |
standalone.bak.* 备份 |
2.3G × 3 |
当前 standalone |
4.1G |
问题不在服务器,在本地。public/downloads/ 从项目诞生起就一直在累积安装包:dmg 每个 115MB、zip 每个 110MB,几个版本下来 2.5G。public/updates/ 再叠 1.5G。部署脚本 cp -r public .next/standalone/public 一股脑全打进了 tar → 4.1G。
之前一直是 MB 级,是因为早期安装包少。后来每次发版都在堆,我居然一直没意识到 tar 已经在悄悄变大——直到这次 40G 磁盘被它撑爆。
第二步:上传中断,留下半截 standalone
磁盘满 + 4.1G 的 tar 上传本来就慢,SSH 长连接一断,服务器上的解压脚本跟着死掉。于是:
standalone/只有部分文件(server.js都不存在)- 部署脚本里的
.env拷贝步骤根本没跑到 →standalone/.env缺失 - PM2 里那个进程还在跑旧的内存代码,看起来"正常"
教训第一条:pm2 list 显示 online ≠ 服务真的可用。curl localhost:3000 返回 200 也可能只是进程内存里残存的旧代码。判断部署是否真的完成,要看 BUILD_ID.txt 是否等于本次部署的、pm2 uptime 是否够新、standalone/server.js 是否存在。
第三步:最快恢复在线
好在部署脚本每次都会自动备份当前版本到 standalone.bak.<timestamp>。这是一个完整的、能跑的上一个版本。恢复它:
cd /www/wwwroot/site/.next
mv standalone standalone.broken
mv standalone.bak.<最新> standalone
pm2 restart cet4-web --update-env
30 秒内服务回到可用状态。教训第二条:standalone.bak.* 是应急回滚的保命符,磁盘紧张时也别全删,至少留最新一个。
第四步:根因修复——安装包不该进 tar
cp -r public .next/standalone/public
rm -rf .next/standalone/public/downloads .next/standalone/public/updates
安装包本来就通过独立 scp 分发,服务器端部署时还会从 standalone.bak 恢复 + 只保留最新版本。把它们打进 web 部署 tar 纯属浪费——4.1G 里 4G 都是死重。
顺手把本地也清了一轮,public/downloads 从 2.5G → 500M(只留当前版本 + 0.3.0 兜底),public/updates 从 1.5G → 290M。
修复后 tar 从 4.1G → 70M,部署从"必超时"变成"几十秒传完"。
经验清单
- 监控部署包体积。如果 tar 突然从 MB 变 GB,八成是静态资源/安装包累积,别硬传。
- PM2 online ≠ 部署成功。看 BUILD_ID、看 uptime、看
server.js是否真在磁盘上。 - 备份是回滚的生命线。
standalone.bak.*一定要留最新一个,恢复就是mv+pm2 restart。 - 大文件不要进 tar。能独立分发、能从备份恢复的东西,就不该进部署包。
- 部署用 nohup 后台跑,本地 SSH 超时不该杀死远程的部署脚本——虽然这次它还是被杀了,但至少不会因为终端断开而中断。
一次部署排障,从磁盘爆满查到本地目录卫生,最后落在部署脚本的一行 rm -rf 上。教训:定期清理,别等磁盘替你做决定。