☰
pip安装报错No space left on device?磁盘与inode排查详解
2026/10/11 22:19:52 网站建设 项目流程

今天帮一位同事排查安装报错时,又看到了那行红色大字:ERROR: Could not install packages due to an OSError: [Errno 28] No space left on device。这行提示我见过太多次,几乎每个用 Python 做开发的人迟早都会撞上,尤其是服务器上装依赖很多的大包时。用大白话说,就是磁盘空间不够了,但麻烦的地方在于它往往不是你以为的那块盘满了。

收到这条报错别急着找rm -rf,也别直接卸包重装。它真正想告诉你的,是安装过程中某个底层写入动作失败了。这篇文章我会把报错产生的环节、排查思路、常用解法,以及我踩过的一些坑一次性讲清楚,适合刚接触 Python 环境管理的开发者,也适合经常部署服务的运维和后台同学。

1. 一字一句拆解这条报错,No space left 里的门道

1.1 报错的完整链路并不只有最后一行

这条错误通常会出现在执行pip install的过程中,看起来只是一句话,但它背后对应的是一个完整的安装流程。pip 在安装一个包时,不是把文件直接复制进目录就结束了,它会经历下载、校验、解压、构建、安装几个阶段。这几个阶段里只要有一步需要向磁盘写入数据,而对应分区的剩余空间不够,文件系统就会返回一个ENOSPC错误。Python 捕获到之后,包装成OSError: [Errno 28] No space left on device抛出来,pip 再在最外层补一句Could not install packages due to an OSError。

所以你看到那条红色提示时,眼睛不要只盯在最后一行,应该往上翻日志,找到它具体卡在哪个动作上。如果是在Downloading阶段报错,往往和下载缓存目录有关;如果是在Building wheel阶段报错,大多和临时目录有关;如果已经走到Installing collected packages才报错,那多半是目标安装目录所在的磁盘满了。把日志看明白,后面的处理就顺手很多。

1.2 为什么一个几百兆的包能把空间“吃”掉几个G

很多人会有个困惑:包明明不大,我磁盘也还有几G空间,怎么就说不够了?这里有个常见的认知盲区:pip 安装时产生的临时数据,远不是最终包的大小。压缩格式的 wheel 文件需要先解压,解压后的源码目录可能比压缩包大好几倍;如果这个包还需要编译,编译器产生的中间目标文件又会占一份空间。同时 pip 会在缓存目录里留下原始下载文件,安装时还会在临时目录里再复制一份。几份数据叠在一起,占用自然成倍往上翻。

我处理过一台设备,根分区就剩 3G,看到一个包显示需要 800M,觉得没问题,结果一执行安装,pip 先下载了 800M 缓存,又解压出 1.5G 的构建目录,最后安装到 site-packages 还要 900M,瞬间把空间挤爆。这类问题,根源就是多个写入点都落在同一个小分区上,单纯看当前剩余多少空间无法预判。

2. 排查现场:先确定是哪个磁盘、哪种空间满了

2.1 用 df 判断是容量不够还是 inode 不够

遇到No space left on device,第一步永远是先看磁盘整体状况,而不是凭感觉猜。我会先执行:

df -hT

这个命令会列出所有文件系统的容量、已用、可用、挂载点和文件系统类型。重点看两个地方:当前工作目录所在分区,以及 pip 缓存和临时目录所在分区。有时候你发现根分区满了,但/home还有一大半空间,那问题就变得简单:想办法把写入路径指向/home即可。

但有一个非常容易踩的坑:很多人只看df -h,看到磁盘还有空间,就百思不得其解。这时候应该再看一眼 inode:

df -i

inode 是文件系统用来记录文件元数据的结构,每个文件或者目录都会占用一个 inode。如果数据块没满,但 inode 耗尽,系统同样无法创建新文件,同样会报No space left on device。这种情况常见于某个目录下堆积了大量小文件,比如日志碎片、编译临时文件。判断方法很简单:df -h还有剩余,df -i已经 100%,那就是 inode 耗尽。找出小文件特别多的目录清理即可,后面我会专门讲。

2.2 找到本次安装到底会把数据写到哪

