尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CentOS服务器磁盘爆满排查与Docker存储清理实战指南

CentOS服务器磁盘爆满排查与Docker存储清理实战指南 1. 项目概述当磁盘爆满成为运维的日常“磁盘又满了”——这大概是每个运维工程师和服务器管理员最常听到的警报之一。尤其是在使用 CentOS 这类稳定、长寿的服务器系统时/dev/vda1这个根分区或系统盘被塞满导致服务异常、日志无法写入甚至系统直接卡死的情况简直是家常便饭。而随着容器化技术的普及Docker 这个“空间吞噬者”更是让问题雪上加霜。表面上df -h命令显示/dev/vda1占用率 100%但用du -sh /*一层层查下去却发现各个目录加起来远没有占满。这种“空间去哪儿了”的悬疑剧每天都在无数台服务器上上演。我处理过太多类似的线上紧急故障从凌晨被叫醒处理生产环境宕机到为客户的测试环境做定期“瘦身”。这个问题的核心远不止“删文件”那么简单。它涉及到 Linux 文件系统的运作机制特别是 ext4/xfs 的 inode 与 block、Docker 的存储驱动原理、以及日志、缓存等系统组件的“隐形”增长。盲目删除rm -rf可能误删关键数据而简单的重启或许能暂时缓解但根源未除几天后问题必然卷土重来。本文将从一个实战场景出发一台 CentOS 7.9 服务器/dev/vda1显示 100% 占用主要嫌疑人是 Docker。我将带你走完从紧急排查、精准定位、安全清理到长效预防的完整闭环。这不仅是一份“救火”指南更是一套理解系统存储、驾驭 Docker 存储的运维心法。无论你是刚接手服务器的开发者还是需要管理大量线上环境运维这些思路和命令都能直接拿来用。2. 核心思路拆解从表象到根源的排查逻辑面对磁盘 100% 占用的告警切忌慌乱地直接登录服务器执行rm -rf。一个系统化的排查思路能帮你高效、安全地解决问题。整个过程可以概括为“确认-定位-清理-预防”四个阶段。2.1 确认问题与影响评估首先我们需要确切地知道问题的严重程度和影响范围。使用df命令确认全局磁盘使用情况df -h这个命令会以人类可读的格式G、M显示所有挂载点的磁盘使用情况。重点关注/dev/vda1对应的挂载点通常是/。Use%列显示为 100% 或接近 100%Avail列显示可用空间极小或为 0这就是问题的直接证据。检查 inode 使用情况 磁盘空间满有两种情况数据块(block)用完了或者索引节点(inode)用完了。后者常发生在存在大量小文件的场景如 Docker 容器日志、邮件队列。df -i查看IUse%列。如果 inode 也用尽了即使df -h显示还有空间系统也无法创建新文件或目录表现同样是“磁盘已满”。快速评估系统状态系统是否响应缓慢使用top或htop查看 CPU 和内存使用率磁盘 I/O 等待wa值是否异常高。关键服务是否正常检查数据库、Web 服务器等核心服务的日志和状态。磁盘满常常导致日志无法滚动进而引发服务崩溃。是否有用户或进程正在写入大量数据使用iotop命令可能需要安装可以实时查看磁盘 I/O 占用最高的进程。注意在磁盘完全写满的情况下某些命令如sudo可能因为无法创建临时文件或写入日志而失败。如果遇到权限问题可能需要通过控制台或带-T参数的 SSH 连接强制不分配伪终端来执行基础命令。2.2 定位“元凶”空间被谁占用了知道磁盘满了下一步是找出占用空间最大的文件或目录。这里的关键是区分“已删除文件但未释放”和“现存大文件”两种情况。第一步查找现存的大文件和目录从根目录开始使用du命令进行排序查找。为了避免权限问题导致扫描中断通常使用sudo。sudo du -h --max-depth1 / 2/dev/null | sort -hr | head -20这条命令的含义是扫描根目录/下第一级子目录的大小--max-depth1忽略所有权限错误2/dev/null然后按人类可读的数字逆序排序sort -hr最后显示前20个结果。通常/var、/home、/usr会是嫌疑最大的目录。第二步深入嫌疑目录假设发现/var目录异常巨大则继续深入sudo du -h --max-depth1 /var 2/dev/null | sort -hr | head -20依次类推像剥洋葱一样最终定位到具体的超大文件或目录。常见的“肥宅”目录包括/var/lib/docker Docker 的默认数据根目录包含镜像、容器、卷和构建缓存。/var/log 系统日志和各类应用日志。/var/cache 软件包管理器如 yum/dnf的缓存。/home 用户数据。/tmp 临时文件。第三步检查“幽灵”空间——已删除文件未释放这是最棘手的一种情况。当一个文件被进程打开时即使你用rm删除了它只要进程不关闭文件句柄该文件占用的磁盘空间就不会被释放。df显示空间已满但du统计却找不到对应的大文件。 使用lsof命令来查找此类文件sudo lsof L1 # 列出所有链接数为0已被删除但仍被打开的文件或者更直接地查看/proc文件系统中的信息sudo lsof | grep deleted这条命令会列出所有状态为“deleted”的文件以及打开它们的进程 PID。你会看到类似这样的输出COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 1234 app 11w REG 253,0 102400000 123456 /var/log/app.log (deleted)这表示 PID 为 1234 的 Java 进程仍在写入一个名为app.log的文件但这个文件在文件系统目录中已经被删除了。空间被它占着却无法通过常规方式找到和清理。解决方法通常是重启该进程或者通过/proc/PID/fd/FD的方式清空文件内容需谨慎。2.3 针对性清理策略根据定位结果采取不同的清理策略。1. 清理 Docker 占用这是本文的重点。Docker 占用的空间主要分四部分镜像、容器、数据卷、构建缓存。查看 Docker 磁盘使用概况docker system df -v这个命令会详细列出镜像、容器、本地卷和构建缓存各自占用的空间是分析 Docker 存储的利器。清理无用镜像 删除所有未被容器引用的悬虚镜像。docker image prune -a加上-a会删除所有未被任何容器使用的镜像包括有标签但未使用的操作前请确认。清理停止的容器、未使用的卷和构建缓存docker system prune -a --volumes这是一个危险命令--volumes会删除所有未被任何容器使用的命名卷和匿名卷。请确保卷内没有需要保留的数据。通常更安全的做法是分步执行docker container prune # 清理停止的容器 docker volume prune # 清理未使用的卷会提示确认 docker builder prune # 清理构建缓存清理容器日志 Docker 容器的标准输出日志默认由json-file驱动管理存放在/var/lib/docker/containers/容器ID/容器ID-json.log且默认没有大小限制极易膨胀。临时清理 找到大日志文件后可以用truncate命令清空比rm安全因为rm可能因进程占用导致空间不释放。sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log长效限制 修改 Docker 守护进程配置全局限制日志大小。编辑/etc/docker/daemon.json若不存在则创建{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }然后重启 Dockersudo systemctl restart docker。这会将每个容器的日志文件最大尺寸限制为10MB最多保留3个文件。2. 清理系统日志CentOS 使用journald和logrotate管理日志。清理 journald 日志sudo journalctl --vacuum-size200M # 将日志总大小限制在200M以内 sudo journalctl --vacuum-time7d # 只保留最近7天的日志清理/var/log下的旧日志 使用logrotate通常会自动处理。也可以手动删除如messages-*secure-*cron-*等压缩过的旧日志文件。3. 清理 YUM/DNF 缓存sudo yum clean all # CentOS 7 # 或 sudo dnf clean all # CentOS 84. 处理已删除未释放的文件对于lsof | grep deleted找到的文件最根本的方法是重启持有该文件句柄的进程。如果无法重启可以尝试向该文件描述符写入空值风险较高# 例如对于上面例子中的 PID 1234, FD 11 sudo sh -c echo /proc/1234/fd/11务必谨慎确保你知道这个文件描述符对应的是什么盲目操作可能导致数据丢失或程序异常。3. 实战演练处理一台被 Docker 吃满的 CentOS 服务器假设我们收到警报一台 IP 为 192.168.1.100 的 CentOS 7.9 服务器根分区已满。我们通过 SSH 连接进行排查。3.1 第一阶段紧急登录与初步确认ssh root192.168.1.100 # 连接后首先确认磁盘状态 df -h输出可能如下Filesystem Size Used Avail Use% Mounted on /dev/vda1 50G 50G 0G 100% / devtmpfs 3.9G 0 3.9G 0% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 3.9G 8.6M 3.9G 1% /run tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup确认/dev/vda1使用率 100%可用空间为 0。接着检查 inodedf -i如果IUse%也是 100%问题就更复杂一些。我们先假设 block 用尽。3.2 第二阶段逐层定位大目录执行空间分析sudo du -h --max-depth1 / 2/dev/null | sort -hr | head -10输出示例45G / 30G /var 10G /home 4.0G /usr 1.5G /opt ... ...很明显/var目录是最大的嫌疑犯。进入/varsudo du -h --max-depth1 /var 2/dev/null | sort -hr | head -10输出示例30G /var 22G /var/lib 6.5G /var/log 1.2G /var/cache ... .../var/lib最大。继续深入这通常是 Docker 的老家sudo du -h --max-depth1 /var/lib 2/dev/null | sort -hr | head -10输出很可能显示22G /var/lib 18G /var/lib/docker 3.5G /var/lib/mysql ... ...至此我们高度怀疑 Docker。用 Docker 自带的命令验证docker system df -v这个命令的输出会非常清晰地列出所有镜像、容器、卷的详细占用。你可能会发现某个镜像特别大或者某个容器的日志文件Local Volumes部分可能不大但Containers部分的LOG SIZE很惊人。3.3 第三阶段执行安全清理在清理前强烈建议与业务负责人确认哪些容器和数据是重要的。假设我们确认可以清理无用的 Docker 资源。1. 清理悬虚镜像和停止的容器# 先列出所有停止的容器确认无误后再删除 docker ps -a --filter statusexited # 删除所有停止的容器 docker container prune # 删除所有未被使用的镜像悬虚镜像 docker image prune # 如果你想删除所有未被任何容器使用的镜像包括有标签的用 -a但要非常小心 # docker image prune -a2. 清理容器日志查看容器日志大小find /var/lib/docker/containers/ -name \*-json.log\ -exec ls -lh {} \\; | sort -hr -k5 | head -10如果发现有几个 G 的日志文件并且确认日志内容可以清理则使用 truncate# 安全做法先备份再清空可选 sudo sh -c cd /var/lib/docker/containers tar -czf /tmp/docker_logs_backup_$(date %Y%m%d).tar.gz ./*/*-json.log # 清空所有容器日志 sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log3. 清理构建缓存如果这台服务器用于构建 Docker 镜像缓存可能很大docker builder prune4. 清理系统其他部分顺手清理一下 YUM 缓存和 journal 日志sudo yum clean all sudo journalctl --vacuum-size200M3.4 第四阶段验证与长效配置清理完成后再次检查磁盘空间df -h应该能看到/dev/vda1的Avail列有了可观的空闲空间Use%大幅下降。配置 Docker 日志轮转防止复发 编辑或创建/etc/docker/daemon.jsonsudo vi /etc/docker/daemon.json输入以下内容{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 5, compress: true }, storage-driver: overlay2, storage-opts: [ overlay2.override_kernel_checktrue ] }max-size: 单个日志文件最大 10MB。max-file: 最多保留 5 个日志文件如-json.log,-json.log.1, ...,-json.log.4更旧的会被自动删除。compress: 启用压缩节省空间。 保存后重启 Docker 使配置生效sudo systemctl daemon-reload sudo systemctl restart docker为未来设置监控 可以考虑添加一个简单的 cron 任务定期检查磁盘使用率并发送告警如果已有监控系统如 Zabbix、Prometheus 则更好# 编辑 root 用户的 crontab sudo crontab -e # 添加一行每天上午8点检查如果根分区使用超过90%发送邮件需要配置邮件 0 8 * * * [ $(df -h / | awk NR2 {print $5} | tr -d %) -gt 90 ] echo \Warning: Root partition usage 90% on $(hostname)\ | mail -s \Disk Space Alert\ adminexample.com4. 深度解析Docker 存储驱动与空间管理要根治 Docker 的存储问题必须理解其存储驱动的工作原理。在 CentOS 7 上默认且推荐的存储驱动是overlay2。4.1 Overlay2 驱动如何工作overlay2使用联合文件系统。一个运行的容器其可写层由以下几部分组成镜像层只读 所有基础镜像和中间镜像层。这些层在多个容器和镜像间共享。容器层可写 一个薄薄的可写层位于所有只读层之上。容器内所有文件修改都发生在这里采用“写时复制”CoW机制。当你在容器内修改一个来自镜像的文件时overlay2会将整个文件从只读层复制到可写层然后进行修改。这意味着容器内频繁修改大文件会导致可写层迅速膨胀。典型的例子就是数据库容器的数据文件如果未挂载卷和应用程序日志如果写到容器内标准输出。4.2 Docker 数据根目录结构默认情况下Docker 的所有数据存放在/var/lib/docker。其子目录结构如下overlay2/ 存储驱动的工作目录包含所有镜像层和容器可写层的具体数据。这是空间占用的大头。containers/ 存放每个容器的配置、元数据和json-file驱动的日志文件*-json.log。volumes/ 存放 Docker 管理的命名卷的数据。image/ 存储镜像元数据。buildkit/ 构建缓存如果使用 BuildKit。docker system df命令的数据就来源于对这些目录的分析。4.3 高级清理技巧与陷阱1. 谨慎使用docker system prune -a --volumes这个命令是“核武器”。--volumes会删除所有未被容器引用的卷。在微服务架构中多个容器可能共享一个命名卷即使只有一个容器停止这个卷也可能被误判为“未使用”而被删除导致数据丢失。最佳实践是始终使用docker volume ls和docker volume inspect查看卷的详细信息和使用者。使用docker volume prune前仔细阅读其确认提示。为重要数据卷使用外部存储如 NFS、云盘或明确的备份策略。2. 镜像分层带来的空间浪费Docker 镜像采用分层结构。当你构建新镜像时每一层都会叠加。如果 Dockerfile 中包含了诸如yum install、apt-get update这样的命令并且没有在同一个 RUN 指令中清理缓存就会产生包含无用缓存数据的镜像层导致镜像臃肿。 优化 Dockerfile 是治本之策# 不好的例子会产生包含 apt 缓存层的镜像 RUN apt-get update RUN apt-get install -y package # 好的例子在同一条 RUN 指令中更新、安装、清理只产生一层 RUN apt-get update \\ apt-get install -y package \\ rm -rf /var/lib/apt/lists/*3. 使用多阶段构建对于编译型语言如 Go, Java在镜像中保留编译工具和中间文件会极大增加镜像尺寸。多阶段构建可以将编译环境和运行环境分离# 第一阶段构建阶段 FROM golang:1.19 AS builder WORKDIR /app COPY . . RUN go build -o myapp . # 第二阶段运行阶段 FROM alpine:latest WORKDIR /root/ COPY --frombuilder /app/myapp . CMD [\./myapp\]这样最终的镜像只包含轻量级的 Alpine 系统和编译好的二进制文件尺寸可能从几百 MB 减少到几十 MB。5. 预防与根治构建健康的存储管理体系救火之后更重要的是建立防火机制。以下是一些长效预防策略。5.1 合理的分区方案在新装系统时为/var和/home单独分区。/var是变动最频繁的目录日志、Docker、数据库将其独立出来可以避免根分区被塞满导致系统崩溃。一个建议的方案/boot: 1G/: 20-30G (系统文件)/var: 50-100G 或更多 (日志、Docker、应用数据)/home: 剩余空间 (用户数据)swap: 根据内存大小设定对于已经部署的系统如果使用 LVM可以在线扩展/var分区。如果是云服务器可以扩容系统盘并扩展分区操作复杂且有风险需备份。5.2 配置系统日志轮转除了 Docker系统服务如 Nginx, MySQL也会产生大量日志。确保/etc/logrotate.d/下相关配置合理。例如一个强力的 Nginx 日志轮转配置/etc/logrotate.d/nginx/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 640 nginx adm sharedscripts postrotate if [ -f /var/run/nginx.pid ]; then kill -USR1 cat /var/run/nginx.pid fi endscript }这个配置会每天轮转日志保留14天并进行压缩。5.3 监控与告警自动化监控是运维的眼睛。即使是最简单的 Shell 脚本监控也比人工发现要快。使用df和du编写监控脚本 如前文 cron 示例定期检查磁盘使用率和特定目录大小。集成到现有监控系统 如果使用 Zabbix、Prometheus Node Exporter可以轻松配置关于磁盘使用率、inode 使用率的告警规则。告警阈值建议设置在 85%为应急处理留出时间。监控 Docker 磁盘使用 Prometheus 的cAdvisor可以监控容器资源但对其宿主机存储的详细监控仍需结合 node_exporter。5.4 定期维护任务将清理工作自动化加入 crontab# 每周日凌晨3点清理 Docker 无用资源不包含卷 0 3 * * 0 docker system prune -f # 每月1号凌晨2点清理 journal 日志只保留1个月 0 2 1 * * journalctl --vacuum-time30d # 每周一凌晨1点清理 YUM 缓存 0 1 * * 1 yum clean all注意docker system prune -f是强制非交互式执行使用时要确保不会误删重要资源。对于生产环境更推荐使用docker image prune -a --filter \until24h\这类更精确的命令只清理24小时前的悬虚镜像。6. 疑难杂症与进阶排查即使遵循了上述所有步骤你仍可能遇到一些古怪的问题。这里记录几个我踩过的坑和对应的解决方案。6.1 空间已释放但df显示未变化现象你用rm删除了一个巨大的文件比如 Docker 日志du命令显示目录大小已减小但df命令显示磁盘使用率丝毫未变。原因 删除该文件的进程仍然活着并持有该文件的打开句柄。在 Linux 中文件描述符才是文件的真正引用。只要还有进程持有打开的描述符即使目录项被删除rm该文件占用的数据块也不会被释放。解决方案找到持有该文件的进程sudo lsof | grep deleted。找到对应的 PID 和 COMMAND。最干净的方法是重启该进程。如果是在容器内重启容器即可。如果绝对不能重启可以尝试清空文件内容风险高sudo sh -c echo /proc/PID/fd/FD。这会将文件截断为0字节空间会立即释放。务必确认你清空的是正确的文件描述符。6.2docker system df与du统计结果对不上现象docker system df报告 Docker 使用了 20G但sudo du -sh /var/lib/docker显示可能有 25G。原因 这通常是正常的。docker system df只计算活跃的镜像、容器和卷。而/var/lib/docker目录下还可能包含已删除但未被垃圾回收的镜像层Docker 17.06.0 有自动垃圾回收。构建缓存buildkit目录。旧的、未清理的存储驱动数据比如从devicemapper迁移到overlay2后残留的。一些内部元数据目录。解决方案 运行docker system prune -a可以清理一部分。对于更顽固的残留在确保所有容器已停止且数据已备份的前提下可以尝试更激进的方法停止 Docker 服务备份整个/var/lib/docker目录然后删除它再启动 Docker。Docker 会重建必要的目录结构但这会丢失所有本地镜像和容器仅作为最后手段。6.3 根分区快满了但找不到任何大文件现象df -h显示/使用率 99%但sudo du -sh /*把所有子目录加起来可能只有总容量的一半。原因 除了之前提到的“已删除未释放文件”还有一种可能是文件系统元数据损坏或磁盘配额quota问题。极少数情况下文件系统错误会导致空间统计异常。解决方案检查文件系统错误在单用户模式或卸载分区后运行fsck -f /dev/vda1。警告此操作有风险务必先备份数据检查磁盘配额使用repquota或quota命令查看是否启用了用户/组配额并是否有用户用尽了配额。使用lsof检查所有打开的文件sudo lsof | awk {print $NF} | grep ^/ | sort | uniq -c | sort -rn | head -20可以查看哪些文件被最多进程打开也许某个小文件被无数进程打开占用了大量 inode。6.4 容器内进程占满磁盘但宿主机du查不到现象容器内的应用疯狂写日志到标准输出导致容器内df显示磁盘满但宿主机上docker system df显示容器占用空间不大在/var/lib/docker/overlay2/id里也找不到特大文件。原因 容器内看到的文件系统是联合挂载的视图。如果应用写文件到容器层这些数据会存在于容器的可写层/var/lib/docker/overlay2/id/diff。但如果应用是通过卷Volume或绑定挂载Bind Mount的方式写入数据那么数据会直接写到宿主机的对应路径完全绕过 Docker 的存储驱动管理。解决方案首先在容器内使用df -h和du -sh /path/to/write定位容器内哪个路径满了。然后在宿主机上使用docker inspect container_name查看容器的挂载信息Mounts部分。找到与容器内写满路径对应的宿主机源路径Source。最后在宿主机上对那个源路径进行du分析。清理工作也应在宿主机上进行。处理磁盘空间问题尤其是与 Docker 相关的是一个需要耐心、细致和系统化思维的过程。从紧急排查的命令组合到理解存储驱动原理再到建立长效预防机制每一步都考验着运维人员对系统底层的理解。记住预防永远比治疗更重要。合理的分区规划、严格的日志管理、定期的清理任务和有效的监控告警能将“磁盘爆满”这个高频故障扼杀在萌芽状态。
返回列表