Python自動(dòng)化運(yùn)維實(shí)戰(zhàn):從Linux系統(tǒng)中收集數(shù)據(jù)
使用Linux命令可以查看當(dāng)前系統(tǒng)狀態(tài)和運(yùn)行狀況的相關(guān)數(shù)據(jù)。然而,單個(gè)Linux命令和應(yīng)用程序只能獲取某一方面的系統(tǒng)數(shù)據(jù)。我們需要利用Python模塊將這些詳細(xì)信息反饋給管理員,同時(shí)生成一份有用的系統(tǒng)報(bào)告。
我們將報(bào)告分為兩部分。第一部分是使用platform模塊獲取的一般系統(tǒng)信息,第二部分是硬件資源,如CPU和內(nèi)存等。
首先從導(dǎo)入platform模塊開始,它是一個(gè)內(nèi)置的Python庫(kù)。platform模塊中有很多方法,它們可用來(lái)獲取當(dāng)前運(yùn)行Python命令的操作系統(tǒng)的詳細(xì)信息。
- import platform
- system = platform.system()
- print(system)
上述代碼的運(yùn)行結(jié)果如下。

該腳本返回當(dāng)前系統(tǒng)的類型,同樣的腳本在Windows系統(tǒng)上運(yùn)行會(huì)得到不同的結(jié)果。當(dāng)它在Windows系統(tǒng)上運(yùn)行時(shí),輸出結(jié)果就變成Windows。

常用的函數(shù)uname()和Linux命令(uname -a)的功能一樣:獲取機(jī)器的主機(jī)名、體系結(jié)構(gòu)和內(nèi)核信息,但是uname()采用了結(jié)構(gòu)化格式,以便通過(guò)序號(hào)來(lái)引用相應(yīng)的值。
- import platform
- from pprint import pprint
- uname = platform.uname()
- pprint(uname)
上述代碼的運(yùn)行結(jié)果如下。

system()方法獲得的第一個(gè)值是系統(tǒng)類型,第二個(gè)是當(dāng)前機(jī)器的主機(jī)名。
使用PyCharm中的自動(dòng)補(bǔ)全功能可以瀏覽并列出platform模塊中的所有可用函數(shù),按Ctrl + Q組合鍵就可以查看每個(gè)函數(shù)的文檔(見下圖)。

