CentOS 配置OOM監控報警

發表於2020-04-18

由於程式設計不合理或者瞬間高併發訪問時,很有可能會觸發OOM(Out of memory),這裡指的是作業系統級別的OOM。具體什麼是OOM,以及怎樣發生這裡不在贅述,因為筆者認為這是IT從業工作者的基本常識了。本篇主要記錄一下生產環境時對發生OOM的程式進行監控,便於我們及時發現以及事後問題的覆盤。
在做這個監控時,筆者也做了很多考察搜尋,幻想著會有那麼一兩個成熟的開源軟體能實現這個監控,事與願違,筆者並未找到這樣的工具,無奈之下,只好自己動手實現了一個略顯粗糙的程式來達到我的目的。
實現思路:

    Apr 18 12:11:25 php001 kernel: Out of memory: Kill process 13546 (php-fpm) score 31 or sacrifice child

系統每次觸發OOM時會在/var/log/message檔案下記下當時系統的執行狀況,以及被殺程式的pid和評分。因此筆者從此檔案入手,寫下了這樣一個指令碼。

1、目錄結構如下:
oom_monitor位於共享儲存NFS上,生產環境的機器都掛載了該NFS,因此所有機器上都會有這麼一個目錄

[admin@prod.001:/mnt/alinas]$ tree -L 1 oom_monitor/
oom_monitor/
├── bin                #存放可執行的指令碼檔案
└── log                #存放日誌檔案

[admin@prod.001:/mnt/alinas]$ tree -L 1 oom_monitor/bin/
oom_monitor/bin/
├── oom_check.sh       #過濾"Out of memory"從/var/log/message,並生成對應檔案儲存在log目錄下,用於後面的傳送警報
├── oom_dingding.py    #傳送OOM對應資訊到釘釘群
├── oom_mail.py        #傳送OOM對應資訊到郵箱
└── oom_send.sh        #用於觸發傳送報警資訊到釘釘和郵箱

2、具體內容如下:

[admin@prod.001:/mnt/nfs/oom_monitor/bin]$ cat oom_check.sh
#!/bin/sh
#獲取主機名
host_name=`hostname`

#定義獲取到的OOM日誌儲存位置
oom_scrape_file=/mnt/nfs/oom_monitor/log/oom_scrape_$host_name

#定義上次發生OOM時的對應資訊,用於去重,防止重複報警
old_oom_scrape_file=/mnt/nfs/oom_monitor/log/old_oom_scrape_$host_name

#獲取OOM報警資訊
msg=$(sudo grep -i "out of memory" /var/log/messages|awk 'END {print}')

#獲取報警產生的時間
time=`echo $msg | awk '{print $3}'`

#獲取被殺的程式型別(java/php/mysql/...)
killed=`echo $msg | awk 'END {print $12}'`

#獲取上次OOM的資訊
old_msg=`cat $old_oom_scrape_file`

#判斷兩次資訊是否相同,相同則不再記錄此次資訊,防止重複報警
if [ "$msg" == "$old_msg" ];then
 	exit 1
else

#如果兩次報警資訊不相同則把這次獲取的資訊覆蓋上次的資訊
	[ ! -z "$msg" ] && echo "$msg" > $old_oom_scrape_file
fi

#記錄此次資訊持久化到檔案
if [ ! -z "$msg" ];then
	echo > $oom_scrape_file
	echo -e "發生時間: $time" >> $oom_scrape_file
	echo -e "日誌資訊: $msg" >> $oom_scrape_file
	echo -e "被殺程式: $killed" >> $oom_scrape_file
	echo -e "發生主機: $host_name" >> $oom_scrape_file
fi


[admin@prod.001:/mnt/nfs/oom_monitor/bin]$ cat oom_send.sh
#!/bin/sh

