一、问题现象
最近在生产系统环境对web服务器集群批量执行任务时发现几台服务器无法完成执行任务。该批量任务执行的前提是需要从管理机器上通过scp命令上传脚本文件到目标服务器上执行,而在执行到对应目标服务器时提示目标服务器没有可用磁盘空间。

随机选择其中一台执行任务失败的服务器,查看磁盘容量有可用剩余空间(批量任务上传文件的目标目录是root目录),而root目录是挂载在系统根目录下,系统根目录使用率仅51%。但是在root目录下尝试创建test.txt文件,也是提示没有可用磁盘空间。

磁盘容量有剩余空间,但是为什么无法创建新的文件?是否磁盘有故障?
通过命令“dmesg | grep -E "sd[a-z]|error|media|reset|timeout"查看内核日志没有相关磁盘报错:

查看文件系统类型是ext3

查看inode索引节点使用情况

根因找到了,这就是典型的常规手段看磁盘容量剩余,但inode使用率占满,导致无法创建新文件的问题。
扩展点:linux系统通常对磁盘分区进行文件系统格式化时(这里以文中的ext3为例),大致会预先分配inode存储空间和数据存储空间。inode是用于存储对应文件的除文件名以外的大小、权限、属主、数据块指针等元数据,一个文件对应一个inode。如果inode使用率100%,即使磁盘数据空间还有剩余也无法新创建文件。一般导致inode空间使用率占满的根本原因是系统创建了大量的小文件,通常都是百万、万千量级的小文件,这些大量小文件基本都是日志文件尤其是系统或者程序异常日志、系统邮件日志导致。
二、如何查找大量文件目录
问题根因找到了,如何查找产生大量文件的目录?
通常容易产生大量日志文件的目录主要有/tmp、/var/tmp、/var/log、/run /var/spool等,也有可能结合你实际应用配置场景。
本案例采用如下命令查找哪些目录下产生文件数量多:
for dir in /tmp /var/tmp /var/log /run /var/spool ;do echo "==== $dir ====";find $dir -xdev 2>/dev/null | wc -l;done
循环到/var/spool目录一直无法输出,说明目录下肯定有很多文件,继续查找,最后定位到/var/spool/postfix/maildrop目录很难输出find命令结果,就是它了,重点排查此目录。

最后定位到/var/spool/postfix/maildrop目录,基本知道情况了:这个目录linux系统内部邮件投递队列目录,如果有的系统任务大量产生系统邮件,每次就会产生一个文件。
通过如下命令查看系统进程
ps -ef | grep sendmail | wc -l #统计sendmail进程数量ps -ef | grep postdrop | wc -l #统计postdrop进程数量ps -ef | grep cron| wc -l #统计任务进行数量

发现有大量的sendmail、postdrop、cron进程,这些进程在大量产生系统邮件。
扩展点:
1.postdrop是Postfix(Linux最常用MTA邮件服务)内部工具,不会由管理员手动执行,而是由sendmail兼容接口、系统程序、cron、应用自动调用。其作用是把待发送邮件写入邮件投递队列目录/var/spool/postfix/maildrop/。
2.Linux默认在处理cron等程序的stdout/stderr信息,会尝试以邮件发给系统管理员root,系统会调用/usr/sbin/sendmail,随后sendmail接着拉起/usr/sbin/postdrop -r,postdrop在/var/spool/postfix/maildrop/创建独立小文件。
三、处理方法
通过前期查找到产生大量文件的目录/var/spool/postfix/maildrop/,那么目标就很简单,就是删除目标目录里的文件即可。但是本文涉及的问题场景,因为有大量的sendmail、postdrop、cron进程在运行同时一直不断的产生邮件文件,直接使用rm命令删除/var/spool/postfix/maildrop/目录下的文件效果不理想。
第一步,先停掉sendmail和postfix这两个邮件进程,分别通过pkill命令停掉:
pkill -f "/usr/sbin/sendmail -FCronDaemon"pkill -f "postdrop -r"
第二步,再执行删除命令:
nohup find /var/spool/postfix/maildrop/ -type f -delete &注意:如果目录下有百万、千万级别的文件,一定不要直接使用rm命令(可能会导致系统卡死),我在前期写过一篇如何删除大量文件的文章可以参考《linux删除大量文件的方法》。
第三步,不让sendmail进程发邮件:
mv/usr/sbin/sendmail /usr/sbin/sendmail.bak #对sendmail程序改名ln-s /usr/bin/true /usr/sbin/sendmail #任何程序调用sendmail 直接返回,不再启动postdrop,不再新建maildrop文件。如果后续需要恢复邮件功能,直接改名回来即可。
第四步,结合cron任务只要有stdout/stderr输出就会自动发系统邮件的逻辑。梳理系统里的定时任务脚本,在定时任务后面一定要加上重定向“>/dev/null 2>&1”。
比如:*/5 * * * * /opt/test.sh > /dev/null 2>&1 (一定加上重定向/dev/null 2>&1)另外,也可以结合情况,关停掉系统的邮件服务比如postfix服务。
四、总结
1. 通常我们在对linux磁盘的时候往往只关注命令“df -h”的结果(磁盘数据分区容量使用情况),实际工作中还需要关注命令“df -i”的结果(查看磁盘inode使用情况)。这俩,只要有其中一个是占满,就会导致系统无法生成新的文件。
2. 系统单条定时任务crontab,最后一定要加上重定向“>/dev/null 2>&1”;或者可以在“crontab -e”里面文件顶部添加“MAILTO=""”,让cron不再尝试发送任何邮件。
3. 操作系统里面postfix邮件服务停掉了,不代表postdrop进程不再产生邮件(我这里的环境就是),因为系统的定时任务、脚本等都会产生stdout/stderr信息发送系统root邮件。