然后,使用Linux文件提供的信息列出Linux機(jī)器中的硬件配置。這里需要記住,在/proc/目錄下可以訪問(wèn)CPU、內(nèi)存以及網(wǎng)絡(luò)等相關(guān)信息;我們將讀取這些信息并在Python中使用標(biāo)準(zhǔn)的open()函數(shù)訪問(wèn)它們。查看/proc/目錄可以獲取更多信息。
下面給出具體的腳本。
首先,導(dǎo)入platform模塊,它僅在當(dāng)前任務(wù)中使用。
- #!/usr/bin/python
- __author__ = "Bassim Aly"
- __EMAIL__ = "basim.alyy@gmail.com"
- import platform
然后,定義函數(shù)。以下代碼包含了本次練習(xí)中需要的兩個(gè)函數(shù)——check_feature()和get_value_from_string()。
- def check_feature(feature,string):
- if feature in string.lower():
- return True
- else:
- return False
- def get_value_from_string(key,string):
- value = "NONE"
- for line in string.split("\n"):
- if key in line:
- value = line.split(":")[1].strip()
- return value
最后是Python腳本的主要部分,其中包括用來(lái)獲取所需信息的Python代碼。
- cpu_features = []
- with open('/proc/cpuinfo') as cpus:
- cpu_data = cpus.read()
- num_of_cpus = cpu_data.count("processor")
- cpu_features.append("Number of Processors: {0}".format(num_of_cpus))
- one_processor_data = cpu_data.split("processor")[1]
- print one_processor_data
- if check_feature("vmx",one_processor_data):
- cpu_features.append("CPU Virtualization: enabled")
- if check_feature("cpu_meltdown",one_processor_data):
- cpu_features.append("Known Bugs: CPU Metldown ")
- model_name = get_value_from_string("model name ",one_processor_data)
- cpu_features.append("Model Name: {0}".format(model_name))
- cpu_mhz = get_value_from_string("cpu MHz",one_processor_data)
- cpu_features.append("CPU MHz: {0}".format((cpu_mhz)))
- memory_features = []
- with open('/proc/meminfo') as memory:
- memory_data = memory.read()
- total_memory = get_value_from_string("MemTotal",memory_data).replace("kB","")
- free_memory = get_value_from_string("MemFree",memory_data).replace("kB","")
- swap_memory = get_value_from_string("SwapTotal",memory_data).replace("kB","")
- total_memory_in_gb = "Total Memory in GB:
- {0}".format(int(total_memory)/1024)
- free_memory_in_gb = "Free Memory in GB:
- {0}".format(int(free_memory)/1024)
- swap_memory_in_gb = "SWAP Memory in GB:
- {0}".format(int(swap_memory)/1024)
- memory_features =
- [total_memory_in_gb,free_memory_in_gb,swap_memory_in_gb]
這部分代碼用來(lái)輸出從上一節(jié)的代碼中獲取的信息。
- print("============System Information============")
- print("""
- System Type: {0}
- Hostname: {1}
- Kernel Version: {2}
- System Version: {3}
- Machine Architecture: {4}
- Python version: {5}
- """.format(platform.system(),
- platform.uname()[1],
- platform.uname()[2],
- platform.version(),
- platform.machine(),
- platform.python_version()))
- print("============CPU Information============")
- print("\n".join(cpu_features))
- print("============Memory Information============")
- print("\n".join(memory_features))
在上面的例子中我們完成了以下任務(wù)。
(1)打開/proc/cpuinfo并讀取其內(nèi)容,然后將結(jié)果存儲(chǔ)在cpu_data中。
(2)使用字符串函數(shù)count()統(tǒng)計(jì)文件中關(guān)鍵字processor的數(shù)量,從而得知機(jī)器上有多少個(gè)處理器。
(3)獲取每個(gè)處理器支持的選項(xiàng)和功能,我們只需要讀取其中一個(gè)處理器的信息(因?yàn)橥ǔK刑幚砥鞯膶傩远家粯樱┎鬟f給check_feature()函數(shù)。該方法的一個(gè)參數(shù)是我們期望處理器支持的功能,另一個(gè)參數(shù)是處理器的屬性信息。如果處理器的屬性支持第一個(gè)參數(shù)指定的功能,該方法返回True。
(4)由于處理器的屬性數(shù)據(jù)以鍵值對(duì)的方式呈現(xiàn),因此我們?cè)O(shè)計(jì)了get_value_from_string()方法。該方法根據(jù)輸入的鍵名通過(guò)迭代處理器屬性數(shù)據(jù)來(lái)搜索對(duì)應(yīng)的值,然后根據(jù)冒號(hào)拆分返回的鍵值對(duì),以獲取其中的值。
(5)使用append()方法將所有值添加到cpu_feature列表中。
(6)對(duì)內(nèi)存信息重復(fù)相同的操作,獲得總內(nèi)存、空閑內(nèi)存和交換內(nèi)存的大小。
(7)使用platform的內(nèi)置方法(如system()、uname()和python_version())來(lái)獲取系統(tǒng)的相關(guān)信息。
(8)輸出包含上述信息的報(bào)告。
腳本輸出如下圖所示。

