硬盘空间消失之谜:Linux 服务器存储排查与优化全过程
<h2 id="前言">前言</h2><p>最近线上服务经常出现一些奇奇怪怪的问题,比如网页上的静态资源加载不出来,或者请求后端莫名报错,又或者 Redis 报错…</p>
<p>当我 SSH 登录到服务器上时,更不对劲了,敲个命令都卡顿…</p>
<p>如果是以前没经验,或许会以为遇到了疑难杂症,但作为多年的 Linux 用户,我已经知道了这种种异常的背后是「存储空间已满」在作祟!</p>
<p>那么问题就来到了「硬盘空间去哪儿了?」</p>
<h2 id="排查">排查</h2>
<h3 id="先用-df-看个大概">先用 df 看个大概</h3>
<p>首先是最常用的命令</p>
<pre><code class="language-bash">df -h
</code></pre>
<p>先来看看各个磁盘和挂载点的情况</p>
<p>在我的服务器上执行这个命令之后发现,根目录的可用空间已经只剩下几百 KB 了…</p>
<h3 id="再用-du-看具体">再用 du 看具体</h3>
<p>使用 du 命令可以查看各个子目录占用的空间,然后再结合 sort 命令排个序</p>
<pre><code class="language-bash">sudo du -h --max-depth=1 / | sort -hr
</code></pre>
<p>参数说明:</p>
<ul>
<li>因为要直接统计根目录,所以需要有 root 权限</li>
<li><code>--max-depth=1</code>:仅显示当前目录及其下一级子目录的占用情况。</li>
<li><code>sort -hr</code>:按照大小进行降序排列。</li>
</ul>
<p>查看目录的总占用空间:</p>
<pre><code class="language-bash">du -sh /
</code></pre>
<h2 id="更好的工具">更好的工具</h2>
<p>前面的 du 命令,还是没那么好用,主要是列出来的数据太多了。</p>
<p>这次我用上了 ncdu 工具</p>
<blockquote>
<p><code>ncdu</code> 是一个更加用户友好的磁盘使用分析工具,支持交互式界面。</p>
</blockquote>
<p>不过很多发行版没有内置,需要先安装:</p>
<pre><code class="language-bash">sudo apt install ncdu
</code></pre>
<h3 id="使用-ncdu-分析根目录占用">使用 ncdu 分析根目录占用</h3>
<pre><code class="language-bash">sudo ncdu -x /
</code></pre>
<p>使用 <code>-x</code> 参数可以限制扫描范围为当前文件系统,不跨越挂载点。因为根目录下有很多其他硬盘的挂载点,根据前面使用 df 命令的分析,是主硬盘满了,所以我只要看主硬盘的就行。</p>
<p>ncdu 启动之后会扫描各个文件的存储空间,然后进入一个交互式界面,可以很直观的看到各个目录的占用空间大小,从大到小排序。</p>
<h2 id="罪魁祸首">罪魁祸首</h2>
<p>在 ncdu 里可以很直观看到 <code>/var/lib/docker</code> 这个目录占用了 70% 以上的存储空间,妥妥的答辩啊!</p>
<p>使用 du 分析</p>
<pre><code class="language-bash">sudo du -h --max-depth=1 /var/lib/docker | sort -hr
</code></pre>
<p>之后大概的占用情况是这样(只是例子,不是真实数据)</p>
<pre><code class="language-c">10G /var/lib/docker/overlay2
5G /var/lib/docker/volumes
1G /var/lib/docker/containers
500M /var/lib/docker/images
</code></pre>
<p>基本就是 docker 的镜像、容器日志、卷之类的把硬盘吃掉了</p>
<h2 id="清理-docker-的未使用资源">清理 Docker 的未使用资源</h2>
<p>找到了问题,那就好办了</p>
<p>首先把没用的 docker 容器停掉</p>
<p>然后执行一下 docker 提供的一些清理命令。</p>
<h3 id="清理未使用的资源镜像容器卷和网络">清理未使用的资源(镜像、容器、卷和网络)</h3>
<p>Docker 提供了 <code>docker system prune</code> 命令,能清理未使用的资源。</p>
<pre><code class="language-bash">docker system prune -a
</code></pre>
<ul>
<li><code>-a</code>:删除所有未使用的镜像(包括没有关联到容器的镜像)。</li>
<li><strong>注意</strong>:这个命令不会删除被正在运行的容器依赖的资源,请小心操作。</li>
</ul>
<h3 id="仅清理未使用的卷">仅清理未使用的卷</h3>
<p>如果是 <code>/var/lib/docker/volumes</code> 占用空间较多:</p>
<pre><code class="language-bash">docker volume prune
</code></pre>
<h3 id="仅清理未使用的网络">仅清理未使用的网络</h3>
<p>如果是 <code>/var/lib/docker/network</code> 占用较多:</p>
<pre><code class="language-bash">docker network prune
</code></pre>
<h2 id="删除不需要的容器镜像和卷">删除不需要的容器、镜像和卷</h2>
<h3 id="删除未运行的容器">删除未运行的容器</h3>
<pre><code class="language-bash">docker container prune
</code></pre>
<h3 id="删除无用的镜像">删除无用的镜像</h3>
<pre><code class="language-bash">docker image prune -a
</code></pre>
<h3 id="删除无用的卷">删除无用的卷</h3>
<pre><code class="language-bash">docker volume prune
</code></pre>
<h2 id="清理旧的镜像和未使用的标签">清理旧的镜像和未使用的标签</h2>
<p>如果在使用大量镜像,很多旧版本可能已经没有用了。</p>
<h3 id="列出镜像按大小排序">列出镜像按大小排序</h3>
<pre><code class="language-bash">docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | sort -k2 -h
</code></pre>
<h3 id="删除指定镜像">删除指定镜像</h3>
<pre><code class="language-bash">docker rmi <image-id>
</code></pre>
<h2 id="啥玩意这么大">啥玩意这么大?</h2>
<p>在分析存储空间的占用过程中,我还发现有个文件特别离谱,下面这个文件,500多个G…</p>
<pre><code class="language-bash">/var/lib/docker/containers/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df-json.log
</code></pre>
<h3 id="找出拉屎的容器">找出拉屎的容器</h3>
<p>来看看是哪个容器拉的屎。</p>
<p>日志文件的路径中包含了容器的 ID:<code>e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df</code></p>
<p>来找一下是哪个容器:</p>
<pre><code class="language-bash">docker ps | grep e4b5a99b429a
</code></pre>
<p>在容器列表中找不到该容器,可能它已经被停止或删除了。在这种情况下,可以使用 <code>docker inspect</code> 检查具体信息:</p>
<pre><code class="language-bash">docker inspect e4b5a99b429a
</code></pre>
<h3 id="分析日志内容">分析日志内容</h3>
<p>可以通过 <code>tail</code> 或 <code>less</code> 查看日志内容,检查是否有异常输出:</p>
<pre><code class="language-bash">sudo tail -n 50 /var/lib/docker/containers/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df-json.log
</code></pre>
<p>日志的具体内容我就不贴了,看起来应该没啥问题,就是运行久了,日积月累…</p>
<p>处理这个问题的方法,见下一小节。</p>
<h2 id="清理-docker-日志文件">清理 docker 日志文件</h2>
<p>如果 <code>/var/lib/docker/containers</code> 占用大量空间,可能是容器日志文件过大。</p>
<h3 id="查看日志文件">查看日志文件</h3>
<p>每个容器的日志存储在 <code>/var/lib/docker/containers/<container-id>/<container-id>-json.log</code>。</p>
<p>使用以下命令找到最大的日志文件:</p>
<pre><code class="language-bash">sudo find /var/lib/docker/containers/ -type f -name "*.log" -exec du -h {} + | sort -hr | head -n 10
</code></pre>
<h3 id="手动清理日志">手动清理日志</h3>
<p>清空一个特定容器的日志文件:</p>
<pre><code class="language-bash">sudo truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log
</code></pre>
<h3 id="设置日志文件大小限制">设置日志文件大小限制</h3>
<p>在 Docker 的配置文件中限制日志大小(推荐):</p>
<ol>
<li>
<p>编辑 Docker 配置文件(通常是 <code>/etc/docker/daemon.json</code>):</p>
<pre><code class="language-bash">sudo nano /etc/docker/daemon.json
</code></pre>
</li>
<li>
<p>添加或修改以下配置:</p>
<pre><code class="language-json">{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
</code></pre>
<ul>
<li><code>max-size</code>:单个日志文件最大 10 MB。</li>
<li><code>max-file</code>:保留 3 个日志文件。</li>
</ul>
</li>
<li>
<p>重新加载 Docker:</p>
<pre><code class="language-bash">sudo systemctl restart docker
</code></pre>
</li>
</ol>
<h2 id="迁移-varlibdocker-到新磁盘">迁移 /var/lib/docker 到新磁盘</h2>
<p>可以将 <strong><code>/var/lib/docker</code></strong> 挂载到其他磁盘,从而缓解当前磁盘的存储压力。这是一个常见的做法,尤其在有多块磁盘的情况下。</p>
<h3 id="方法一直接迁移-varlibdocker-到新磁盘">方法一:直接迁移 <code>/var/lib/docker</code> 到新磁盘</h3>
<h4 id="停止-docker-服务">停止 Docker 服务</h4>
<p>在迁移数据之前,需要先停止 Docker 服务:</p>
<pre><code class="language-bash">sudo systemctl stop docker
</code></pre>
<h4 id="移动-varlibdocker-到新位置">移动 <code>/var/lib/docker</code> 到新位置</h4>
<p>假设新磁盘挂载在 <code>/mnt/new-disk</code>,执行以下命令:</p>
<pre><code class="language-bash">sudo mv /var/lib/docker /mnt/new-disk/docker
</code></pre>
<h4 id="创建软链接">创建软链接</h4>
<p>将新的路径链接回 <code>/var/lib/docker</code>,让 Docker 继续按默认路径工作:</p>
<pre><code class="language-bash">sudo ln -s /mnt/new-disk/docker /var/lib/docker
</code></pre>
<h4 id="启动-docker-服务">启动 Docker 服务</h4>
<pre><code class="language-bash">sudo systemctl start docker
</code></pre>
<h4 id="验证">验证</h4>
<p>运行以下命令确保 Docker 正常工作:</p>
<pre><code class="language-bash">docker info
</code></pre>
<h3 id="方法二修改-docker-配置文件指定新存储位置">方法二:修改 Docker 配置文件,指定新存储位置</h3>
<h4 id="停止-docker-服务-1">停止 Docker 服务</h4>
<pre><code class="language-bash">sudo systemctl stop docker
</code></pre>
<h4 id="移动-varlibdocker-到新磁盘">移动 <code>/var/lib/docker</code> 到新磁盘</h4>
<p>将现有数据迁移到新磁盘挂载点。例如,新磁盘挂载在 <code>/mnt/new-disk</code>:</p>
<pre><code class="language-bash">sudo mv /var/lib/docker /mnt/new-disk/docker
</code></pre>
<h4 id="修改-docker-配置">修改 Docker 配置</h4>
<p>编辑 Docker 的配置文件(通常是 <code>/etc/docker/daemon.json</code>),指定新的存储路径:</p>
<pre><code class="language-bash">sudo nano /etc/docker/daemon.json
</code></pre>
<p>添加或修改以下内容:</p>
<pre><code class="language-json">{
"data-root": "/mnt/new-disk/docker"
}
</code></pre>
<h4 id="启动-docker-服务-1">启动 Docker 服务</h4>
<pre><code class="language-bash">sudo systemctl start docker
</code></pre>
<h4 id="验证-1">验证</h4>
<p>再次检查 Docker 是否正常运行:</p>
<pre><code class="language-bash">docker info | grep "Docker Root Dir"
</code></pre>
<p>你应该能看到新的路径(如 <code>/mnt/new-disk/docker</code>)。</p>
<h3 id="方法三直接挂载新磁盘到-varlibdocker">方法三:直接挂载新磁盘到 <code>/var/lib/docker</code></h3>
<p>如果想直接将新磁盘作为 <code>/var/lib/docker</code> 的挂载点,可以使用以下方法</p>
<h4 id="格式化新磁盘">格式化新磁盘</h4>
<p>假设新磁盘为 <code>/dev/sdb1</code>,先格式化并创建文件系统(如 <code>ext4</code>):</p>
<pre><code class="language-bash">sudo mkfs.ext4 /dev/sdb1
</code></pre>
<h4 id="挂载新磁盘">挂载新磁盘</h4>
<p>将新磁盘挂载到 <code>/var/lib/docker</code>:</p>
<pre><code class="language-bash">sudo mount /dev/sdb1 /var/lib/docker
</code></pre>
<h4 id="迁移现有数据">迁移现有数据</h4>
<p>如果 <code>/var/lib/docker</code> 目录下已有数据,需要先复制到新磁盘:</p>
<pre><code class="language-bash">sudo rsync -a /var/lib/docker/ /mnt/new-disk/
</code></pre>
<p>然后再将新磁盘挂载回 <code>/var/lib/docker</code>。</p>
<h4 id="修改-etcfstab-确保开机自动挂载">修改 <code>/etc/fstab</code> 确保开机自动挂载</h4>
<p>编辑 <code>/etc/fstab</code> 文件,添加一行挂载配置:</p>
<pre><code class="language-bash">/dev/sdb1/var/lib/dockerext4defaults02
</code></pre>
<hr>
<h3 id="注意事项">注意事项</h3>
<ol>
<li>
<p><strong>数据迁移风险</strong>:在迁移或重建 <code>/var/lib/docker</code> 时,务必备份重要数据(如持久卷数据)。</p>
</li>
<li>
<p>权限问题:确保新目录的权限与原始目录一致:</p>
<pre><code class="language-bash">sudo chown -R root:root /mnt/new-disk/docker
</code></pre>
</li>
<li>
<p><strong>检查挂载点</strong>:确保新磁盘挂载成功,并设置自动挂载,避免系统重启后路径丢失。</p>
</li>
</ol>
<p>通过以上方法,可以成功将 <code>/var/lib/docker</code> 挂载到其他磁盘,缓解存储压力并优化存储布局。</p>
<h2 id="重建-varlibdocker">重建 /var/lib/docker</h2>
<p>如果清理后空间仍然不足,可以重建 Docker 的存储目录(会删除所有容器、镜像和数据)</p>
<p>停止 Docker 服务:</p>
<pre><code class="language-bash">sudo systemctl stop docker
</code></pre>
<p>备份现有 Docker 数据(可选):</p>
<pre><code class="language-bash">sudo mv /var/lib/docker /var/lib/docker.bak
</code></pre>
<p>创建一个新的空目录:</p>
<pre><code class="language-bash">sudo mkdir /var/lib/docker
</code></pre>
<p>启动 Docker 服务:</p>
<pre><code class="language-bash">sudo systemctl start docker
</code></pre>
<h2 id="总结">总结</h2>
<p>在这次 Linux 服务器硬盘空间消失问题的排查过程中,我经历了一次完整的存储分析和优化实战。</p>
<h4 id="关键步骤概括">关键步骤概括:</h4>
<ol>
<li><strong>初步排查存储占用情况</strong>
<ul>
<li>使用 <code>du</code> 和 <code>ncdu</code> 等工具,快速定位占用空间较大的目录。</li>
<li>发现 <code>/var/lib/docker</code> 目录占用了大量存储空间。</li>
</ul>
</li>
<li><strong>深入定位具体问题</strong>
<ul>
<li>找到具体的容器日志文件路径,通过容器 ID 确认了是哪个容器产生了大量日志。</li>
<li>使用 <code>docker inspect</code> 和 <code>docker logs</code>,进一步分析日志内容。</li>
</ul>
</li>
<li><strong>解决问题</strong>
<ul>
<li>清空了过大的容器日志文件,通过 <code>truncate</code> 命令立即释放空间。</li>
<li>修改 Docker 配置文件(<code>daemon.json</code>)限制了日志文件的大小,避免类似问题再次发生。</li>
</ul>
</li>
<li><strong>验证与优化</strong>
<ul>
<li>重启 Docker 服务后,验证了服务正常运行。</li>
<li>使用 <code>docker system prune</code> 清理了无用资源,并规划了日志管理策略。</li>
</ul>
</li>
</ol>
<h4 id="个人收获与思考">个人收获与思考:</h4>
<p>这次问题的解决让我深刻体会到以下几点:</p>
<ul>
<li><strong>系统监控的重要性</strong>:及时监控存储使用情况,可以避免问题扩大化。</li>
<li><strong>日志管理最佳实践</strong>:过度增长的日志文件是常见的存储占用原因,必须设置合理的日志大小限制。</li>
<li><strong>工具的高效使用</strong>:<code>du</code>、<code>ncdu</code> 和 Docker 命令等工具在排查问题中大大提升了效率。</li>
<li><strong>日常维护习惯</strong>:定期清理无用的容器资源(例如停止的容器、未使用的镜像),可以保持系统健康运行。</li>
</ul>
<p>这次实践不仅解决了磁盘空间问题,也让我对 Linux 系统管理和 Docker 的运维有了更深的理解。在未来的运维工作中,我将更加注重系统的监控与优化,提前预防类似问题的发生。</p>
</div>
<div id="MySignature" role="contentinfo">
微信公众号:「程序设计实验室」
专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。<br><br>
来源:https://www.cnblogs.com/deali/p/18612139
頁:
[1]