LHIDC

Next.js网站构建失败并提示磁盘不足,如何检查容量、inode与日志增长

面向维护Next.js网站的站长和技术负责人,介绍构建出现ENOSPC时如何依次检查文件系统容量、inode、日志增长、配额及I/O状态,并说明安全清理原则与修复后的构建验证方法。

Next.js网站构建失败并提示磁盘不足,如何检查容量、inode与日志增长

Next.js网站执行 npm run buildpnpm build 或 CI/CD 发布任务时,如果出现 ENOSPC: no space left on device,不应只根据“磁盘不足”四个字直接删除文件。Linux 中的 ENOSPC 既可能表示磁盘数据块已满,也可能是 inode 耗尽;如果错误发生在开发模式或文件监听阶段,还可能是 inotify 监听资源达到上限。除此之外,用户配额、容器存储层、只读文件系统和持续增长的日志也会产生相似现象。

建议先检查构建目录、/tmp 和运行用户主目录所在文件系统,再依次排查容量、inode、日志、已删除但未释放的文件以及文件系统和 I/O 状态。确认根因前,不要直接清空整个 .nextnode_modules、Docker 数据目录或系统日志,以免扩大故障范围。

先确认错误发生在哪个文件系统

Next.js构建并非只向项目目录写入数据。根据包管理器、部署方式和项目配置,构建过程还可能使用以下位置:

  • 项目中的 .next,或 next.config.jsnext.config.mjs 中自定义的 distDir
  • /tmp 或环境变量 TMPDIR 指定的临时目录
  • 当前用户的 npm、pnpm 或 Yarn 缓存目录
  • CI Runner 的工作目录和缓存目录
  • Docker 容器可写层或挂载卷
  • PM2、systemd、Docker及应用自身的日志目录

以下命令适用于常见Linux环境。dfdu 参数以 GNU coreutils 为例;Alpine、BusyBox或精简容器中的选项可能不同,应先执行 cat /etc/os-release 和命令帮助确认。

cat /etc/os-release
pwd
id
node -v
printf 'TMPDIR=%s\n' "${TMPDIR:-/tmp}"

df -hT "$PWD" "${TMPDIR:-/tmp}" "$HOME"
df -i "$PWD" "${TMPDIR:-/tmp}" "$HOME"
findmnt -T "$PWD"

重点不是只看根分区,而是确认实际写入路径对应哪个挂载点。例如,项目位于独立数据盘,但 /tmp 仍在根分区,那么根分区已满时,项目盘还有空间也无法完成构建。

常见报错可以按以下方式区分:

报错或现象 优先检查方向 判断依据
ENOSPC: no space left on device 容量、inode、临时目录 同时检查 df -hdf -i
EDQUOT: disk quota exceeded 用户或项目配额 文件系统仍有空间,但当前用户不能继续写入
EROFS: read-only file system 文件系统被只读挂载 检查挂载参数和内核日志
EIO: input/output error 存储设备或文件系统异常 检查内核日志与I/O状态
System limit for number of file watchers reached inotify限制 通常发生在开发监听阶段,不等同于磁盘已满
构建长时间停顿、超时但无容量报错 I/O延迟、内存压力或进程阻塞 结合 iostat、系统日志和构建输出判断

第一层:检查磁盘容量是否真的用尽

先查看目标文件系统的总量、已用量和可用量:

df -hT "$PWD"
df -hT "${TMPDIR:-/tmp}"
df -hT "$HOME"

如果项目所在分区的可用空间很少,需要进一步找出空间由哪些目录占用。建议先从项目和日志目录开始,不要一上来扫描整个根目录。du 扫描大量文件会产生额外I/O,业务繁忙时应谨慎执行。

du -xhd1 "$PWD" 2>/dev/null | sort -h
sudo du -xhd1 /var/log 2>/dev/null | sort -h

