ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Docker磁盘占满排查与清理:overlay2、容器日志、Build Cache全解析

Docker磁盘占满排查与清理:overlay2、容器日志、Build Cache全解析 先看现象磁盘满了df -h显示 / 或者 /var/lib/docker 所在分区 100%容器起不来、日志写不进去SSH 偶尔还能敲两下命令但 docker 命令已经开始报错。这时候着急没用先把占用找出来再谈清理。这篇文章我从实际排障角度把 overlay2、容器日志、Build Cache 三类大头逐一拆开讲顺便把容易误删、踩坑的地方也列出来。想先应急清理的直接跳到 2.3 和 3.2想搞明白为什么占空间的建议从头看。我接到过不少 Docker 磁盘占满的求助现象基本一致df -h一敲/ 分区 100%或者专门挂载给 Docker 的数据盘满了容器各种异常。很多人第一反应就是“删镜像”但删完发现释放不了多少问题依旧。其实 Docker 占磁盘的大头通常集中在三处overlay2 存储目录、容器日志文件、构建缓存Build Cache。这三个理顺了磁盘基本就救回来了。这篇文章就围绕这三块把排查思路和清理实操结合起来适合所有用 Docker 部署过应用、遇到过磁盘告警的人参考。1. 磁盘告警后的第一件事先定位再动手1.1 用 df 和 du 快速定位占用层级不管报警来自云监控还是自己df -h看到的第一步永远是先搞清楚是谁在占用、占在哪一层。不要上来就docker system prune -af这会把有用的镜像一起干掉误伤面太大。我常用的排查顺序是这样# 1. 看整体磁盘使用率 df -h # 2. 看 Docker 数据目录占用默认是 /var/lib/docker sudo du -sh /var/lib/docker sudo du -h --max-depth1 /var/lib/docker | sort -rh | head -20du -h --max-depth1能直接看到/var/lib/docker下一级目录的体积排行。正常来讲最大的几个就是overlay2、containers、buildkit偶尔还有volumes。如果overlay2就占了几十个 GB那基本可以断定是镜像层和容器可写层堆积如果containers目录大了说明是某个容器的 json 日志在疯狂增长如果buildkit目录不小那就是 Build Cache 没跑了。这里有个细节du统计的时候比较消耗 IO如果是生产环境、磁盘 IO 本来就紧张建议在业务低峰期执行或者直接用--max-depth限制层级别对整个磁盘全量扫。1.2 用 docker system df 看 Docker 自己统计的空间账Linux 上 Docker 自带的磁盘统计命令是docker system df它会按镜像、容器、本地卷、构建缓存四个维度分别列出占用。这个命令的输出非常直观适合做第一轮判断docker system df输出大致长这样TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 5 4.852GB 3.624GB (74%) Containers 8 3 124.6MB 112.4MB (90%) Local Volumes 4 2 1.321GB 512MB (38%) Build Cache 26 0 8.223GB 8.223GB注意看RECLAIMABLE那列它表示可回收空间。Build Cache 如果一大堆且后面显示0个活跃项那基本能全部释放Images 一栏里面积大的多半是历史版本镜像和悬空镜像dangling image叠出来的。docker system df的好处是它告诉你的是一个“账面上的数”和du看到的物理占用可能不完全一致但作为判断依据足够了。我习惯两个结合看du看物理分布docker system df看逻辑构成。2. overlay2 目录增长拆解镜像层、容器层与孤儿数据2.1 overlay2 到底存了什么/var/lib/docker/overlay2是 Docker 默认存储驱动 OverlayFS 的工作目录它同时承担镜像层image layers和容器可写层container layer的存储。说白了所有镜像拉取下来的底层文件、所有容器运行过程中写入的文件最终都会落在 overlay2 下面。为什么它会越占越大主要有三个来源镜像层累积每次docker pull都会把镜像的所有层拉下来每次docker build如果基础镜像变了也会产生新的层。时间久了老镜像不删层就一直在。容器可写层残留容器删除后如果是在旧版本 Docker 或异常删除场景下一部分可写层数据可能残留在 overlay2 里成为孤儿目录。日志直写容器如果没有配置日志轮转日志文件会直接落在/var/lib/docker/containers/但日志文件的缓存数据也会占用 overlay 的读写资源看起来也是 overlay2 在涨。排查时你可以交叉验证docker ps -a看有没有大量已退出但没删除的容器再配合docker images看有没有一堆\u003cnone\u003e悬空镜像。这两样经常是 overlay2 虚胖的元凶。2.2 最小干预的清理顺序清理 overlay2 不需要直接进目录去删文件千万不要手贱去rm -rf /var/lib/docker/overlay2/*那是直接拆家会把现有容器搞坏的。正确的顺序是从“容器”往“镜像”一级级处理。第一步清理不用的容器# 删除所有已退出的容器 docker container prune -f第二步清理悬空镜像dangling images# 只删除没有标签、没有被容器引用的镜像 docker image prune第三步如果确认某个旧镜像不再需要直接按 ID 删docker rmi image_id整套操作执行完再跑一遍docker system df对比 RECLAIMABLE 数值通常能释放好几个 GB。这里注意docker image prune后面如果加-a会把所有未在使用的镜像都删掉包括那些你可能还想留着、但当前没跑容器的镜像。所以除非你心里有数否则默认不加-a。2.3 一键清理的粗粒度方案如果你想省事、不在乎重建镜像时的额外成本可以一步到位docker system prune -a -f --volumes这行的意思是删掉所有未使用的容器、网络、悬空镜像和没有被容器引用的镜像顺带把没有被容器使用的数据卷也一并删除。潜在风险很大如果你有一个存放数据库文件的数据卷而容器当前是停止状态这条命令会直接把这个数据卷删掉那数据库基本就没了。我的建议是--volumes慎用尤其生产环境。可以拆两步走docker system prune -a -f先释放镜像、容器、网络数据卷再单独排查。执行前最好docker ps -a过一遍确认没有你想保留下来的停止状态的容器。2.4 生产环境 overlay2 异常增长的应急操作生产环境有时候会碰到 overlay2 出现大量无法被docker system prune回收的孤儿目录这通常是容器被强杀、Docker 服务异常重启导致的。高低压场景下直接重启 Docker 不现实但可以这样处理先找到哪些容器还在运行不要动它们。把已退出的容器先清理掉docker container prune -f如果 overlay2 下的孤儿目录依然没人管可以用docker system df -v看每个镜像和容器的实际占用定位是否是某个特定容器的可写层异常膨胀。有一个容易忽略的点容器内部如果持续写日志到 stdout却没有配置日志轮转日志文件会无限增长。这类文件不体现在 overlay2 目录厚度上而是出现在/var/lib/docker/containers/容器ID/下文件名是容器ID-json.log。这就是下一节要解决的日志问题。3. 容器日志最常见的“隐形”磁盘杀手3.1 日志为什么能撑爆磁盘Docker 默认的日志驱动是 json-file容器里所有写到 stdout 的日志都会被 Docker 收集到宿主机上的/var/lib/docker/containers/container_id/container_id-json.log文件里。这个文件默认没有任何大小限制也不做轮转。如果你的应用打印日志特别勤快比如每次请求打一行、每天跑批任务刷屏那这个 json 日志文件可以在几天内从几十 MB 涨到几十 GB。实际排障时这类文件用du -sh /var/lib/docker/containers/*/*-json.log扫一眼就能看到谁是大头有些日志文件能到几十 GB场面相当恐怖。注意直接rm -f删掉正在运行容器的日志文件不是好办法因为容器的 stdout 文件句柄还开着文件虽然看不到了但空间并不会释放直到容器重启。正确的应急操作是sudo sh -c truncate -s 0 /var/lib/docker/containers/container_id/*-json.logtruncate -s 0会把文件大小强行截断为 0而且不会影响容器运行文件句柄依然有效后面的新日志会接着往里写。这是生产环境处理大日志文件最稳妥的急救手段。3.2 给日志加上限daemon.json 全局配置应急只是治标长期来看必须给日志加上限。推荐的方式是在 Docker 的 daemon 配置文件/etc/docker/daemon.json里设置日志轮转参数{ log-driver: json-file, log-opts: { max-size: 20m, max-file: 5 } }max-size: 20m单个日志文件最大 20MBmax-file: 5日志文件超过上限后滚动保留最多 5 个这样单容器最多占20 * 5 100MB从此不用担心单个容器日志写爆磁盘。改完需要重启 Docker 生效。重启之前记得确认当前系统里所有容器的启动方式有些用docker run命令行方式设置过--log-opt的会覆盖 daemon.json 的全局配置那就要单独处理。3.3 docker-compose 场景下的日志限制写法用 Docker Compose 编排服务的日志限制可以写在对应服务的配置里不用依赖全局配置services: app: image: myalatest logging: driver: json-file options: max-size: 20m max-file: 5这种按服务配置的方式更适合多租户或者说多服务混合部署的机器你可以给日志量大的服务一个更严格的上限给业务型服务相对宽松一点。唯一要提醒的是修改 logging 配置后需要docker compose up -d重建容器才能生效日志文件上限对已有容器不会自动生效。3.4 大日志文件的三个检查习惯我在处理完一轮日志清理后一般会立刻做三件事du -sh /var/lib/docker/containers/*/*-json.log复查一次确认没有再出现大文件。用docker inspect 容器名 --format{{.LogPath}}快速确认每个容器的日志路径和驱动配置。给监控系统加一个对/var/lib/docker目录总大小的定期检查超过阈值就告警。4. Build Cache构建任务留下的时间胶囊4.1 为什么 Build Cache 会越攒越多用 Docker 构建镜像时每执行一条RUN、COPY、ADD指令都可能产生新的层和缓存数据。这些缓存数据躺在 Docker 的 Build Cache 里目的是下次构建时复用加速构建过程。但如果你的项目经常改动基础镜像、换依赖版本或者跑 CI 流水线Build Cache 会以坦白讲让人意外的速度膨胀几十个 GB 都是常事。docker system df里的Build Cache一栏就是它的占地报表。这个区域经常被忽略因为普通应用场景里镜像和容器已经够占地方了构建频率不高的机器上 Build Cache 不会特别扎眼。可一旦你频繁构建比如每天都跑docker build更新测试环境几周下来 Build Cache 的体积可能比实际镜像体积还大。4.2 安全的清理命令Build Cache 属于“删了也能重建”的数据清理风险很低。最直接的方式是docker builder prune -a -f这条命令会把所有构建缓存清空包括那些可能还有用的中间层缓存。清完之后下次构建会从零开始拉基础镜像、重新执行指令构建时间会明显变长。如果你只是想清理部分缓存、保留最近用过的可以不加-adocker builder prune -f这样会只删掉那些不再被任何镜像引用的构建缓存。实际生产中如果构建任务不算频繁我一般直接用-a全清简单省事如果是 CI 机器构建频率高尽量保留近期缓存免得每次构建都重跑一遍投入产出比反而不好。4.3 BuildKit 缓存与 Dockerfile 优化Docker 新版默认使用 BuildKitdocker buildx或 Dockerfile 兼容模式构建缓存的存储位置和统计方式都归到buildkit目录下。BuildKit 的缓存机制比旧版更智能但同样会占用磁盘空间。如果你用的构建方式涉及RUN --mounttypecache比如依赖包缓存挂载那部分缓存体积会更大清理时用docker buildx prune -a -f更对路docker buildx prune -a -f如果实际环境中 buildx 没有被启用用docker builder prune即可。两者命令层级不同但清理目标一致不会产生冲突。另外与其等缓存膨胀后再清理不如在 Dockerfile 里做点预防措施。比如能合并的RUN指令就合并减少镜像层数使用--mounttypecache时合理控制缓存路径临时下载的文件在同一个 RUN 指令内清理掉。构建缓存的问题源头控制比事后清理更有效。5. 其它隐蔽占用与长期治理方案5.1 数据卷volumes的取舍docker system df里的Local Volumes经常被人忽略。数据卷用来持久化容器数据比如 MySQL 的数据目录、Redis 的持久化文件等。正常情况下它不会无限膨胀但如果某个容器写入了大量临时文件、或者数据库日志增长快数据卷也可能成为磁盘占用大户。清理数据卷前务必先确认该卷是否被其他容器在用。常用命令docker volume ls docker volume inspect volume_name如果确认是废弃数据卷再删docker volume prune -f注意数据卷一旦删除基本无法恢复数据库、消息队列、存储类中间件的数据目录最好单独做好物理备份再动。5.2 磁盘 Inode 耗尽容易和生产事故混淆的情况有些场景下df -h显示磁盘还有余量但 Docker 或者系统进程开始报 “No space left on device”这多半是 Inode 耗尽了。Inode 耗尽通常是大量小文件堆积导致的比如容器频繁创建临时文件、日志文件数量过多、overlay2 目录下残留碎片文件。排查命令很简单df -i如果IUsed%达到 100%那说明小文件把 inode 撑爆了。尤其老版本 Docker 在频繁创建、删除容器后overlay2 目录里的残留文件可能多到爆炸。这种问题比单纯磁盘满更麻烦因为即便删除大文件也不一定释放 inode得清理海量小文件才能缓解。遇到 inode 耗尽先停掉不停创建文件的容器然后找数量最多的目录用find /var/lib/docker -type f | wc -l统计文件数量逐步缩小范围。如果问题反复出现就要检查宿主机文件系统类型ext4的 inode 数量在创建文件系统时固定XFS管理方式会好一些。5.3 定期巡检与自动化清理脚本一次清干净不代表一劳永逸。我的建议是把清理动作沉淀成定期任务至少一个月跑一次。一个稳妥的自动化思路是每天检查容器日志大小超过某阈值直接 truncate 或告警。每周清理已退出容器和悬空镜像。每月清理 Build Cache 和无效数据卷。可以把它写成 shell 脚本配合 crontab 执行。示例脚本抽象如下#!/bin/bash # Docker cleanup script log_path/var/lib/docker/containers # 1. truncate 超过 100M 的日志文件 find $log_path -name *-json.log -size 100M -exec truncate -s 0 {} \; # 2. 清理已退出容器和悬空镜像 docker container prune -f docker image prune -f # 3. 清理构建缓存 docker builder prune -f这个脚本按需微调即可比如单独排除某些容器、控制docker builder prune的执行频率。注意脚本里的truncate部分在生产环境执行前最好先在测试环境验证一下避免误伤还在写日志的容器。5.4 是时候关注 Docker 数据目录迁移了如果你的 Docker 数据目录越长越大系统盘空间紧张长期来看可以考虑把/var/lib/docker整体迁移到大容量数据盘。迁移思路并不复杂停 Docker、把数据目录 rsync 到新位置、挂载或修改 daemon.json 的>{ data-root: /data/docker }然后重启 Docker 即可。注意目录迁移前后的文件权限要保持一致最好用rsync -avxP同步不要直接cp -r否则文件属主和软链接容易出问题。迁移完成后验证容器能否正常启动再考虑删除旧数据目录。这个操作虽然不直接降低磁盘占用但能从架构层面避免系统盘被 Docker 拖垮。很多团队把 Docker 装在默认目录上等系统盘满了才想办法其实迁移到数据盘之后至少不用为系统盘空间天天提心吊胆。6. 实战复盘一次典型的 80GB 磁盘清理6.1 现场状态与定位过程拿一次比较典型的案例来说某次测试环境告警df -h显示 / 分区 80G 用满容器全部进入报错状态。我先执行了docker system df结果如下Images 31 8 26.3GB 18.2GB Containers 45 6 2.1GB 1.96GB Local Volumes 7 3 4.5GB 1.2GB Build Cache 19 0 31.7GB 31.7GBBuild Cache 占了 31.7GBimages 里 RECLAIMABLE 有 18.2GB两块加起来差不多 50GB。再du -sh /var/lib/docker/containers/*/*-json.log发现有两个容器日志单文件超过 15GB。三个阶段分别处理后最终释放约 60GB 空间系统盘恢复到了 20% 以下。6.2 处理顺序与效果比对实际操作顺序是先处理日志——因为生产系统日志还在写越早截断越省空间。再清理 Build Cache——docker buildx prune -af一下释放了 31.7GB。最后删悬空镜像和停止的容器——docker image prune -f、docker container prune -f。每走一步我都重新跑一次docker system df确保每个阶段的释放量都符合预期避免某一步误删关键数据后找不到回溯依据。这套顺序背后的逻辑很简单日志是系统正在使用的缓存重建成本最低先处理它们“止损”悬空镜像和废弃容器对业务基本无感放最后操作风险最小。6.3 清理后的检查项清理完成不代表收工。我还会做几个检查docker ps -a确认所有预期中的服务都已运行docker logs --tail 50 服务名抽查几个容器的日志输出是否正常df -h确认磁盘余量恢复正常检查/etc/docker/daemon.json是否已配置日志轮转和>
返回列表