另一種呈現(xiàn)數(shù)據(jù)的方式是利用第5章中介紹的Matplotlib庫(kù),可視化隨時(shí)間變化的數(shù)據(jù)。
11.1.1 通過(guò)郵件發(fā)送收集的數(shù)據(jù)
從上一節(jié)生成的報(bào)告中可以看到系統(tǒng)中當(dāng)前的資源。在本節(jié)中,我們調(diào)整腳本,增強(qiáng)其功能,比如,將這些信息通過(guò)電子郵件發(fā)送出去。對(duì)于網(wǎng)絡(luò)操作中心(Network Operation Center,NOC)團(tuán)隊(duì)來(lái)說(shuō),這個(gè)功能非常有用。當(dāng)某個(gè)特殊事件(如HDD故障、高CPU或丟包)發(fā)生時(shí),他們希望被監(jiān)控系統(tǒng)能夠自動(dòng)給他們發(fā)送郵件。Python有一個(gè)內(nèi)置庫(kù)smtplib,它利用簡(jiǎn)單郵件傳輸協(xié)議(Simple Mail Transfer Protocol,SMTP)從郵件服務(wù)器中發(fā)送和接收電子郵件。
使用該功能要求在計(jì)算機(jī)上安裝本地電子郵件服務(wù)器,或者能夠使用免費(fèi)的在線電子郵件服務(wù)(如Gmail或Outlook)。在這個(gè)例子中我們將使用SMTP登錄Gmail網(wǎng)站,將數(shù)據(jù)通過(guò)電子郵件發(fā)送出去。
接下來(lái),開始動(dòng)手修改腳本,為其添加SMTP功能。
將所需模塊導(dǎo)入Python,這次需要導(dǎo)入smtplib和platform。
- #!/usr/bin/python
- __author__ = "Bassem Aly"
- __EMAIL__ = "basim.alyy@gmail.com"
- import smtplib
- imp ort platform
下面是check_feature()和get_value_from_string()這兩個(gè)函數(shù)的代碼。
- def check_feature(feature,string):
- if feature in string.lower():
- return True
- else:
- return False
- def get_value_from_string(key,string):
- value = "NONE"
- for line in string.split("\n"):
- if key in line:
- value = line.split(":")[1].strip()
- return value
最后是Python腳本的主體,其中包含了獲取所需信息的Python代碼。
- cpu_features = []
- with open('/proc/cpuinfo') as cpus:
- cpu_data = cpus.read()
- num_of_cpus = cpu_data.count("processor")
- cpu_features.append("Number of Processors: {0}".format(num_of_cpus))
- one_processor_data = cpu_data.split("processor")[1]
- if check_feature("vmx",one_processor_data):
- cpu_features.append("CPU Virtualization: enabled")
- if check_feature("cpu_meltdown",one_processor_data):
- cpu_features.append("Known Bugs: CPU Metldown ")
- model_name = get_value_from_string("model name ",one_processor_data)
- cpu_features.append("Model Name: {0}".format(model_name))
- cpu_mhz = get_value_from_string("cpu MHz",one_processor_data)
- cpu_features.append("CPU MHz: {0}".format((cpu_mhz)))
- memory_features = []
- with open('/proc/meminfo') as memory:
- memory_data = memory.read()
- total_memory = get_value_from_string("MemTotal",memory_data).replace("kB","")
- free_memory = get_value_from_string("MemFree",memory_data).replace("kB","")
- swap_memory = get_value_from_string("SwapTotal",memory_data).replace("kB","")
- total_memory_in_gb = "Total Memory in GB:
- {0}".format(int(total_memory)/1024)
- free_memory_in_gb = "Free Memory in GB:
- {0}".format(int(free_memory)/1024)
- swap_memory_in_gb = "SWAP Memory in GB:
- {0}".format(int(swap_memory)/1024)
- memory_features =
- [total_memory_in_gb,free_memory_in_gb,swap_memory_in_gb]
- Data_Sent_in_Email = ""
- Header = """From: PythonEnterpriseAutomationBot <basim.alyy@gmail.com>
- To: To Administrator <basim.alyy@gmail.com>
- Subject: Monitoring System Report
- """
- Data_Sent_in_Email += Header
- Data_Sent_in_Email +="============System Information============"
- Data_Sent_in_Email +="""
- System Type: {0}
- Hostname: {1}
- Kernel Version: {2}
- System Version: {3}
- Machine Architecture: {4}
- Python version: {5}
- """.format(platform.system(),
- platform.uname()[1],
- platform.uname()[2],
- platform.version(),
- platform.machine(),
- platform.python_version())
- Data_Sent_in_Email +="============CPU Information============\n"
- Data_Sent_in_Email +="\n".join(cpu_features)
- Data_Sent_in_Email +="\n============Memory Information============\n"
- Data_Sent_in_Email +="\n".join(memory_features)
下面給出連接到gmail服務(wù)器所需的信息。
- fromaddr = 'yyyyyyyyyyy@gmail.com'
- toaddrs = 'basim.alyy@gmail.com'
- username = 'yyyyyyyyyyy@gmail.com'
- password = 'xxxxxxxxxx'
- server = smtplib.SMTP('smtp.gmail.com:587')
- server.ehlo()
- server.starttls()
- server.login(username,password)
- server.sendmail(fromaddr, toaddrs, Data_Sent_in_Email)
- server.quit()
在前面的例子中實(shí)現(xiàn)了以下功能。
(1)第一部分與上一個(gè)例子相同,只是沒(méi)有將數(shù)據(jù)輸出到終端,而是將其添加到Data_Sent_in_Email變量中。
(2)Header變量表示電子郵件標(biāo)題,包括發(fā)件人地址、收件人地址和電子郵件主題。
(3)使用smtplib模塊內(nèi)的SMTP()類連接到公共Gmail SMTP服務(wù)器并完成TTLS連接。這也是連接Gmail服務(wù)器的默認(rèn)方法。我們將SMTP連接保存在server變量中。
(4)使用login()方法登錄服務(wù)器,最后使用sendmail()函數(shù)發(fā)送電子郵件。sendmail()有3個(gè)輸入?yún)?shù)——發(fā)件人、收件人和電子郵件正文。
(5)關(guān)閉與服務(wù)器的連接。
腳本輸出如下圖所示。