其中,-x 表示不跨越其他文件系统,能够避免扫描挂载在子目录中的其他磁盘。若项目位于 /srv/example,还可以继续逐层缩小范围:

du -xhd1 /srv/example 2>/dev/null | sort -h
du -xhd1 /srv/example/.next 2>/dev/null | sort -h

需要重点关注:

  • .next/cache.next/server 或自定义构建输出目录
  • 多个未清理的历史发布目录
  • CI工作区中的旧构建产物
  • npm、pnpm或Yarn缓存
  • 应用日志、访问日志和错误日志
  • 容器镜像、可写层以及未使用的构建缓存

df很满,但du找不到大文件

如果 df 显示空间已被占用,而各目录的 du 统计明显较小,常见原因是文件已经被删除,但进程仍然保持文件句柄。日志文件最容易出现这种情况:文件名已经不存在,空间却要等进程关闭句柄后才会释放。

安装了 lsof 时可以检查:

sudo lsof +L1

输出中应重点查看 SIZE/OFF、进程名、PID和原文件路径。如果确认是某个日志文件被删除后仍由服务占用,应通过对应的服务管理器执行日志重开、平滑重载或维护窗口内重启。

不要看到PID后直接使用 kill -9。强制终止可能中断正在处理的请求,也可能损坏尚未完成的写入。systemd、PM2和Docker的处理方式不同,应先确认实际运行方式:

ps -ef | grep -E 'next|node|pm2' | grep -v grep
systemctl list-units --type=service | grep -Ei 'next|node'
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'

以上命令只用于识别服务,不要同时对多个管理器执行重启。

第二层:容量还有剩余,但inode可能已经耗尽

inode用于记录文件和目录的元数据。一个分区即使还有较多数据容量,只要可用inode为零,也无法创建新文件。Next.js、包管理器和前端依赖通常包含大量小文件,因此旧版 node_modules、多份发布目录和构建缓存容易消耗inode。

使用以下命令检查:

df -i "$PWD"
df -i "${TMPDIR:-/tmp}"
df -i "$HOME"

如果 IUse% 已接近耗尽,或者 IFree 为零,就需要找出小文件最集中的目录。GNU du 支持按inode数量统计:

du --inodes -x -d1 "$PWD" 2>/dev/null | sort -n

也可以针对发布目录、缓存目录逐层检查:

du --inodes -x -d1 /srv/example 2>/dev/null | sort -n
du --inodes -x -d1 "$HOME/.npm" 2>/dev/null | sort -n

inode不足时,单纯删除一个体积很大的日志文件未必有效,因为它只释放一个inode。更有效的处理对象通常是:

  • 已下线版本中的 node_modules
  • 无需保留的CI工作区
  • 大量小型缓存文件
  • 过期会话文件或临时文件
  • 重复保存的历史发布目录

清理前应核对目录是否属于当前生产版本。采用软链接切换发布目录时,可以先查看链接目标:

readlink -f /srv/example/current
ls -ld /srv/example/current

当前版本及其回滚版本不应直接删除。对于其他历史版本,应先确认不被进程、定时任务或回滚机制引用,再根据既定保留策略处理。

第三层:定位持续增长的日志

如果释放空间后很快再次出现磁盘不足,问题通常不是“一次性文件太大”,而是某类日志持续写入。需要查明日志产生者和增长速度,而不是反复手动删除。

查找系统日志中的大文件

以下命令使用 GNU find,扫描 /var/log 时可能产生I/O开销:

sudo find /var/log -xdev -type f -printf '%s %p\n' 2>/dev/null \
  | sort -nr \
  | head -n 30

常见位置包括:

  • /var/log/nginx/
  • /var/log/syslog/var/log/messages
  • /var/log/journal/
  • 应用自行配置的日志目录
  • PM2的 ~/.pm2/logs/
  • Docker默认JSON日志文件

检查systemd journal

使用systemd的系统可以查看journal占用量:

journalctl --disk-usage

