服务器入坑指北:容器的层占用与清理边界

举报
Tulpartner 发表于 2026/11/09 09:18:12 2026/11/09
【摘要】 镜像删了,磁盘占用却没降;同一份基础层被反复下载;想清理,又不敢下手——怕把正在跑的容器一起删掉。 顺序是先分清"被引用的层"与"无引用的层",再清理:列出谁在引用 → 判断哪些层已无引用 → 按"先停用、后清理"动手。顺序反了,要么误删在跑的东西,要么白清一场。

镜像删了,磁盘占用却没降;同一份基础层被反复下载;想清理,又不敢下手——怕把正在跑的容器一起删掉。
顺序是先分清"被引用的层"与"无引用的层",再清理:列出谁在引用 → 判断哪些层已无引用 → 按"先停用、后清理"动手。顺序反了,要么误删在跑的东西,要么白清一场。
这篇落在内核层与内容寻址规范层(统一写"运行时"),不点名任何容器产品;给三段可直接粘贴的内容:看清层构成 → 判引用 → 清理与保留。取值用占位符。

层是怎么堆起来的:overlay 与四个字段

容器镜像是由"层"堆起来的,运行时把若干只读层与一个可写层叠加成一个视图——这套叠加由 overlay(overlayfs)完成。一个挂载项由四个字段组成:

  • lowerdir=:只读下层,可以多个、以冒号分隔;
  • upperdir=:可写上层;
  • workdir=:工作目录,必须与上层在同一个文件系统上;
  • 再加一个挂载点。于是"只读层"与"可写层"的关系就写在挂载项里。
    这一层还有两个必须记住的语义(下面的 read-only 层与可写层就是靠这几个字段拼出来的):
  • whiteout(白障):在下层删除一个文件时,运行时不是去改下层(它只读),而是在上层放一个白障把它遮掉——"删了"在层里是一个占位标记,不等于空间立刻回来;
  • copy_up(写时复制):修改下层的文件时,先把它复制到上层,因此同一份内容会同时存在于上下两层——这是"同一份内容多占一份"的机制原因。
    判据:看清一个挂载项的四个字段,就知道这一层的占用由谁构成。

看清层构成块

findmnt --target <路径>                    # 现读该路径落在哪个挂载项上(含叠加选项)
cat /proc/self/mountinfo                   # 现读挂载表:同一信息的内核视角
du -s --block-size=1 <路径>                # 现读占用(统一口径)
du --apparent-size <路径>                  # 表观大小:与实际占用是两个口径
df -h                                      # 文件系统可用量:清理前后同口径对比

在拿这两个数下结论之前,先把口径差看清:du 统计的是目录树里文件占用的块,df 统计的是文件系统的可用量;两者的差还包括已删除但仍被进程持有的文件。所以 du 与 df 对不上是常态,别直接把差额算成"都被容器吃了"(空间与 inode 耗尽的分流见《磁盘还有空间却写不进去》那篇)。
判据:du -s --block-size=1 <路径> 与 du --apparent-size 的差值说明实际占用与表观大小的偏离;df -h 用来看清理后可用量有没有真的变化;若怀疑是 inode 而不是空间,用同族的 df -i 再看一眼。
另外几个只读入口可以互相印证:mountinfo(也就是 /proc/self/mountinfo)与 /proc/mounts 是内核视角的挂载表,mount 也能列出挂载项——三者应当互相一致。

判引用块:看"层被谁引用"

在清之前,先把这条前提看清:要看的是层被谁引用,不是镜像还在不在列表里。删除一个镜像不等于删掉它用过的所有层——旧的构建层可能不再被任何镜像引用,却仍然占着磁盘。
两边的只读入口:

cat manifest.json                          # 清单:这次由哪些层组成(以摘要引用)
cat index.json                             # 索引:标签指向哪个清单(标签只是名字)
ls blobs/                                  # 层内容按摘要存放的地方(清单引用的摘要对应的是存放的层内容)

