ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker磁盘清理实战:悬空镜像、数据卷与构建缓存全攻略

Docker磁盘清理实战:悬空镜像、数据卷与构建缓存全攻略 1. 先说结论为什么你的Docker越用越臃肿问题往往出在看不见的地方先说一个很多人的共同困惑明明没装几个容器磁盘怎么就被吃掉了。我在帮朋友排查服务器的时候遇到过最典型的案例——某个跑了好几个项目的机器df -h一看根分区已经用了98%但docker ps列出来只有五六个容器每个镜像看起来也就几百MB。后来一查真相是堆积的悬空镜像、退出状态的容器、还有疯狂增长的数据卷这三样东西加起来把几十GB空间吞得干干净净。这个现象其实来源于Docker本身的架构设计。镜像的分层存储、写时复制Copy-on-Write机制、可读写容器层、数据卷这些设计给了我们极大的灵活性但也意味着一旦你不主动管理残留数据就会像厨房里的塑料袋一样越攒越多永远不坏直到你不得不大扫除。这篇文章不是单纯罗列几条docker system prune命令就完事。我会从磁盘空间的构成说起把什么东西占了多少空间哪些能删哪些必须留着删错了怎么恢复这些关键点都讲清楚再附上我自己日常使用的完整清理流程和踩坑记录。适合刚上手Docker的同学建立正确的清理意识也适合已经跑了一段时间、磁盘告急的老手做一次系统性的梳理和优化。2. Docker磁盘空间到底被什么吃掉了先搞清五类垃圾和一类宝贝在动手删东西之前一定要先搞清楚Docker在你的磁盘上到底放了什么。我见过不少人上来就rm -rf /var/lib/docker这等于直接把数据库、数据卷、日志全删了后果是非常严重的。所以先花五分钟把目录结构弄清楚后面每一刀都能砍得明白。2.1 镜像层、容器层、数据卷、构建缓存、日志文件分别是什么Docker在宿主机上默认把数据放在/var/lib/dockerLinuxWindows和macOS的Docker Desktop则是放在虚拟机磁盘镜像里这里面主要包含几类东西镜像Images镜像由多层只读层组成每一层是Dockerfile里一条指令产生的文件变更。多个镜像可以共享底层所以docker images显示的总大小往往比实际占用要大。容器Containers容器在镜像层之上有一个可读写层你在这个基础上写的文件都在这一层。容器一旦被删除可写层就没了但是如果容器还停在退出状态它占用的空间就一直留在磁盘上。数据卷Volumes数据卷是独立于容器生命周期的存储用来持久化数据库数据、上传文件等。docker volume这块是最容易被忽视的空间大户尤其是你频繁跑测试、反复创建容器但没清卷的时候。构建缓存Build Cache执行docker build时Docker会缓存每一层的中间结果为了下次构建能复用。这个缓存膨胀速度极快一个月可能涨好几个GB。容器日志文件容器写到stdout/stderr的日志会被Docker截获并落到宿主机文件里单个容器如果不限制大小几天就能写出几个GB的文本日志。这五类里面前三类是业务数据或者疑似垃圾第四类缓存是可再生的冗余第五类是潜在风险。真正需要你小心的是数据卷——它不是垃圾一旦误删可能找不回来。2.2 用官方工具先看清账本空间占用分布Docker自带一个很好用的报表工具就是docker system df。它类似于标准分区工具的Docker版但专门统计Docker对象占用的空间。docker system df输出大概是这样的TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 28 12 12.35GB 6.53GB (52%) Containers 17 8 1.24GB 0.98GB (79%) Local Volumes 23 10 15.8GB 9.1GB (58%) Build Cache 0 0 0B 0B看到这张表你就能直观地知道哪个类别占了多大、有多少是可以回收的。加-v参数还能显示更详细的明细docker system df -v这个命令会列出每个镜像、每个容器的ID、名称、大小、共享层以及它被哪些容器引用。排查的时候非常有价值。注意docker system df只统计Docker自己管理的文件路径。如果你把数据卷绑定到了宿主机的自定义目录比如-v /data/mysql:/var/lib/mysql那部分空间不会显示在这里需要你自己用du去查。3. 清理策略的优先级先删容器再删镜像最后才是卷很多人一上来就docker system prune -a这个命令确实能删掉一堆东西但它太一刀切了。我更建议按照风险从低到高、收益从高到低的顺序来清理总共分四步每一步都可控、可回看。3.1 第一梯队清理停止状态的容器和悬空镜像先看最容易也最安全的一类停止的容器和悬空镜像dangling images。容器很直观停掉之后如果你不主动docker rm那个可写层就一直在磁盘上躺着。悬空镜像是指那些没有任何标签none:none的镜像层通常是重新构建镜像后旧版本留下的中间产物——它们引用的层已经没有可用的镜像标签了但占着磁盘空间。清理停止的容器docker container prune它会提示你将删除所有已停止的容器输入y确认或者直接加-f跳过提示。清理悬空镜像docker image prune这条命令只删悬空镜像不碰有标签的镜像所以非常安全。我见过有些教程推荐docker rmi $(docker images -qf danglingtrue)效果一样只是少了一层交互确认新手还是建议用prune系列。合并起来的快捷命令docker system prune这条默认清理所有停止的容器、悬空的镜像、无用的网络和构建缓存但不会动数据卷。整体来说docker system prune是日常最安全的清理命令可以放心用。3.2 第二梯队用 -a 参数清理所有未使用的镜像如果上面做完磁盘空间还是不够就需要进一步下重手了。docker image prune -a会删除所有没有被容器引用的镜像注意是所有未被引用的不是悬空的——区别在于那些原本有标签、但当前没有容器在用它们跑的镜像也会被删掉。比如你有个nginx:latest的镜像当前没有容器用它prune -a就会把它删了。下次要用就得重新拉取。删除命令docker image prune -a这条命令的执行风险比第一条高不少所以我给它划到了第二梯队。执行前建议先跑一遍docker image ls -a确认有哪些镜像、有没有你打算保留的。如果机器上只跑着一套固定的生产服务那么prune -a基本没问题因为正在用的容器会带着它们的镜像引用不会被删。3.3 第三梯队清理构建缓存docker build产生的Build Cache增长速度非常快尤其是CI/CD机器上几乎每次构建都会产生几十上百MB的缓存。缓存不是不能删删了只会让下次构建稍微慢一点重新生成不会有任何功能影响。所以构建缓存属于最值得回收、风险几乎为零的一类。清理构建缓存也有两种力度docker builder prune这条只清理不再使用的构建缓存。想全部清空就加-adocker builder prune -aBuild Cache有个特点是它经常是藏起来的大头。我用docker system df -v排查过一台机器发现镜像总共才5GB而Build Cache占到了14GB。这种数据在普通的df -h里根本看不出来但它真实地占据着一个分区。3.4 第四梯队数据卷清理——最容易误删、也最需要谨慎的一步终于到了最容易出问题的一步。数据卷是Docker里持久化数据的正规途径数据库文件、上传的附件、各种中间件状态都存在这里。docker system prune默认不会动数据卷这是Docker官方设计的安全机制。想看哪些卷是无主的没有被任何容器使用docker volume ls -f danglingtrue这些卷确实可以删但它们极可能承载着旧项目的数据库数据。删除之前我强烈建议你先花一分钟确认这个卷对应的容器是永久删除了还是只是暂时停掉了卷里的数据有没有备份这个项目是否以后还会再用到确认完再执行docker volume prune这一步我只推荐在有明确把握的情况下做。比如这台机器只是测试环境或者你确定所有业务数据都有外置备份那么把悬空卷一股脑清理掉完全没问题。如果是生产环境宁可漏删也别误删。4. 两条必学必用的日常维护命令日志大小限制和 overlay2 目录排查上面聊的都是已经堆积了怎么清但更高级的玩法是在源头就控制住让它堆不起来。这一节讲两个我自己的日常必用项。4.1 容器日志疯狂膨胀一条配置止血容器日志的默认驱动是json-file单个容器日志文件默认没有任何大小限制。生产环境最常见的磁盘事故就是某个容器的日志在几天内写出了几十GB的文本。比如一个调试级别开得比较高的Java服务或者一个在循环里打印访问日志的Nginx容器都能轻松做到。解决思路分两层第一层对已运行的容器做限制。可以在启动时指定日志驱动参数docker run -d \ --log-driver json-file \ --log-opt max-size10m \ --log-opt max-file3 \ nginx含义是这个容器的单个日志文件最多10MB超过就轮转最多保留3个文件加起来最多30MB。注意这些参数在容器创建时生效对已存在的容器需要重建才能应用。第二层从Docker Daemon全局配置入手。修改/etc/docker/daemon.json让所有新创建的容器默认带上日志限制{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }改完重启Docker服务。这个配置的效果是全局的任何新建的容器都会继承。实测下来这一条能避免绝大多数的日志撑爆磁盘问题。清理已有的大日志也可以直接找到文件删默认路径在/var/lib/docker/containers/容器ID/容器ID-json.log。但更推荐用truncate而不是rmtruncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log因为容器进程持有这个文件的句柄直接rm可能不会立即释放磁盘而truncate把文件清空为0字节最小化对运行容器的影响。4.2 overlay2 目录镜像层与容器层的真实存放点很多人在/var/lib/docker下面看到overlay2这个目录体积特别大却不知道里面是什么。它是Docker的存储驱动目录容纳了镜像层和容器可写层的全部数据。docker system df显示的镜像和容器占用最终对应的就是这个目录下的文件。排查这个目录的最笨但最有效的方法du -sh /var/lib/docker/overlay2如果你发现这个目录巨大但docker system df里的镜像、容器加起来又对不上那大概率是容器可写层在膨胀或者有大量悬空镜像。先用docker system df -v定位到底哪些对象占了大头再去对应处理。有一点要提醒不要手贱直接删overlay2里的子目录。很多网贴说把overlay2删掉就能清出空间这种操作极其危险——你删的可能是一个正在运行的容器的文件系统底层结果就是容器瞬间损坏甚至整个Docker的数据目录变得不一致。清理工作一定通过Docker命令实现不要绕过引擎直接操作文件。5. 实战一次完整的磁盘告急处理过程记录我在清理一台测试服务器时完整走过一遍排查和清理流程全程大概20分钟释放了将近40GB。这里把过程和思考写出来你可以照搬这个思路。5.1 第一步定位所有占用大头机器上执行df -h根分区已经93%。再跑docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 45 9 18.3GB 12.2GB (66%) Containers 31 7 2.8GB 2.3GB (82%) Local Volumes 26 11 11.7GB 6.2GB (53%) Build Cache 18 0 9.6GB 9.6GB (100%)乍一看镜像最大但真正浪费的是退出的容器2.3GB可回收、悬空的镜像和未用镜像12.2GB可回收、悬空卷6.2GB可回收、以及全是可回收状态的Build Cache9.6GB。这四块加起来差不多30GB。5.2 第二步按顺序执行清理我先执行最安全的第一梯队docker container prune -f docker image prune -f docker builder prune -f这一波执行完再看一眼docker system df大概释放了14GB。接着评估一下这台机器上还有哪些有标签的镜像没有容器在用——因为这台机器是测试环境镜像随时可以重新拉取所以我可以放心执行docker image prune -a -f这一步又释放了约10GB。最后是数据卷我仔细观察了docker volume ls -f danglingtrue的结果确认这些都是之前跑临时测试容器时创建的无主卷没有业务数据库挂在里面于是清理掉docker volume prune -f全部执行完docker system df显示TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 9 6.1GB 0B Containers 7 7 0.0B 0B Local Volumes 10 10 5.5GB 0B Build Cache 2 0 0.4GB 0.4GB根分区从93%降到了71%释放约22GB。5.3 第三步清理后的验证与收尾清理完要验证服务是否正常。最直接的办法是把关键容器的接口和日志看一遍docker ps docker logs --tail 50 容器名 curl -I http://localhost:8080我在实际清理中还发现过一个问题有个数据库容器虽然一直在跑但清完悬空卷之后它启动时挂载的是命名卷所以完全没受影响。这又印证了一件事——只要你是按容器引用着卷就不会被删的规则操作的清理本身是安全的。最后建议养成一个习惯把docker system prune和docker image prune -a作为每周或每两周的例行命令特别是在开发机、CI机器上。定期清理比等磁盘红了再紧急处理要舒服得多。6. 绕开常见坑为什么清理完空间没回来或者Docker直接起不来了清理Docker磁盘空间时有几个看似正常、实则埋雷的操作专门开一节说。6.1 清理了但磁盘没释放可能你删的是空壳有一种情况是docker system df显示某个对象已经删了可df -h一看可用空间几乎没变。这通常是因为那个文件其实还被某个进程占着句柄。最常见的就是容器日志文件——你已经把容器删了但对应的json.log文件被另一个进程比如还在运行的日志采集agent打开着文件删除不了空间暂时释放不出来。遇到这种情况最简单的处理是重启一下Docker服务systemctl restart docker重启之后所有持有的文件句柄会被释放该回收的空间会真正回来。我在一台日志采集的机器上就遇到过类似现象重启Docker后立刻多出几GB空间。6.2 千万别动 /var/lib/docker 的底层文件为什么手动 rm 会致命前面已经提过一次这里重点展开。/var/lib/docker/overlay2下的每一层目录都被Docker引擎通过内部ID关联着。如果你用rm -rf删掉了某个子目录但是对应的镜像和容器元数据还指向它轻则某个容器启动报错重则Docker的镜像层链断裂后续构建和拉取全部异常。更危险的是/var/lib/docker/containers目录——每个容器的config文件、日志、挂载信息都在这。一旦误删Docker甚至可能无法识别容器的状态。所以强烈建议任何清理动作都通过docker命令行或者调用Docker API完成永远不要绕过引擎直接操作数据目录。如果你真的要针对某个目录做清理比如确认某个大文件日志已经没用了优先使用truncate -s 0来清空文件内容而不是rm。6.3 Docker Desktop用户和Linux用户清理的差异如果你是Windows或macOS上的Docker Desktop用户磁盘空间的实际占用是落在宿主机的一个虚拟磁盘镜像文件里通常是Docker.raw或Docker.vhdx。docker system df的统计结果是一样的但清理完之后虚拟磁盘镜像文件并不会自动瘦身。也就是说你在Docker里删了东西宿主机上的那个.raw文件可能还是那么大。Docker Desktop有一定自动回收机制但效果不一定及时。实在需要释放宿主机空间时正确的做法是把不需要的镜像、容器、卷全部清理干净在Docker Desktop的Troubleshoot故障排除页面里使用Clean / Purge data功能来清理数据在设置里调整磁盘镜像大小的上限或者通过内置的disk utilization功能查看空间占用。Linux原生Docker没有这层虚拟磁盘的问题删了就删了空间立刻释放。所以两个平台的清理思路在清出来这一步是一样的差别主要在空间是否马上还给宿主机。6.4 清理后Docker服务启动失败的常见原因清理完重启Docker偶尔会遇到Docker服务启动失败或者docker ps命令直接报错的情况。我把最常见的几个原因列出来daemon.json配置错误日志大小限制、存储驱动等配置写错会导致守护进程起不来。检查/etc/docker/daemon.json的JSON语法可以用docker info查看当前生效配置。镜像层目录损坏如果你之前手动删除过overlay2下的目录启动时Docker会报层数据缺失。磁盘空间仍然不足Docker自身也需要空间来写配置和临时文件。如果清理后可用空间依然不足守护进程可能拒绝启动。先确认df -h /var/lib/docker所在分区有足够的可用空间再做后续操作。我自己的习惯是清理前先把当前所有容器列表和卷列表导出备份一下docker ps -a --format table {{.ID}}\t{{.Image}}\t{{.Names}} containers_backup.txt docker volume ls volumes_backup.txt一旦出问题至少知道之前的环境里有什么排查范围不会漂移太远。多数情况下只要不碰底层目录、不删卷、不动daemon配置怎么清都很难把Docker清挂。7. 挂载目录、镜像仓库扫描和CI场景清理之外的三块隐性空间最后再补充三个日常容易忽略的角落。它们不属于标准的清理命令但加在一起经常是磁盘爆满的暗线。7.1 使用 bind mount 的数据不在 Docker 统计内很多人把MySQL、PostgreSQL的数据目录用-v /host/data:/var/lib/mysql挂载到宿主机某路径下。这样的话docker system df永远看不到这部分数据占的空间因为数据存在于宿主机目录里Docker只负责容器内部与外部目录的映射关系。排查这部分空间占用只能在宿主机上用常规文件工具du -sh /host/data/*所以当你觉得Docker占用不大但磁盘满了的时候别忘了检查这类绑定挂载目录。它们算Docker运行产生的数据但从统计口径上不属于Docker管理范围。7.2 镜像源和镜像仓库拉取与存储的隐形账单Docker默认镜像源在国外很多人在国内环境拉镜像会感觉很慢于是配置了国内镜像源加速。这块本身不占本地磁盘但有一个间接影响如果镜像源不稳定你会反复尝试拉取同一个大镜像每次失败都会留下不完整的镜像层数据。虽然不完整层通常在下次拉取成功后被覆盖但在未完成前可能作为悬空数据存在。更常见的是自建或私有镜像仓库如Harbor、Docker Registry产生的存储。这类服务如果自己本身也跑在Docker里它的数据卷会非常大。我曾经排查过一个Harbor容器光它的数据卷就占了80GB里面全是历史镜像的存储层。这类仓库应该周期性地清理不再需要的镜像Tag并执行仓库的垃圾回收GC机制。对于Harbor还需要在Web界面里执行Garbage Collection才能真正释放磁盘。7.3 CI / CD 机器上构建缓存是最大的隐形元凶持续集成机器上的Docker磁盘空间问题和普通开发机不一样。CI机器最常见的场景是docker build和docker push反复执行产生的Build Cache、旧的镜像Tag、以及每次构建遗留下来的临时容器数量惊人。我处理过一台跑着Jenkins和GitLab Runner的机器根分区整整200GB居然也快满了。排查下来Build Cache占了80GB历史版本的业务镜像占了60GB。清完之后空间立刻恢复了120GB以上。如果CI用的Docker是动态创建容器来跑任务的模式比如DinD或者Kaniko还要额外留意每个任务结束时是否把临时容器、网络、卷都清理干净。很多CI任务如果不加--rm参数任务跑完会留下一堆停止状态的容器时间一长就是巨大的垃圾场。当时我给运维定的方案很简单在CI的Docker Host上配置一个定时任务每天凌晨执行一次docker system prune -a -f --filter until24h这个命令只清理24小时前创建的、现在没被使用的镜像和容器时间上保证了当天跑过的构建产物不会被误删同时又能把历史垃圾清掉。然后Build Cache单独用docker builder prune -f --filter until24h来回收。这个策略在那台机器上运行了半年多磁盘占用一直很平稳。8. 我的最终建议一套可落地的清理节奏和一条必须记住的底线把上面所有内容归纳成一套能直接执行的节奏大概是这样日常每周/每两周docker system prune -f docker builder prune -f告急时磁盘剩余不足20%docker container prune -f docker image prune -a -f docker volume prune -f长期预防Docker Daemon全局配置日志轮转max-size / max-fileCI机器添加定时缓存清理任务数据卷和镜像使用情况定期检查而不是等告警必须记住的底线是数据卷不要轻易用prune -f一把梭删除前要确认卷里没有业务数据/var/lib/docker下的底层文件不要手动删绑定挂载进容器的宿主机目录Docker的清理命令管不到得自己用du和常规文件管理工具去处理。经验一点凡是在生产环境加清理任务先在测试机器上跑通一遍观察一两个清理周期确认对业务容器没有任何影响再上生产。我早期就吃过不加验证的亏——直接在生产上跑docker system prune -a -f结果把一个还挂在Docker Compose环境中、但暂时停止的关键服务镜像清掉了下次启动时拉不到镜像直接服务起不来。后来养成了先看docker ps -a的习惯情况就好多了。Docker的磁盘清理说到底就八个字看得清、删得对、留得住。搞清楚每类对象是干什么的按风险从低到高去清理再补上日志和缓存的上限控制你的Docker主机就基本告别磁盘告急这种事了。
返回列表