如果Next.js网站由systemd服务管理,可先确认服务名,再查看近期日志。下面的服务名仅为变量示例,应替换为实际名称:

SERVICE=nextjs.service
journalctl -u "$SERVICE" --since "24 hours ago" --no-pager | tail -n 200

如果同一条异常每秒重复出现,应优先修复应用错误或健康检查配置。仅限制日志大小虽然能缓解磁盘压力,却不会消除错误源。

需要回收journal历史空间时,应先确认审计和故障留存要求,并将必要日志备份到其他文件系统。以下命令会删除超过保留时间的归档日志,不能作为未经确认的默认操作:

sudo journalctl --vacuum-time=14d

保留时间应按实际运维要求调整,执行后再次使用 journalctl --disk-usage 核对。

检查PM2日志

如果使用PM2运行Next.js,可先查看日志路径和近期输出:

pm2 list
pm2 logs --lines 100
du -h -d1 "$HOME/.pm2/logs" 2>/dev/null | sort -h

PM2日志增长通常与未配置轮转、应用反复重启或异常堆栈循环输出有关。不要在未备份的情况下直接执行清空全部日志的操作,应先确认故障证据是否已经保存,并配置与当前PM2环境兼容的轮转方案。

检查Docker日志与存储占用

容器内看到的磁盘空间和宿主机实际存储层可能不同,因此要分别检查容器和宿主机:

docker system df -v
docker ps --size

查看某个容器实际使用的日志文件:

CONTAINER=nextjs-app
docker inspect --format='{{.HostConfig.LogConfig.Type}}' "$CONTAINER"
docker inspect --format='{{.LogPath}}' "$CONTAINER"

不要直接删除 /var/lib/docker 下的文件,也不要在生产环境中未经筛选执行带 -a--volumes 的清理命令。此类操作可能删除仍需回滚的镜像、构建缓存或持久化数据。更稳妥的方式是先识别占用来源,再配置Docker日志轮转,并评估重启Docker服务对现有容器的影响。

第四层:检查配额、只读挂载和I/O异常

用户配额导致“有空间却写不进去”

df -hdf -i 都正常,但构建日志出现 EDQUOT,应检查执行构建的用户是否受到磁盘配额限制:

quota -s
id

配额工具和配置方式取决于文件系统及系统环境。XFS项目配额、容器存储配额和托管平台工作区限制不能仅依靠普通 quota 命令判断,需要由具备权限的管理员检查挂载参数和配额策略。

文件系统变成只读

先确认项目所在挂载点及其选项:

findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS -T "$PWD"

如果选项中出现 ro,或者构建报 EROFS,继续检查内核日志:

sudo journalctl -k -b --no-pager \
  | grep -Ei 'I/O error|read-only|EXT4-fs error|XFS.*error|buffer I/O'

某些系统限制普通用户读取内核日志,此时需要管理员权限。若日志中出现文件系统错误、设备重置或I/O错误,不要直接强制重新挂载为可写,也不要对已挂载文件系统运行 fsck。应先停止非必要写入、备份关键数据,再安排离线检查或存储层故障处理。

容量正常但构建异常缓慢

构建会读取大量依赖并生成许多小文件,磁盘高延迟可能使任务超时,即使空间没有耗尽。系统已安装 sysstat 时,可以观察:

iostat -xz 1 5

重点结合以下字段判断:

  • await:请求完成等待时间是否持续升高
  • aqu-sz:设备队列是否持续堆积
  • %util:设备是否长时间繁忙
  • 读写吞吐和请求数量是否与构建时间吻合

这些指标不能脱离设备类型和业务负载使用固定阈值判断。如果只有并发构建时队列明显上升,可以减少并行构建数量、错开备份任务或将构建工作区与高写入日志分离。如果同时出现内核I/O错误,则应优先处理存储或文件系统问题,而不是反复重跑构建。

清理Next.js构建目录时要区分“缓存”和“当前产物”