判据:读清单看"这一层被谁引用"——sha256: 形态的摘要才是内容身份,layer 是层的语义单位;这套布局描述的是"镜像由哪些层组成",它不是通用的引用视图(换一种存放形态就不一定有这几个文件)。另外注意:清单里引用的摘要是对存放的层内容计算的,所以别拿"把文件重新算一遍摘要去对上清单"当判据——真正的引用关系看运行时侧的只读视图(按"有哪些容器在用这个镜像"筛选),清理前必须用它确认"没有在跑的实例依赖它"。

动手之前,先把两条看清

别忘了 挂载命名空间:容器里的挂载视图与宿主机不在同一个命名空间,在宿主上看到的那一堆叠加挂载项,才是你付钱的那部分占用(跑在 guest 里也同样算你的)。
还有一条以现读为准的前提:运行时对无引用层的回收(分层回收,GC)时机各不相同,别假设它会在固定时刻自动发生;而层内容在存储侧是以 blob 为单位按摘要存放的,同一个 blob 被多处引用时,清掉一处并不会让占用立刻下降。所以"清完占用没变"未必是没清掉,可能只是还有别的引用。

清理与保留块

清理与卸载属高危写操作,动手前先确认没有实例依赖。

umount <挂载点>                            # 卸载一个层叠加(高风险写操作,需回滚点;先确认无实例依赖)
du -s --block-size=1 <路径>                # 读回:占用确实下降了吗
df -h                                      # 读回:可用量的变化与口径差能否解释

保留策略的写法:先写下"要能回滚几代",再清。

  • 按剩余空间清理的后果很具体:下次要回滚时,那一代已经被删了;
  • 按 回滚代际清理是用空间换"能回去";
  • 判据:要回滚时那一代还在。这里不给代数——它由你的回滚需求定。
    判断"谁还在用"靠的是 引用计数:一个层被几个镜像或实例引用,决定了它能不能被清。摘要是内容身份、标签只是名字——能不能回收,看的是前者而不是后者。

现象到动作的判定表

现象 结论 动作
镜像删了磁盘没降 它用过的层还被引用,或已成为无引用层 用只读引用视图看"层被谁引用",再决定清哪一层
同一基础层被反复下载 引用没固定到内容身份,或本地层没被保留下来 用 sha256: 摘要核对"是不是同一份内容",并把引用方式固定下来
不敢清、怕删掉在跑的 缺"谁在引用"这一路证据 先按引用视图确认无实例依赖,再清
该保留几代 取决于回滚需求 写下"要能回滚几代",只清超出代际的
du 与 df 对不上 口径差(含已删除但被持有的文件) 用同口径前后对比,别把差额全算给容器

改完怎么验证:每项一条判据

  • du -s --block-size=1 <路径> 的占用确实下降;
  • 没有实例依赖被误删(在跑的仍正常);
  • 保留代际与写下的策略一致;
  • df -h 的可用量变化与 du 的口径差可解释;
  • 有回滚形态(卸载与清理前的原状态有记录)。

这篇不解决什么

  • 讲部署前先固化产物的那篇是构建产物的摘要与版本对应;本篇是运行时的层与清理——两篇都用"摘要才是身份",但对象与动作不同。
  • 讲磁盘还有空间却写不进去的那篇是空间与 inode 耗尽的分流;本篇是容器层占用的清理,是它的一个具体来源。
  • 讲新文件为什么别人改不了的那篇是权限与继承;本篇不涉及权限——若清理失败,先排除权限与视图。

踩坑清单

  • 拿"镜像还在不在列表"当判据:要看的层被谁引用。
  • 直接清而不先看在跑的实例:把依赖它的东西一起删掉。
  • 按剩余空间清理:下次回滚时那一代没了。
  • 把引用挂在会变的标签上:说不清"这份内容对应哪个摘要"。
  • 把标签当内容身份:标签会重新指向别的摘要。
  • 把 du 与 df 的差当成"都被容器吃了":里面还有已删除但被持有的文件。
  • 不看 whiteout 就以为删除不占空间:它是上层的一个标记。
  • 忘了写时复制会让同一份内容多占一份。
  • 在 guest 里以为占用与自己无关:跑在 guest 里也算你付的钱。
  • 清完不读回:不知道占用到底降了没有。
  • 忽略挂载命名空间的差异:在错误的视图里找占用。
  • 卸载前不留回滚点:卸载之后回不去。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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