由於上級的工作安排,今年的5月份開始每天都需要做一些服務器信息的巡檢;
對方交接時,完全是通過手敲指令、手動記錄來實現的;
熟悉了一段時間,把流程和記錄方式優化了一下(依舊是手動);
后來聽說Y哥那邊在部署總行提供的新的監控系統,基本可以替代巡檢;
就打算堅持一下,不研究自動化巡檢了,等監控上線;
等來等去就等了4個月。。。。。
新的監控一直不能正式上線,告警很多很雜,項目組一直在做優化和過濾;
還好這4個月一直不太忙,每天花點時間巡檢好像也沒什么;
進入三季度最后一個月突然忙了起來;
決定不等了,研究一下腳本;
這篇隨筆就用來記錄和整理自動化服務器巡檢的實現過程吧。
自動化巡檢系統大致分為幾個部分:
1. 常規服務器巡檢腳本;
2. 定制服務器巡檢腳本;
3. 腳本的下發;
4. 巡檢信息的統一收集;
5. 巡檢信息每日自動發送到郵箱。
先上一張結構圖:

第一次做這種多服務器互相發送文件的系統真的是沒有經驗;
最一開始應該先考慮網絡結構,因為圖中的Windows管理機並不能和特殊網段服務器通信,導致后面加入特殊網段后,不得不用堡壘機下發腳本和收集結果,導致結構很混亂,不過還好,特殊網段服務器不算多;
如果一開始就用堡壘機作為下發和收集的核心,整體結構會清晰很多。
1. 常規服務器巡檢腳本
常規服務器巡檢腳本是在一台Linux服務器上編寫、測試,再上傳到Windows管理機的。需要巡檢的服務器每天定時去提供SFTP服務的Windows管理機上下載腳本,方便后續腳本更新,實現此功能的腳本(getshell.sh)如下:
#!/bin/bash # ----- 從SFTP下載巡檢腳本 ----- sftpip=XX.XXX.XX.XXX user=XXXXXXX downDir=/patrol/shell # ----- 隨機延時約0-30s后下載 ----- sleeptime=`expr $RANDOM / 1000` sleep $sleeptime sftp $user@$sftpip << EOF cd $downDir lcd /root/patrol get patrol.sh bye EOF
SFTP腳本無法像FTP一樣直接在腳本中寫入密碼,需要先將訪問者的公鑰提供給SFTP服務端,生成公鑰的方法我參考的是http://bbs.chinaunix.net/thread-508290-1-1.html
在Windows管理機上我使用的是FreeSSHD提供的SFTP服務,把公鑰放在軟件配置中指定的位置就可以了。
因為涉及到的服務器比較多,好幾十條公鑰,所以合成公鑰是用bat腳本實現的:
@echo off dir copy *.pub result.pub echo DONE pause
腳本中添加了一個隨機延時功能,原因是一開始同步操作多個服務器進行SFTP訪問時,出現了超過SFTP最大連接數的現象。因為每個服務器訪問SFTP的時間很短,因此隨機延時0-30s就夠了。其中$RANDOM是系統自帶的隨機數變量。
寫好腳本后在服務器的crontab中添加了一行定時任務,每天7點執行getshell.sh:
0 7 * * * /bin/bash /root/patrol/getshell.sh > /dev/null 2>&1
后面的 > /dev/null 2>&1 是不寫定時任務日志的意思,因為功能比較簡單,感覺沒必要寫日志。crontab的寫法參考的是菜鳥教程:https://www.runoob.com/w3cnote/linux-crontab-tasks.html
下面就是巡檢腳本(patrol.sh)的主體了:
#!/bin/bash PATH=/sbin:/usr/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games # ----- 巡檢腳本 V2.0 ----- # ----- 定義變量:上傳SFTP地址、用戶名、密碼、路徑 ----- cd /root/patrol sftpip=XX.XXX.XX.XXX user=XXXXXXX remoteDir=/patrol/upload # ----- 定義變量:本機信息、巡檢參數、時間參數 ----- localip=`ifconfig | grep 'XX.XXX.XX' | grep Bcast | awk '{print $2}' |awk -F: '{print $2}'` loadavg=`top -b -n 1 | grep 'load average' | awk '{print $11 $12 $13 $14 $15}'` cpuid=`top -b -n 1 | grep %Cpu | awk '{print $8 $9}'` diskfree=`df -h | awk '{print $5}'` memtotal=`free -l | grep Mem | awk '{print $2}'` memocp=`free -l | grep buffers/cache | awk '{print $3}'` memratio=`expr $memocp \* 100 / $memtotal` if [ $memratio -ge 80 ] then memwarn=內存過高!!! else memwarn=內存正常 fi date=$(date "+%Y%m%d") filename=$date"_"$localip.txt # ----- 主程序 ----- echo -e $date-----$localip-----$loadavg-----$cpuid-----$diskfree-----$memratio-----$memwarn >$filename # ----- 隨機延時約0-30s后上傳SFTP ----- sleeptime=`expr $RANDOM / 1000` sleep $sleeptime sftp $user@$sftpip << EOF cd $remoteDir put $filename bye EOF rm $filename
整個腳本中,比較難的就是各個巡檢項變量的定義,從上到下依次是本機IP、平均負載、CPU空閑、邏輯卷占用、內存總量、內存占用、內存占比,一共七項。shell中復雜變量的定義需要用··框起來,就是鍵盤上1左邊的點;
其中grep 'XXX'是為了抓取需要的行,awk '{print $X}'是為了抓取這一行內需要的位置,memratio這一項中的expr 'XXX'是為了用上面的memtotal和memocp兩個變量計算內存占比,然后加了一個內存是否過高的判斷;
主程序只有一行,就是把各個變量echo到一個文件中,文件以當天的日期_服務器IP.txt命名。echo -e的目的是激活轉義字符。
后面還是添加了延時和SFTP上傳,上傳后刪除掉了服務器上的文件,防止時間長了占用太多服務器磁盤。
寫完上述內容后,手動運行patrol.sh就可以實現功能了,但是添加了crontab定時任務后發現不生效:
30 7 * * * /bin/bash /root/patrol/patrol.sh > /dev/null 2>&1
一開始認為是crontab寫得有問題,后來發現是沒有在腳本中添加環境變量,即開頭的PATH這一行,參考的是:https://www.cnblogs.com/huskiesir/p/9970291.html
因為腳本中用到的變量比較多,所以干脆把系統提示的所有環境變量都扔進去了。。。
目前巡檢腳本的輸出結果是這樣的:
20211014-----XX.XXX.XX.XX-----average:0.18,0.11,0.09-----63.6id,-----Use% 1% 1% 10% 0% 17% 51% 59% 1% 9% 1% 33% 38% 27% 10% 5% 1% 0%-----24-----內存正常
只對內存的狀態進行了一個簡單的判斷,后續優化的過程中打算把每一項的狀態都判斷出來,這樣看巡檢結果就比較方便了,尤其是邏輯卷占用這塊,現在看着簡直頭疼,不過這也比之前每天點開一大堆服務器手動巡檢強太多了。
2. 定制服務器巡檢腳本
除了每台服務器都需要的常規項巡檢,某些服務器還需要針對某個服務或者某些定時生成的文件進行巡檢,實現起來其實就是一些特殊變量的定義,最后還是全部echo到一個txt中統一收集,下面一一進行列舉:
① 查看進程
custom_patrol=`ps -ef | grep XXX | grep XXX`
沒什么好說的,就是看一些進程在不在,輸出結果就是ps -ef的某一行。
② 查看文件
custom_patrol=`ls -l /某路徑 | grep 文件關鍵字`
也沒啥好說的,就是看一些文件在不在,grep后面可以跟一些日期變量啥的,可以抓取文件名中有日期的文件。一開始我還不知道腳本中不能用ll這個命令,查了一下才知道ll就是ls -l的縮寫。。。
③ 查看consul服務數
custom_patrol=`/某路徑/consul catalog services | wc -l`
查看consul微服務的個數,后面的 | wc -l 是計數的意思,不加的話會列出服務的列表。
④ telnet端口
telnet本來是一個很簡單的指令:
telnet XX.XXX.XX.XXX 某端口
但是用腳本實現起來就要稍微復雜一點了,因為telnet本身是類似於FTP、SSH、SFTP這類讓系統進入某個客戶端的指令。在腳本中,進去容易,出來就得靠EOF了,所以實現起來是這樣的:
custom_patrol=`telnet XX.XXX.XX.XXX 某端口 << EOF
exit
EOF`
沒錯EOF就是為了多加一個exit讓系統退出來。。。這樣的話就把telnet的情況定義成了變量,實現了端口檢測。
⑤ Redis數據庫狀態檢測
custom_patrol=`redis-cli -c -p 某端口 -h XX.XXX.XX.XXX -a '密碼'` << EOF cluster nodes ping exit EOF`
同樣用到了EOF格式,cluster nodes是為了檢查主從節點,ping是為了pong,exit是為了退出。
⑥ Oracle數據庫表空間占用查詢
custom_patrol=`su - oracle -c "sqlplus 用戶名/密碼 << EOF select round(sum(bytes)/1024/1024/1024,2)||'G' from user_segments where TABLESPACE_NAME = '表名'; exit
EOF
exit" | grep -E 'G|M' | grep -v Mining`
公司內的要求是Oracle數據庫只能用oracle用戶來啟動,因此這個變量的聲明首先要切換到oracle用戶,在su - oracle后加-c "指令"添加切換用戶后的指令,sqlplus的指令同樣用EOF格式。
因為進入sqlplus后會顯示一大堆東西,因此加了兩個grep來縮小一下范圍,一個是-E顯示含有G或者M的行,一個-v是不顯示含有Mining的行。
⑦ 文件下載測試
這個功能一開始是想把下載信息定義成變量,然后echo這個變量來顯示下載是否成功,后來感覺這樣操作太麻煩了,直接下載這個文件,然后用ls -l檢測文件是否存在(或是否為當天下載)即可。
curl -o download_test.zip http://XX.XXX.XX.XXX:某端口/路徑/文件名\&name=n.zip custom_patrol=`ls -lh | grep download_test.zip`
curl下載這行我一直沒跑出來,后來是讓開發小伙伴RCM幫忙改的,現在也沒太搞懂,有空再研究下。。。
目前用到的一些需要定制的巡檢基本就是這些。在echo某個多行變量到txt的時候,打開txt發現寫入的時候忽略了換行,參考https://blog.csdn.net/jxfgh/article/details/6757488在echo后加了一個while循環,實現了換行:
echo -e "$XXX" > result.txt | while read i do echo $i done
3. 腳本的下發和4. 巡檢信息的統一收集都已經在1. 常規服務器巡檢腳本的腳本中實現了,不再贅述。
5. 巡檢信息每日自動發送到郵箱
每天每台服務器的巡檢信息上傳到Windows管理機后,需要進行匯總、上傳FTP、歸檔這幾個動作,通過bat腳本實現:
@echo off copy nul all.tx for %%a in (*.txt) do type %%a >>all.tx && echo. >> all.tx ren all.tx *.txt ren all.txt %date:~0,4%%date:~5,2%%date:~8,2%.tmp del *.txt ren %date:~0,4%%date:~5,2%%date:~8,2%.tmp %date:~0,4%%date:~5,2%%date:~8,2%.txt ftp -i -s:"upload" move %date:~0,4%%date:~5,2%%date:~8,2%.txt D:\FTPhome\patrol\history_data del %date:~0,4%%date:~5,2%%date:~8,2%.txt
其中ftp命令文件upload:
open XX.XX.XX.XX 用戶名 密碼 lcd 本地路徑 cd FTP路徑 binary put *.txt bye
上傳到FTP后,下一步就是下載到辦公機然后通過郵件發送了,這個功能是通過Windows任務計划程序和Python實現的,首先通過定時任務啟動bat去調用兩個.py:
@echo off cd 本地路徑 python ftp_download.py python auto_email.py move %date:~0,4%%date:~5,2%%date:~8,2%.txt 本地路徑\history_data
ftp_download.py(參考:https://www.cnblogs.com/Dahlia/p/10551929.html)
#coding=utf-8 import os import time from ftplib import FTP # 引入ftp模塊 class MyFtp: ftp = FTP() def __init__(self,host,port=21): self.ftp.connect(host,port) def login(self,username,pwd): self.ftp.set_debuglevel(2) # 打開調試級別2,顯示詳細信息 self.ftp.login(username,pwd) print(self.ftp.welcome) def downloadFile(self,localpath,remotepath,filename): os.chdir(localpath) # 切換工作路徑到下載目錄 self.ftp.cwd(remotepath) # 要登錄的ftp目錄 self.ftp.nlst() # 獲取目錄下的文件 file_handle = open(filename,"wb").write # 以寫模式在本地打開文件 self.ftp.retrbinary('RETR %s' % os.path.basename(filename),file_handle,blocksize=1024) # 下載ftp文件 # ftp.delete(filename) # 刪除ftp服務器上的文件 def close(self): self.ftp.set_debuglevel(0) # 關閉調試 self.ftp.quit() if __name__ == '__main__': ftp = MyFtp('FTP地址') ftp.login('用戶名','密碼') date = time.strftime("%Y%m%d", time.localtime()) ftp.downloadFile('本地路徑','FTP路徑','%s.txt' % date) ftp.close()
auto_email.py(參考:https://www.cnblogs.com/yufeihlf/p/5726619.html)
#coding: utf-8 import time import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.header import Header #設置smtplib所需的參數 #下面的發件人,收件人是用於郵件傳輸的。 smtpserver = 'SMTP服務器' username = '用戶名' password = '密碼' #成功開啟IMAP/SMTP服務,在第三方客戶端登錄時,登錄密碼輸入以下授權密碼: #HLSNDVBIOUQEUURU sender = '發送者郵箱' receiver = '接收者郵箱' #若收件人為多個收件人 #receiver=['XXX@126.com','XXX@126.com'] date = time.strftime("%Y%m%d", time.localtime()) #subject = 'Automatic server patrol' #通過Header對象編碼的文本,包含utf-8編碼信息和Base64編碼信息。以下中文名測試ok subject = '服務器巡檢結果' subject = Header(subject, 'utf-8').encode() #構造郵件對象MIMEMultipart對象 #下面的主題,發件人,收件人,日期是顯示在郵件頁面上的。 msg = MIMEMultipart('mixed') msg['Subject'] = subject msg['From'] = '發件人' msg['To'] = receiver #收件人為多個收件人,通過join將列表轉換為以;為間隔的字符串 #msg['To'] = ";".join(receiver) #若無時間,就默認一般為當前時間,該值一般不設置 #msg['Date']='2012-3-16' #構造文字內容 text = "今日巡檢結果" text_plain = MIMEText(text, 'plain', 'utf-8') msg.attach(text_plain) #構造圖片鏈接 # sendimagefile=open(r'D:\pythontest\testimage.png','rb').read() # image = MIMEImage(sendimagefile) # image.add_header('Content-ID','<image1>') # image["Content-Disposition"] = 'attachment; filename="testimage.png"' # msg.attach(image) #構造html #發送正文中的圖片:由於包含未被許可的信息,網易郵箱定義為垃圾郵件,報554 DT:SPM :<p><img src="cid:image1"></p> # html = """ # <html> # <head></head> # <body> # <p>Hi!<br> # How are you?<br> # Here is the <a href="http://www.baidu.com">link</a> you wanted.<br> # </p> # </body> # </html> # """ # text_html = MIMEText(html,'html', 'utf-8') # text_html["Content-Disposition"] = 'attachment; filename="texthtml.html"' # msg.attach(text_html) # 構造附件 sendfile=open('本地路徑\%s.txt' % date,'rb').read() text_att = MIMEText(sendfile, 'base64', 'utf-8') text_att["Content-Type"] = 'application/octet-stream' #以下附件可以重命名成aaa.txt(測試不成功) # text_att["Content-Disposition"] = 'attachment; filename="Python自動郵件附件測試.txt"' #另一種實現方式 text_att.add_header('Content-Disposition', 'attachment', filename = "%s.txt" % date) #以下中文測試不ok #text_att["Content-Disposition"] = u'attachment; filename="中文附件.txt"'.decode('utf-8') msg.attach(text_att) #發送郵件 smtp = smtplib.SMTP() smtp.connect(smtpserver) #我們用set_debuglevel(1)就可以打印出和SMTP服務器交互的所有信息。 #smtp.set_debuglevel(1) smtp.login(username, password) smtp.sendmail(sender, receiver, msg.as_string()) smtp.quit()
對兩個.py的理解比較少,基本上調試了一下能夠運行以后就沒有再改動了,其中還遇到了ftplib使用報錯和計算機名為中文導致郵件發送報錯兩個問題,分別參考這兩篇解決的:
https://blog.csdn.net/BobYuan888/article/details/82980817
https://blog.csdn.net/weixin_41278305/article/details/110943401
在內網環境安裝VSCode的離線插件參考了這兩篇:
https://blog.csdn.net/l508742729/article/details/103543755
https://blog.csdn.net/sinat_36188088/article/details/105203338
至此自動化服務器巡檢並且通過郵件發送巡檢結果就基本實現了,后續考慮繼續優化巡檢腳本,增加判斷語句,直接給出巡檢項是否正常的結果,減少人工查看的部分。