11.1.2 使用time和date模塊
到目前為止,我們已經(jīng)能將從服務(wù)器中生成的自定義數(shù)據(jù)通過(guò)電子郵件發(fā)送出去。但由于網(wǎng)絡(luò)擁塞、郵件系統(tǒng)故障或任何其他問(wèn)題,生成的數(shù)據(jù)與電子郵件的傳遞時(shí)間之間可能存在時(shí)間差,因此我們不能根據(jù)收到電子郵件的時(shí)間來(lái)推算實(shí)際生成數(shù)據(jù)的時(shí)間。
出于上述原因,需要使用Python中的datetime模塊來(lái)獲取被監(jiān)控系統(tǒng)上的當(dāng)前時(shí)間。該模塊可以使用各種字段(如年、月、日、小時(shí)和分鐘)來(lái)格式化時(shí)間。
除此之外,datetime模塊中的datetime實(shí)例實(shí)際上是Python中獨(dú)立的對(duì)象(如int、string、boolean等),因此datetime實(shí)例在Python中有自己的屬性。
使用strftime()方法可以將datetime對(duì)象轉(zhuǎn)換為字符串。該方法使用下表中的格式符號(hào)來(lái)格式化時(shí)間。

修改腳本,將下面的代碼段添加到代碼中。
- from datetime import datetime
- time_now = datetime.now()
- time_now_string = time_now.strftime("%Y-%m-%d %H:%M:%S")
- Data_Sent_in_Email += "====Time Now is {0}====\n".format(time_now_string)
在這段代碼中,首先從datetime模塊中導(dǎo)入datetime類。然后使用datetime類和now()函數(shù)創(chuàng)建time_now對(duì)象,該函數(shù)返回系統(tǒng)的當(dāng)前時(shí)間。最后使用帶格式化符號(hào)的strftime()來(lái)格式化時(shí)間并將其轉(zhuǎn)換為字符串,用于輸出(注意,該對(duì)象包含了datetime對(duì)象)。
腳本的輸出如下。

11.1.3 定期運(yùn)行腳本
在腳本的最后一步,設(shè)置運(yùn)行腳本的時(shí)間間隔,它可以是每天、每周、每小時(shí)或某個(gè)特定的時(shí)間。該功能使用了Linux系統(tǒng)上的cron服務(wù)。cron用來(lái)調(diào)度周期性的重復(fù)事件,例如,清理目錄、備份數(shù)據(jù)庫(kù)、轉(zhuǎn)儲(chǔ)日志或任何其他事件。
使用下面的命令可以查看當(dāng)前計(jì)劃中的任務(wù)。
- crontab -l
編輯crontab需要使用-e選項(xiàng)。第一次運(yùn)行cron時(shí),系統(tǒng)會(huì)提示你選擇自己喜歡的編輯器(nano或vi)。
典型的crontab由5顆星組成,每顆星代表一個(gè)時(shí)間項(xiàng)(見下表)。

如果需要每周五晚上9點(diǎn)運(yùn)行某個(gè)任務(wù),可以使用下面的配置。
- 0 21 * * 5 /path/to/command
如果需要每天0點(diǎn)運(yùn)行某條命令(比如備份),使用這個(gè)配置。
- 0 0 * * * /path/to/command
另外,還可以讓cron以某個(gè)特定時(shí)間間隔運(yùn)行。如果需要每5min運(yùn)行一次命令,可以使用這個(gè)配置。
- */5 * * * * /path/to/command
回到腳本,如果我們期望它每天早上7:30運(yùn)行,使用這個(gè)配置。
- 30 7 * * * /usr/bin/python /root/Send_Email.py
最后,記得在退出之前保存cron配置。
最好使用絕對(duì)路徑的Linux命令,而不是相對(duì)路徑,以避免出現(xiàn)任何潛在的問(wèn)題。