ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker镜像与缓存清理实战:从原理到自动化运维策略

Docker镜像与缓存清理实战:从原理到自动化运维策略 1. 从一次磁盘告警说起Docker空间管理的现实困境那天下午监控系统突然弹出一条告警提示某台核心服务器的磁盘使用率超过了90%。我第一反应是业务日志暴增但登录服务器一看df -h显示根目录下/var/lib/docker这个目录占用了超过200GB的空间。这显然不是业务日志的问题而是Docker这个“空间吞噬者”在作祟。相信很多运维和开发同学都遇到过类似场景随着容器化应用的持续部署和迭代Docker会像松鼠囤积过冬粮食一样在本地积累大量废弃的镜像、停止的容器、无用的网络和悬空的卷。这些资源不仅占用宝贵的磁盘空间更关键的是它们会拖慢Docker自身的操作速度比如docker pull、docker build甚至影响宿主机的整体性能。Docker的存储管理尤其是镜像和缓存清理是容器化运维中一项看似基础却至关重要的“内功”。它不像部署一个微服务架构那样引人注目但却是保障系统长期稳定、高效运行的基石。很多人对Docker清理的理解可能还停留在偶尔手动执行一下docker system prune的层面但这远远不够。不恰当的清理可能误删正在使用的镜像层而清理不及时则会导致磁盘爆满、服务宕机。今天我们就来深入聊聊如何系统性地、安全高效地清理Docker的废弃镜像与各类缓存把这门“内功”练扎实。2. Docker存储架构拆解理解“垃圾”从何而来要有效清理首先得明白Docker把东西都存哪儿了以及为什么会产生“垃圾”。Docker默认使用/var/lib/docker作为其数据根目录可以通过docker info命令查看Docker Root Dir确认其下的子目录结构决定了不同类型的资源存储位置。2.1 核心存储目录解析/var/lib/docker目录下主要有以下几个关键子目录它们共同构成了Docker的存储世界overlay2/(或aufs/,devicemapper/): 这是容器和镜像分层存储的核心。现代Docker默认使用overlay2存储驱动。每个镜像层Layer和容器层可写层都以目录形式存放在这里。当你删除一个容器时其对应的可写层理论上可以被回收但如果有其他容器或镜像共享其底层只读层这些只读层会保留。真正的问题在于当你反复构建、拉取不同标签的镜像时会产生大量中间层和悬空dangling层。image/: 存储镜像的元数据如镜像ID、标签、父层关系等。它本身不大但关联着overlay2中的实际数据层。containers/: 存放每个容器的配置信息、日志如果未配置日志驱动外抛和初始层信息。停止的容器仍然会占用这里的空间。volumes/: Docker管理的命名卷Named Volumes存储在此。如果容器使用了匿名卷未指定名称的卷或命名卷但容器删除后未清理这里就会留下“孤儿卷”。buildkit/: 如果使用BuildKit现代Docker默认的构建器进行镜像构建这里会缓存构建过程中的中间状态以加速后续构建。但如果构建频繁且差异大缓存也会快速增长。2.2 “废弃”与“缓存”的具体所指我们常说的“废弃镜像与缓存”在Docker语境下主要有以下几类悬空镜像 (Dangling Images): 这是最主要的“垃圾”来源。所有没有标签Tag且没有被任何容器引用的镜像层都被称为悬空镜像。它们通常由以下操作产生重新构建镜像当你为同一个镜像名如myapp:latest构建新版本时旧版本的镜像ID会失去myapp:latest这个标签变成悬空镜像。拉取新标签覆盖旧标签docker pull nginx:latest执行两次第一次拉取的镜像就会变成悬空。 你可以通过docker images -f “danglingtrue”来查看它们。未使用的镜像 (Unused Images): 指那些有标签但当前没有任何正在运行或已停止的容器基于其创建的镜像。它们可能是你为了测试拉取但不再需要的或者旧版本的应用镜像。停止的容器 (Stopped Containers): 容器停止后其文件系统层可写层和元数据依然存在占用空间。未使用的卷 (Unused Volumes): 没有任何容器在使用的Docker卷。这是数据丢失的高风险区清理时需要格外小心。未使用的网络 (Unused Networks): 用户自定义的、未被任何容器使用的网络。通常占用空间很小但数量多时也需管理。构建缓存 (Build Cache): 使用docker build时Docker会缓存每一步Dockerfile中的每一行指令的结果。当Dockerfile或构建上下文发生变化时从变化点开始之后的缓存失效但之前的缓存依然存在。频繁构建不同项目会导致缓存膨胀。理解这些概念的来源是我们进行精准清理的前提。接下来我们将从手动到自动从简单到深入逐一拆解清理策略。3. 手动清理实战命令行工具深度使用指南Docker提供了一系列命令行工具来帮助我们进行清理。虽然自动化是最终目标但掌握手动命令是理解和控制清理过程的基础。3.1 基础清理命令docker system prune这是最广为人知的清理命令它是一个复合命令的快捷方式。# 最常用的交互式清理会询问是否继续 docker system prune # 强制清理无需确认 docker system prune -f # 清理所有未使用的资源包括镜像、容器、卷、网络谨慎使用 docker system prune -a命令详解与风险提示docker system prune默认会删除所有已停止的容器。所有未被任何容器使用的网络默认的bridge、host、none网络除外。所有悬空镜像danglingtrue。它不会删除未被容器使用的普通镜像有标签的、构建缓存、以及任何卷。docker system prune -a是威力巨大的命令。它在默认清理的基础上额外删除所有未被任何容器使用的镜像无论是否有标签。这意味着如果你有一些备用镜像、历史版本镜像没有正在运行的容器使用它们会被一并删除。执行此命令前务必确认你是否真的不再需要这些镜像。docker system prune不会删除构建缓存。这是很多人误以为清理了但空间回收不明显的原因之一。注意对于生产环境除非有明确的维护窗口和备份策略否则不建议使用-a参数。更推荐针对性地清理特定类型的资源。3.2 精准打击针对特定资源类型的清理对于生产环境或需要更精细控制的场景我们应该使用针对特定资源类型的子命令。清理镜像# 删除所有悬空镜像 docker image prune # 删除所有未被任何容器使用的镜像无论是否悬空 docker image prune -a # 删除超过48小时的未被使用的镜像 docker image prune -a --filter “until48h”使用--filter参数可以按时间过滤非常实用。例如until24h表示删除24小时前创建的未使用镜像。清理容器# 删除所有已停止的容器 docker container prune # 删除停止超过一周的容器 docker container prune --filter “until168h”清理卷# 删除所有未被任何容器使用的卷高危操作 docker volume prune这是最需要谨慎的操作Docker卷通常用于持久化重要数据如数据库文件、配置文件。执行前必须确保卷内数据已备份或确认无用。一个常见的检查方法是docker volume ls -f danglingtrue列出所有悬空卷然后手动检查其内容或确认其来源。清理网络# 删除所有未被使用的自定义网络 docker network prune清理构建缓存# 这是清理构建缓存的正规命令 docker builder prune # 删除所有构建缓存 docker builder prune -a # 删除超过一天未使用的构建缓存 docker builder prune --filter “until24h”如果你发现docker system prune后空间释放不多但/var/lib/docker/buildkit目录很大那么就需要专门执行这个命令。3.3 空间占用分析神器docker system df在动手清理之前先做诊断。docker system df命令类似于Linux的df但专用于Docker它能清晰展示各类资源占用的空间情况。$ docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 24 10 5.43GB 3.21GB (59%) Containers 15 5 1.32GB 1.32GB (100%) Local Volumes 5 2 250MB 150MB (60%) Build Cache 58 0 2.1GB 2.1GB这个输出非常直观RECLAIMABLE列直接告诉你可以回收的空间大小和百分比。从上例可以看出镜像有59%可回收主要是未使用的旧镜像所有已停止的容器占用的空间100%可回收构建缓存全部可回收。这个报告让你有的放矢知道该优先清理哪一类资源。进阶用法docker system df -v可以列出详细的、每个镜像、容器、卷的占用空间情况帮助你定位具体的“大块头”。4. 自动化清理策略告别手动实现无人值守手动清理适合临时救急或维护窗口但可持续的运维需要自动化。这里介绍几种主流的自动化清理方案。4.1 使用Docker内置的定时任务Docker本身没有内置的定时清理功能但我们可以结合Linux的Cron或Systemd Timer来实现。Cron Job示例创建一个脚本文件如/usr/local/bin/clean-docker.sh#!/bin/bash # 清理悬空镜像和已停止容器 docker image prune -f docker container prune -f # 谨慎添加清理构建缓存 # docker builder prune -f # 非常谨慎添加清理未使用卷通常不建议自动化 # docker volume prune -f然后赋予执行权限并添加到Cron中例如每天凌晨3点执行chmod x /usr/local/bin/clean-docker.sh crontab -e # 添加一行 0 3 * * * /usr/local/bin/clean-docker.shSystemd Timer示例更现代的方式创建服务单元文件/etc/systemd/system/clean-docker.service[Unit] DescriptionClean unused Docker resources [Service] Typeoneshot ExecStart/usr/bin/docker image prune -f ExecStart/usr/bin/docker container prune -f # 可以添加更多ExecStart行创建定时器单元文件/etc/systemd/system/clean-docker.timer[Unit] DescriptionRun docker cleanup daily [Timer] OnCalendardaily Persistenttrue [Install] WantedBytimers.target然后启用并启动定时器sudo systemctl enable clean-docker.timer sudo systemctl start clean-docker.timer个人经验在生产环境中我通常只自动化docker image prune和docker container prune。对于docker volume prune由于其高风险性我从不将其加入自动化任务而是通过监控告警如卷数量或特定目录空间触发人工审查流程。构建缓存的清理docker builder prune可以按周或按月自动化具体频率取决于构建的活跃度。4.2 第三方工具docker-clean与docker-gc对于需要更复杂策略的团队可以考虑第三方工具。docker-clean: 一个Python脚本提供更丰富的过滤选项和报告功能。你可以配置保留最近N个镜像排除某些关键镜像等。docker-gc(Docker Garbage Collector): 一个经典的、以容器方式运行的垃圾收集工具。它通过环境变量提供灵活的配置比如GRACE_PERIOD_SECONDS容器停止后多久可被删除、FORCE_IMAGE_REMOVAL等。它的运行逻辑是标记所有正在使用的资源然后删除所有未标记的资源。使用docker-gc示例# 拉取镜像 docker pull spotify/docker-gc # 运行一次只删除一天前的资源 docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /etc:/etc:ro spotify/docker-gc # 作为定时任务运行第三方工具的好处是策略可定制化程度高但需要引入额外的维护成本。4.3 在CI/CD流水线中集成清理在持续集成/持续部署环境中构建节点是Docker垃圾产生的重灾区。在每个构建任务Job的post阶段无论成功与否加入清理步骤是保持节点健康的最佳实践。例如在GitLab CI的.gitlab-ci.yml中cleanup: stage: .post # .post是一个特殊的始终最后执行的阶段 script: - docker system prune -f || true when: always # 无论之前阶段成功失败都执行或者在Jenkins Pipeline中post { always { sh ‘docker system prune -f’ } }这样能确保每次构建任务结束后都会自动清理本次构建产生的临时镜像和容器避免节点磁盘被慢慢撑满。5. 高级议题与避坑指南清理背后的原理与风险控制掌握了基本操作后我们需要深入一些高级话题和常见陷阱这能让你从“会用”变成“精通”。5.1 镜像分层与共享为什么不能乱删这是Docker存储的核心机制。一个镜像由多个只读层Layer叠加而成容器则在最上层添加一个可写层。多个镜像可以共享相同的底层只读层。风险场景假设你有一个基础镜像ubuntu:18.04镜像A和镜像B都基于它构建。如果你因为镜像A暂时不用而用docker rmi强制删除了它的某个独有层这没问题。但如果你删除了ubuntu:18.04的某个共享层而镜像B还在使用它那么镜像B就会损坏基于镜像B运行的容器也会出现文件丢失错误。安全清理的原则docker image prune系列命令在设计上遵循了引用计数原则。只有当某个镜像层没有任何镜像或容器引用时它才会被判定为“悬空”或“未使用”从而被删除。因此使用Docker官方提供的prune命令通常比直接docker rmi $(docker images -q)更安全。手动docker rmi时务必用docker image inspect查看镜像的依赖关系。5.2 数据卷的生命周期管理最大的风险点卷的清理是重中之重误删意味着数据丢失。避坑实践命名卷优于匿名卷在docker run或Compose文件中始终为需要持久化的数据使用命名卷-v mydata:/path/in/container而不是匿名卷-v /path/in/container。命名卷易于识别和管理。清理前的检查清单执行docker volume ls列出所有卷。使用docker volume inspect volume_name查看卷的详细信息特别是Mountpoint它指向宿主机上的实际目录。你可以去该目录查看文件内容确认其用途。检查是否有容器即使是停止的在使用它。悬空卷dangling才是docker volume prune的目标。备份第一对于任何计划删除的卷尤其是生产环境先备份。一个简单的备份命令docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .。5.3 配置Docker守护进程的存储驱动与存储选项对于磁盘空间特别紧张或性能要求极高的环境可以在安装Docker时就进行配置。更改数据根目录默认的/var/lib/docker可能在根分区而根分区通常较小。可以在/etc/docker/daemon.json中配置将其移到更大的磁盘分区。{ “data-root”: “/mnt/big-disk/docker” }修改后需要重启Docker服务并迁移原有数据复制或挂载。存储驱动选择overlay2是当前推荐且默认的驱动它在空间利用和性能上比较均衡。对于某些极端环境如超多镜像层可以评估zfs或btrfs但它们需要额外的文件系统支持增加了复杂度。不要轻易更换存储驱动更换通常需要重新初始化所有Docker数据。5.4 监控与告警让清理成为可观测的一环纯粹的定时清理是“盲目的”。我们应该建立监控让清理动作更有依据。监控指标/var/lib/docker目录的磁盘使用率和增长趋势。Docker镜像、容器、卷、网络的数量。悬空镜像的数量和总大小。告警策略当磁盘使用率超过80%或悬空镜像总大小超过一定阈值如20GB时触发告警。告警可以触发自动清理脚本也可以通知运维人员手动介入审查。集成到现有监控系统使用docker system df的输出结合Shell脚本或Python脚本将数据提取并上报到Prometheus、Zabbix等监控系统实现可视化仪表盘。6. 实战案例一个高负载CI服务器的清理方案设计最后我们以一个真实的场景来串联所有知识点为一台高负载的GitLab CI Runner服务器设计Docker清理方案。背景该服务器每天执行数百次Docker镜像构建任务使用Kubernetes执行器但构建本身发生在Pod内的Docker环境中产生了大量中间镜像和缓存。问题根分区/var/lib/docker所在每周都会告警需要人工登录清理。解决方案设计分层清理策略每日在Runner的每个构建任务结束后通过post脚本执行docker image prune -f和docker builder prune --filter “until2h” -f清理2小时前产生的构建缓存和悬空镜像。这能清理掉大部分短期垃圾。每周通过一个Systemd Timer在周日凌晨执行一个更全面的清理脚本#!/bin/bash # 清理所有已停止的容器CI环境中容器都是临时任务 docker container prune -f # 清理所有未被使用的镜像CI环境通常不需要保留历史镜像 docker image prune -a -f # 清理超过7天的构建缓存 docker builder prune --filter “until168h” -f # 列出悬空卷供人工检查不自动删除 echo “ Dangling Volumes docker volume ls -f danglingtrue每月人工登录根据docker system df -v的输出审查是否有异常巨大的镜像或卷并决定是否清理。配置调整修改Runner配置将Docker数据目录通过DOCKER_OPTS或daemon.json指向一个单独的大容量数据盘与系统盘隔离。评估并设置Docker守护进程的日志轮转策略/etc/docker/daemon.json中的log-opts防止容器日志占满磁盘。监控集成编写一个简单的Python脚本定期执行docker system df并将RECLAIMABLE数据解析后通过Pushgateway上报到Prometheus。在Grafana中创建仪表盘可视化可回收空间的变化趋势。设置告警规则当整体可回收空间大于50GB时发送警告通知。通过这套组合拳这台CI服务器的磁盘空间问题得到了根本解决从被动救火变成了主动管理运维人员只需关注每月一次的报表即可。这个案例的核心思想是清理不是目的建立可持续的、数据驱动的存储资源管理机制才是。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进