对于磁盘的监控属于最基础的监控,但是很多时候往往因为运维的疏忽而忽略监控磁盘,最终导致事故发生。希望读到这篇文章的朋友,一定要把监控磁盘这件事重视起来。
本案例需求如下:
1)每分钟检测一次磁盘状况
2)当磁盘空间使用率或者iNode使用率高于90%,需要发邮件告警,假设收件邮箱为[email protected]
3)统计使用率超过90%的分区所有子目录的大小,并把排名前三的子目录写到邮件内容中发给上面的邮箱
4)第一次告警后,如果我们没有及时处理,则需要每隔30分钟告警一次
5)每分钟脚本执行时,需检测该脚本是否已执行完,如果没有完成则本次不执行
知识点一:查看磁盘使用
命令:df
df查看已挂载磁盘的总容量、使用容量、剩余容量等,可以不加任何参数,默认是以k为单位显示的,常用选项有-i,-h,-k,-m。-i选项查看iNode使用状况,-h选项会使用合适的单位显示,例如G或M,-k、-m选项分别以K、M为单位显示。
# df -h 文件系统 容量 已用 可用 已用% 挂载点 /dev/sda3 16G 8.0G 7.9G 51% / devtmpfs 903M 0 903M 0% /dev tmpfs 912M 0 912M 0% /dev/shm tmpfs 912M 8.7M 903M 1% /run tmpfs 912M 0 912M 0% /sys/fs/cgroup /dev/sda1 197M 113M 85M 58% /boot tmpfs 183M 0 183M 0% /run/user/0
第一列是分区名字,第二列为该分区总共的容量,第三列为已经使用了多少,第四列为还剩下多少,第五列为已经使用的百分比,最后一列为挂载点。
知识点二:查看目录或文件大小
命令:du
du命令用来查看某个目录或文件所占空间的大小。
语法:du [-abckmsh] [文件名或目录名]
常用参数:
-a:表示全部文件和目录的大小都列出来。若后面不加任何选项和参数。则只会列出目录(包含子目录)的大小。若du命令不指定单位的话,默认显示单位为KB。
示例命令:
# du /tmp/test 0 /tmp/test # du -a /tmp/test 0 /tmp/test/1.txt 0 /tmp/test
-b:表示列出的值以B为单位输出。
-k:表示以KB为单位输出,这和默认不加任何选项的输出值是一样的。
-m:表示以MB为单位输出。
-h:表示系统自动调节单位。
-c:表示最后加总。不常用,示例命令:
# du -c /tmp/test 0 /tmp/test 0 总用量
-s:表示只列出总和。常用选项,示例命令:
# du -s /tmp/test 0 /tmp/test
常用的用法:du -sh filename
结合find查找某个目录下所有子目录,并统计大小,命令为:
# find /dir/ -type d |sed '1d' |xargs du -sm
说明:用sed '1d'删除第一行,原因是第一行是目录本身,而我们要统计的是子目录。
知识点三:查看进程
在Windows下可以进入任务管理器查看进程,在Linux下有一些命令也可以查看进程。
1)top
这个命令用于动态监控进程所占系统资源,每隔3秒变一次。该命令的特点是把占用系统资源(CPU、内存、磁盘IO等)最高的进程放到最前面。
top命令打印出了很多信息,包括系统负载(load average)、进程数(Tasks)、CPU使用情况、内存使用情况以及交换分区使用情况。
top重点查看的还是下面的进程使用系统资源详细情况。这部分东西反映的还是比较多,不过需要关注的也就几项:RES,%CPU,%MEN,COMMAND。RES为进程所占内存大小,%CPU为进程使用CPU百分比。COMMAND为具体进程名字。
在top状态下,按M可按内存使用大小排序,按P切回CPU排序,按数字1可列出每颗CPU的使用状态。常用的一个命令为:top -bn1,表示静态打印系统资源使用情况,适合用在shell脚本中。top还有一个-c选项,可以显示具体的命令,也就是说在COMMAND这一列出更加详细。
2)ps
ps命令用来汇报当前系统进程的状况,常见用法有ps aux和ps -elf。
在本例中,我们检查某个进程是否存在,可以用:
# ps aux |grep '进程名'
假如,本脚本的名字为mon_disk.sh,则需要这样统计:
# ps aux |grep 'mon_disk.sh' |grep -vE "$$|grep"
说明:这里的$$为本进程PID ,之所以要排除掉它,是因为我们需要检查之前的旧进程而不是本次的进程,并且需要把grep这个进程也排除掉。
知识点四:告警收敛思路分析
本案例中有要求,如果发生告警后,下次再告警应该是30分钟后。脚本本应该是一分钟执行一次,告警邮件也会一分钟发一次,告警邮件也会一分钟发一次,如果我们不能在短时间内修复完问题,则会造成邮件骚扰。
这里的思路是引入一个计数器,而且需要考虑以下几个场景:
1)脚本从来没有告警过,第一次告警
这种情况,不用考虑太多东西,直接发邮件,但需要做两件事,第一需要记录此时的时间戳到一个临时文件中,第二需要建立一个临时文件记录告警次数,告警发生但不一定发邮件,要区分差异。
2)脚本之前告警过,距离上一次告警超过30分钟
判断距离上一次告警多久需要借助记录时间戳的临时文件,求本次告警时间戳和上一次告警时间戳的差值,是否大于1800秒。根据需求,只要大于30分钟就可以立即发邮件,同时记录时间戳和告警次数到两个不同的临时文件中。
3)脚本之前告警过,距离上一次告警不超过30分钟
不超过30分钟则需要查看记录告警次数的临时文件,只有次数大于等于30才会再一次发邮件。
本案例参考脚本
#!/bin/bash ##监控磁盘使用情况,并做告警收敛(30分钟发一次邮件) ##作者: ##日期: ##版本:v0.1 #把脚本名字存入变量s_name s_name=`echo $0 |awk -F '/' '{print $NF}'` #定义收件人邮箱 [email protected] #定义检查磁盘空间使用率函数 chk_sp() { df -m |sed '1d' |awk -F '%| +' '$5>90 {print $7,$5}'>/tmp/chk_sp.log n=`wc -l /tmp/chk_sp.log|awk '{print $1}'` if [ $n -gt 0 ] then tag=1 for d in `awk '{print $1}' /tmp/chk_sp.log` do find $d -type d |sed '1d' |xargs du -sm |sort -nr|head -3 done > /tmp/most_sp.txt fi } #定义检查iNode使用率的函数 chk_in() { df -i |sed '1d'|awk -F '%| +' '$5>90 {print $7,$5}'>/tmp/chk_in.log n=`wc -l /tmp/chk_in.log|awk '{print $1}'` if [ $n -gt 0 ] then tag=2 fi } #定义告警函数(这里的mail.py是案例二中的那个脚本) m_mail(){ log=$1 #此处的$1表示第一个函数chk_sp t_s=`date +%s` t_s2=`date -d "1 hours ago" +%s` if [ ! -f /tmp/$log ] then #创建$log文件 touch /tmp/$log #增加a权限,只允许追加内容,不允许更改或删除 chattr +a /tmp/$log #第一次告警,可以直接写入1小时以前的时间戳 echo $t_s2 >> /tmp/$log fi #无论$log文件是否是刚刚创建,都需要查看最后一行的时间戳 t_s2=`tail -1 /tmp/$log|awk '{print $1}'` #取出最后一行即上次告警的时间戳后,立即写入当前的时间戳 echo $t_s>>/tmp/$log #取两次时间戳差值 v=$[$t_s-$t_s2] #如果差值超过1800,立即发邮件 if [ $v -gt 1800 ] then #发邮件,其中$2为mail函数的第二个参数,这里为一个文件 python mail.py $mail_user "磁盘使用率超过90%" "`cat $2`" 2>/dev/null #定义计数器临时文件,并写入0 echo "0" > /tmp/$log.count else #如果计数器临时文件不存在,需要创建并写入0 if [ ! -f /tmp/$log.count ] then echo "0" > /tmp/$log.count fi nu=`cat /tmp/$log.count` #30分钟内每发生一次告警,计数器加1 nu2=$[$nu+1] echo $nu2>/tmp/$log.count #当告警次数超过30次,需要再次发邮件 if [ $nu2 -gt 30 ] then python mail.py $mail_user "磁盘使用率超过90%持续30分钟了" "`cat $2`" 2>/dev/null #第二次告警后,将计数器再次从0开始 echo "0" > /tmp/$log.count fi fi } #把进程情况存入临时文件,如果加管道求行数会有问题 ps aux |grep "$s_name" |grep -vE "$$|grep">/tmp/ps.tmp p_n=`wc -l /tmp/ps.tmp|awk '{print $1}'` #当进程数大于0,则说明上次的脚本还未执行完 if [ $p_n -gt 0 ] then exit fi chk_sp chk_in if [ $tag == 1 ] then m_mail chk_sp /tmp/most_sp.txt elif [ $tag == 2 ] then m_mail chk_in /tmp/chk_in.log fi