定位到哪个盘重要,搞清楚会写进哪个目录更重要。pip 的写入路径主要有三条:

  • 下载缓存目录,负责存放下载好的包文件;
  • 临时目录,负责构建和打包;
  • site-packages 目录,负责放置最终安装文件。

分别用下面几个命令可以找到它们的位置:

pip cache dir python3 -c "import tempfile; print(tempfile.gettempdir())" python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])"

拿到这三条路径之后,对着df -hT的输出看一眼,基本就知道哪一块先撑不住。我在排查的时候习惯列一个表记录路径和对应分区,这样处理时心里有数。比如下载缓存默认在家里目录下,如果家目录在一号分区;临时目录默认在/tmp,可能对应二号分区;site-packages 在虚拟环境内部,对应三号分区。三条路径对应三个不同的“水池”,任何一个满了都会触发行错误。看清这个,后面的清理或改路径就有的放矢。

3. 清缓存清日志清旧包:比你想的更立竿见影

3.1 pip cache 一把梭:清缓存前先看清位置

如果报错时你发现根分区快满了,而空间占用大户往往就是 pip 的缓存目录。pip 的默认行为是缓存下载过的 wheel 包,这样下次安装同一个版本就不用再从网络下载。但缓存只进不出,时间一长很容易涨到几个G甚至十几个G。清理命令如下:

pip cache info pip cache remove "*"

先看缓存信息,再清空。如果你用的是比较老版本的 pip,没有cache子命令,那就直接到缓存目录手动清理。Linux 下通常在当前用户的~/.cache/pip,Windows 下在用户目录的AppData\Local\pip\cache。删掉整个目录也没关系,最多下次下载慢一点,不会影响已安装的包。

我个人的操作习惯是:先用du -sh ~/.cache/pip看一眼大小,有时能惊讶地发现缓存比全部环境占用的空间还大。曾经在一台服务器上,光 pip 缓存就占了 8G,清理之后根分区使用率从 96% 掉到 68%,比装什么优化工具都管用。顺便提醒一句:pip cache remove "*"会把所有缓存清掉,如果你只想去掉某几个包,可以用包名匹配,操作前看清楚参数。

3.2 日志、临时文件和旧虚拟环境,都是空间大户

除了 pip 缓存,系统里还藏着很多不起眼的空间消耗源。日志是最典型的,很多服务默认保留几个月甚至几年的日志。Linux 下可以检查journald日志,如果积压太多,可以直接清理最近一周之前的内容:

journalctl --vacuum-time=7d

包管理器也会留下历史缓存,比如运行清理命令来清除旧安装包和索引。这类命令的选项我一般建议先看帮助,但总体思路就是清掉已经下载过的旧安装包。

临时目录/tmp也是重灾区。一些程序异常退出后,临时文件不会自动清除。尤其 pip 在构建包时会在临时目录下创建一堆以pip-开头的临时目录,如果构建失败没被清理,会一直留在那里。可以这样查看并清理:

du -sh /tmp/pip-* 2>/dev/null rm -rf /tmp/pip-* 2>/dev/null

看到文件路径是你确认过的、无关紧要的临时残留,再执行删除,别一股脑把所有/tmp都清空,因为可能仍有正在运行的程序在占用。至于旧虚拟环境,我见过很多人项目迭代后旧的虚拟环境一直留在磁盘上,一个环境动辄好几个G,删掉不用的环境能释放不少空间。先看大小:

du -sh ~/.virtualenvs/* 2>/dev/null | sort -h

自己确认哪些环境已经不再需要,注意只删确定不用的目录。清理博客后,再重新执行安装命令,有很大概率直接问题解决。

4. 挪窝:把缓存、临时目录、安装目标一起搬到别的分区

4.1 改 TMPDIR,让解压临时文件落在空闲盘

如果清理之后空间仍然不够,或者你根本不想动系统里其他内容,那就换思路:不改乾坤,只给 pip 挪窝。首先要考虑的是临时目录。pip 在构建 wheel 时,会通过 Python 的tempfile模块寻找临时目录,默认是/tmp。如果/tmp所在分区太小,我们就把它指向大分区。

mkdir -p /data/tmp export TMPDIR=/data/tmp pip install some-package

这里/data可以换成你当前设备上空间充足的那个挂载点。设置环境变量之后,pip 和其他很多构建工具都会遵守这个路径。注意,需要在执行 pip 的同一个 shell 里设置环境变量,否则不会生效。为了稳妥,我习惯在安装时同时清理缓存并设置新临时目录:

mkdir -p /data/tmp TMPDIR=/data/tmp pip install --no-cache-dir 包名

--no-cache-dir可以防止缓存再写进根分区,TMPDIR则把解压和构建过程挪到大分区,两条一起用,临时盘和缓存盘的压力同时解除。

4.2 改 cache-dir,让下载缓存不再占满系统盘

缓存目录的位置同样可以永久设置。pip 支持通过配置文件设定缓存目录,Linux 下全局配置通常在/etc/pip.conf,用户级配置在~/.config/pip/pip.conf。你也可以通过命令行直接设置当前用户的配置:

pip config set global.cache-dir /data/pip-cache

执行完之后,再安装包时下载产物就会写到新位置。和临时目录不同,这个配置是持久的,重启终端也不会丢失。我建议在服务器上第一次部署 Python 环境时就设置好这个项,避免投产之后缓存不断膨胀把系统盘打满。需要注意:旧缓存目录里的文件不会自动迁移,如果你之前的缓存已经占了很多空间,改完配置之后最好顺手把旧目录清掉。

4.3 应急方案:--target 和 --user 怎么选

有时候你不想动系统分区,但又要装一个很大的包。这时可以把安装目标指定到别的磁盘。--target参数允许你把包安装到一个任意目录,然后通过PYTHONPATH导入:

pip install --target /data/pylib 包名 export PYTHONPATH=/data/pylib:$PYTHONPATH

这个方案非常灵活,适合临时测试或者强制绕过空间限制。但要注意,用--target安装的包不会走常规的卸载流程,也没有生成入口脚本,依赖关系需要你自己维护,长期使用容易乱。所以我通常在应急场景才用它。

另一个思路是装到当前用户目录,使用--user参数:

pip install --user 包名

这类包会安装到用户主目录下的.local/lib/python3.x/site-packages。如果你的家目录所在分区比较大,这条路很省事。但如果你当前已经在一个虚拟环境里,--user的语义会有变化,作用并不明显;如果你在家目录分区也很小,那它帮不上忙。选择哪个,取决于你已经定位到的各分区剩余情况。

5. 这几个坑让我被坑过,见到同类报错先按这条思路查

5.1 不是根分区满,而是 /tmp 挂载的 tmpfs 小

有次我在一台内存很大的设备上装依赖,根分区明明还剩 20G,但安装大型框架仍然报No space left。查了半天才发现/tmp并不是磁盘上的目录,而是挂载的tmpfs。tmpfs使用内存作为存储,容量默认只有物理内存的一半左右。当时那台设备的内存看起来很大,但可用内存的余量已经很紧张,临时目录能写入的空间少得可怜。

遇到这种情况,第一反应不是清理根分区,而是确认df -hT里/tmp的文件系统类型。如果显示tmpfs,那你需要定制一下,或者干脆把临时目录指向磁盘上的一个目录。方式是修改挂载参数,或者在安装时用前面提到的TMPDIR环境变量。我要强调一句:报错信息不会告诉你是哪块空间有问题,你自己排查定位是唯一可靠的方法。

5.2 不是 block 不够,而是 inode 用完了

还有一个迷惑性更强的场景。磁盘明明显示剩余好几个G,df -i却显示 inode 已经 100%。报错虽然也叫No space left on device,但真正原因是文件数量超过上限。这种场景我见过好几次,都是因为某个目录下生成了大量极小文件,比如某个测试脚本在/tmp下循环创建文件但没按时删除,或者日志系统按秒切分文件。

排查命令很简单:

df -hT df -i

如果第二个命令提示已经耗尽,接下来用:

find /tmp -xdev -type f | wc -l

统计某个分区的小文件数量,找到数量离谱的目录再清理。别在 inode 耗尽时还往同一个分区写大文件,那是反向操作。清掉一批垃圾文件之后,inode 腾出来,问题自然消失。因为 inode 是预分配的,即使删了文件它也不会自动增加,所以一开始设置分区时,如果预期会放很多小文件,就要考虑适当调大 inode 数量。

5.3 容器环境下“磁盘满”另有说法

在容器环境里运行时,No space left on device还有一层额外含义:它可能不是物理磁盘满了,而是容器的可写层满了。容器默认的写入层大小有限,如果你在容器里安装包、生成日志,写满了这一层,同样会报这个错误。这时你在宿主机上查看磁盘使用率可能一切正常,因为限制的是容器层大小而不是宿主机磁盘。

一般处理方法是:清理容器内部不需要的缓存和日志,或者给容器存储重新分配更大的空间,也可以把数据卷挂载到宿主机。关键点是别把容器当成一台正常的服务器来排查,先确认自己所在的运行环境,再选方向。我在一套自动化构建系统里就遇到过镜像反复构建,每一层都留下大体积文件,最终把容器分区撑满的情况,最后清掉多余镜像和构建缓存才恢复。

6. 预防方案:能自动化就自动化,别等磁盘爆了才动手

6.1 写一个清理脚本,定时跑起来

这类问题的根本原因是“只进不出”。与其每次都等人报错再上去救火,不如提前部署一个清理脚本。我在维护的设备上会放一个简单脚本,定期执行三个动作:清 pip 缓存、清理 journal 日志、清理过期临时目录。脚本内容并不复杂,核心代码如下:

#!/bin/bash # 清理 pip 缓存 if command -v pip &>/dev/null; then pip cache purge 2>/dev/null || true fi # 清理超过 7 天的 journal 日志 journalctl --vacuum-time=7d 2>/dev/null || true # 清理 /tmp 下超过 3 天未修改的 pip 临时目录 find /tmp -maxdepth 1 -name "pip-*" -mtime +3 -exec rm -rf {} \; 2>/dev/null || true

通过计划任务每天运行一次,磁盘使用率长期保持在健康水位。写脚本的时候有一个很重要的细节:不要直接清理所有临时文件,只清理自己认识的那一类,否则很可能误删正在被使用的数据。脚本上线前先手动跑一遍,查看输出,确认不会误删再交给计划任务。

6.2 环境初始化时就把“大件”放到独立分区

比定时清理更稳的做法,是在环境初始化阶段就把缓存和临时目录指向大分区。用 pip 时,我在新环境里第一件要做的事就是设置cache-dir指向数据盘,同时把TMPDIR固定下来。如果使用虚拟环境,我会在项目说明里标注清楚“安装前请确保分区剩余空间不低于若干G”。与其事后手忙脚乱,不如一开始让写入路径落在规划好的位置。

容器和 CI 环境同样如此。构建镜像时如果不需要缓存,尽量用不缓存的方式构建;流水线里定期清理历史构建产物和未使用的中间层。我习惯在 CI 脚本里加一个清理步骤,每次构建结束之后清理该轮产生的临时文件,防止“节省一时”变成“日后爆盘”。

清理脚本和定期监控配合起来能省很多事。你也可以用系统自带的状态检查工具,自己在本地写几行代码检测分区使用率,超过阈值就输出告警。比如随手写一个判断:

usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$usage" -gt 80 ]; then echo "根分区使用率超过 80%" fi

这个可以做成计划任务,一旦超阈值就把提醒发出来。几十行代码的投资,能避免大半夜被“安装失败”的告警吵醒。

最后分享一点个人经验

处理这个报错多了,我最大的体会是:遇到No space left on device,第一反应不要是找“删除大文件命令大全”,而是先回答清楚三个问题——哪个分区满了、是容量还是 inode 满了、写入路径为什么会落在那里。把这三点摸清楚,再动手清理或者调整路径,基本不会再翻车。

另外,日常操作时留个心眼:任何删文件的操作都先du -sh确认目录大小,任何清理命令都先在少量数据上验证。真出问题的时候,宁可多花五分钟看日志,也不要凭经验盲目删。毕竟,空间不足往往只是表象,看清底层的写入机制,这个问题其实一点也不难。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询