默认情况下,Next.js将构建结果写入 .next。其中不仅有可再生成的缓存,还可能包含当前运行实例需要的服务端文件、静态资源和增量缓存。直接删除整个 .next 可能导致正在运行的网站返回错误。

先检查项目是否修改了输出目录:

grep -R "distDir" next.config.js next.config.mjs next.config.ts 2>/dev/null
du -xhd1 .next 2>/dev/null | sort -h

处理原则如下:

  • CI临时工作区中的 .next 通常可在确认任务已结束后重建。
  • 当前生产版本的 .next 不应在服务运行期间直接删除。
  • .next/cache 通常可以重新生成,但删除后会失去构建缓存,下一次构建可能更慢;运行时缓存行为还与Next.js版本和部署方式有关。
  • node_modules 不是简单的临时目录。删除后必须能够依赖锁文件重新安装,并确认私有包源和网络访问正常。
  • 历史发布目录应按版本保留策略清理,至少保留确认可用的回滚版本。

如果必须清理CI工作区,先校验变量和当前路径,避免路径为空或指向生产目录。以下示例只适用于已经确认可丢弃的CI工作区,rm -rf不可直接套用到在线目录:

WORKSPACE=/srv/ci-workspaces/example

cd "$WORKSPACE" || exit 1
test "$PWD" = "$WORKSPACE" || exit 1
printf '即将检查的目录:%s\n' "$PWD"
du -sh .next 2>/dev/null

确认目录无误、构建进程已经停止且不需要保留现场后,再按内部清理流程删除对应缓存或重建整个工作区。生产服务器更适合使用“新目录构建、验证后切换”的发布方式,避免构建和在线服务共同修改同一份 .next

修复后如何确认Next.js网站已经恢复

释放空间或修复文件系统后,不能只看首页能够访问。应重新验证构建路径、inode、日志和退出状态。

先检查相关文件系统:

df -hT "$PWD" "${TMPDIR:-/tmp}" "$HOME"
df -i "$PWD" "${TMPDIR:-/tmp}" "$HOME"

然后使用实际包管理器重新执行构建。例如项目使用npm时:

npm run build

使用pnpm或Yarn的项目,应以锁文件和现有部署流程为准,不要临时更换包管理器。构建成功至少应满足:

  • 命令退出状态为0
  • 不再出现 ENOSPCEDQUOTEROFSEIO
  • 配置的构建输出目录已生成
  • 构建过程中目标分区和inode没有再次接近耗尽
  • 应用启动后静态资源、服务端渲染和API路由可以正常访问
  • systemd、PM2或容器日志中没有持续重复的新异常

可以在另一个终端观察构建期间的容量变化:

watch -n 2 'df -hT . /tmp "$HOME"; echo; df -i . /tmp "$HOME"'

watch 并非所有精简系统都默认提供。如果系统没有该命令,可以在构建前后分别记录 df 输出进行比较。不要只按当前使用率设置告警,还应记录一次完整构建新增了多少数据块和inode,并为日志轮转、临时文件及回滚版本预留空间。

后续监控至少应覆盖项目分区、/tmp、用户主目录、inode使用量、日志增长速度、Docker存储占用和磁盘I/O延迟。若空间释放后仍快速下降,应先找到持续写入的进程;若容量与inode均正常但仍报 ENOSPC,再核对错误是否明确指向文件监听限制:

sysctl fs.inotify.max_user_watches
sysctl fs.inotify.max_user_instances

inotify参数调整会影响系统资源占用,不应仅为绕过报错而盲目增大。只有确认问题发生在开发监听或文件监控阶段,并排除容量、inode和配额后,才应根据实际进程数量评估调整。

上一篇 PHP-FPM是进程管理器还是PHP解释器:作用范围与判断边界解析

LHIDC 产品中心

继续查看可购买的海外服务器产品

文章用于辅助选型,最终价格、库存与配置请以产品详情页和下单页面展示为准。

查看产品 查看方案