#定義獲取到的OOM日誌儲存位置、OOM傳送報警指令碼位置
#這裡用*的原因是:機器很多,每臺機器一個檔案,所以用*
oom_scrape_file=/mnt/nfs/oom_monitor/log/oom_scrape*                
oom_warn_script=/mnt/nfs/oom_monitor/bin/oom_mail.py
oom_dingding_script=/mnt/nfs/oom_monitor/bin/oom_dingding.py
History_file=/mnt/nfs/oom_monitor/log/history_oom_scrape
basedir=/mnt/nfs/oom_monitor/log/
cd $basedir
for file in `ls $oom_scrape_file`
do
	hostname=`grep "發生主機" $file|awk '{print $2}'`
	warn_time=`grep "發生時間" $file |awk '{print $2}'`
	killed=`grep "被殺程式" $file |awk '{print $2}'`

    #傳送郵件報警,把日誌資訊全部發出去
	/usr/local/bin/python3 $oom_warn_script $file

    #傳送釘釘報警
	/usr/local/bin/python3 $oom_dingding_script $hostname $killed $warn_time

    #報警資訊發出後把日誌資訊追加到歷史資訊檔案中,然後刪除對應的oom資訊檔案,防止重複報警
	cat $file >> $History_file  && mv $file /tmp
done


[admin@prod.001:/mnt/nfs/oom_monitor/bin]$ cat oom_mail.py
#!/usr/bin/env python3
'''
當收到系統OOM時,觸發指令碼發出郵件報警資訊,資訊格式如下:

您的主機「hostname」發生OOM,具體資訊如下:
    /var/log/message中的資訊

'''
import os
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import time
import sys
def send_email(file_name):
    try:

        # 讀取測試報告中的內容作為郵件的內容
        with open(file_name, 'r', encoding='utf8') as f:
            mail_body = f.read()

        # 發件人地址
        send_addr = '此處替換為發件人的郵箱使用者名稱'

        # 收件人地址
        reciver_addr = ['接收人a的郵箱地址','接收人b的郵箱地址',]

        # 傳送郵箱的伺服器地址,這裡用的是阿里雲的
        mail_server = 'smtp.mxhichina.com'
        now = time.strftime("%Y-%m-%d %H:%M:%S")

        # 郵件標題
        subject = '[OOM報警觸發]' + now

        # 發件人的郵箱及郵箱密碼
        username = '此處替換為發件人的郵箱使用者名稱'
        password = '此處替換為發件人的郵箱密碼'

        # 郵箱的內容和標題
        message = MIMEText(mail_body, 'html', 'utf8')
        message['From'] = send_addr
        message['To'] = ','.join(reciver_addr)
        message['Subject'] = Header(subject, charset='utf8')

        # 傳送郵件,使用的使smtp協議
        smtp = smtplib.SMTP()

        #埠注意下,通常伺服器的25埠是關閉的,所以我這裡用了80、或者465也闊以
        smtp.connect(mail_server,80)
        smtp.login(username, password)
        smtp.sendmail(send_addr, message['To'].split(','), message.as_string())
        smtp.quit()
        print("郵件傳送成功!")
    except:
        print("傳送郵件失敗!")

send_email(file_name=sys.argv[-1])



[admin@prod.001:/mnt/nfs/oom_monitor/bin]$ cat oom_dingding.py
#!/usr/bin/env python3
import json
import requests
import datetime
import sys
def sendmessage(hostname,killed,warn_time):
    now_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

#替換成你自己的釘釘群的webhook
    url = 'https://oapi.dingtalk.com/robot/send?access_token=917b00b5faee51bcb67e862c13b0a0ff605f0f74f4f692c9a70fe32351'

    HEADERS = {
        "Content-Type": "application/json;charset=utf-8"
    }
    message='''
【OOM報警觸發】:
    報警主機:%s
    被殺程式:%s
    報警時間:%s
    ''' %(hostname,killed,warn_time)
    String_textMsg = {
        "msgtype": "text",
        "text": {"content": message},
        "at": {
            "atMobiles": [
                "110120119"  # 如果需要@某人,這裡寫他的手機號
            ],
            "isAtAll": 0  # 如果需要@所有人,這些寫1
        }
    }
    String_textMsg = json.dumps(String_textMsg)
    res = requests.post(url, data=String_textMsg, headers=HEADERS)
    print(res.text)
sendmessage(sys.argv[-3],sys.argv[-2],sys.argv[-1])

3、結果演示:

相關文章