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

該腳本返回當前系統的類型,同樣的腳本在Windows系統上運行會得到不同的結果。當它在Windows系統上運行時,輸出結果就變成Windows。

常用的函數uname()和Linux命令(uname -a)的功能一樣:獲取機器的主機名、體系結構和內核信息,但是uname()采用了結構化格式,以便通過序號來引用相應的值。
- import platform
- from pprint import pprint
- uname = platform.uname()
- pprint(uname)
上述代碼的運行結果如下。

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

然后,使用Linux文件提供的信息列出Linux機器中的硬件配置。這里需要記住,在/proc/目錄下可以訪問CPU、內存以及網絡等相關信息;我們將讀取這些信息并在Python中使用標準的open()函數訪問它們。查看/proc/目錄可以獲取更多信息。
下面給出具體的腳本。
首先,導入platform模塊,它僅在當前任務中使用。
- #!/usr/bin/python
- __author__ = "Bassim Aly"
- __EMAIL__ = "basim.alyy@gmail.com"
- import platform
然后,定義函數。以下代碼包含了本次練習中需要的兩個函數——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腳本的主要部分,其中包括用來獲取所需信息的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]
這部分代碼用來輸出從上一節的代碼中獲取的信息。
- 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))
在上面的例子中我們完成了以下任務。
(1)打開/proc/cpuinfo并讀取其內容,然后將結果存儲在cpu_data中。
(2)使用字符串函數count()統計文件中關鍵字processor的數量,從而得知機器上有多少個處理器。
(3)獲取每個處理器支持的選項和功能,我們只需要讀取其中一個處理器的信息(因為通常所有處理器的屬性都一樣)并傳遞給check_feature()函數。該方法的一個參數是我們期望處理器支持的功能,另一個參數是處理器的屬性信息。如果處理器的屬性支持第一個參數指定的功能,該方法返回True。
(4)由于處理器的屬性數據以鍵值對的方式呈現,因此我們設計了get_value_from_string()方法。該方法根據輸入的鍵名通過迭代處理器屬性數據來搜索對應的值,然后根據冒號拆分返回的鍵值對,以獲取其中的值。
(5)使用append()方法將所有值添加到cpu_feature列表中。
(6)對內存信息重復相同的操作,獲得總內存、空閑內存和交換內存的大小。
(7)使用platform的內置方法(如system()、uname()和python_version())來獲取系統的相關信息。
(8)輸出包含上述信息的報告。
腳本輸出如下圖所示。

另一種呈現數據的方式是利用第5章中介紹的Matplotlib庫,可視化隨時間變化的數據。
11.1.1 通過郵件發送收集的數據
從上一節生成的報告中可以看到系統中當前的資源。在本節中,我們調整腳本,增強其功能,比如,將這些信息通過電子郵件發送出去。對于網絡操作中心(Network Operation Center,NOC)團隊來說,這個功能非常有用。當某個特殊事件(如HDD故障、高CPU或丟包)發生時,他們希望被監控系統能夠自動給他們發送郵件。Python有一個內置庫smtplib,它利用簡單郵件傳輸協議(Simple Mail Transfer Protocol,SMTP)從郵件服務器中發送和接收電子郵件。
使用該功能要求在計算機上安裝本地電子郵件服務器,或者能夠使用免費的在線電子郵件服務(如Gmail或Outlook)。在這個例子中我們將使用SMTP登錄Gmail網站,將數據通過電子郵件發送出去。
接下來,開始動手修改腳本,為其添加SMTP功能。
將所需模塊導入Python,這次需要導入smtplib和platform。
- #!/usr/bin/python
- __author__ = "Bassem Aly"
- __EMAIL__ = "basim.alyy@gmail.com"
- import smtplib
- imp ort platform
下面是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腳本的主體,其中包含了獲取所需信息的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服務器所需的信息。
- 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()
在前面的例子中實現了以下功能。
(1)第一部分與上一個例子相同,只是沒有將數據輸出到終端,而是將其添加到Data_Sent_in_Email變量中。
(2)Header變量表示電子郵件標題,包括發件人地址、收件人地址和電子郵件主題。
(3)使用smtplib模塊內的SMTP()類連接到公共Gmail SMTP服務器并完成TTLS連接。這也是連接Gmail服務器的默認方法。我們將SMTP連接保存在server變量中。
(4)使用login()方法登錄服務器,最后使用sendmail()函數發送電子郵件。sendmail()有3個輸入參數——發件人、收件人和電子郵件正文。
(5)關閉與服務器的連接。
腳本輸出如下圖所示。

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

修改腳本,將下面的代碼段添加到代碼中。
- 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模塊中導入datetime類。然后使用datetime類和now()函數創建time_now對象,該函數返回系統的當前時間。最后使用帶格式化符號的strftime()來格式化時間并將其轉換為字符串,用于輸出(注意,該對象包含了datetime對象)。
腳本的輸出如下。

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

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