天天看點

【Python之旅】第六篇(七):開發簡易主機批量管理工具

通過前面對paramiko子產品的學習與使用,以及python中多線程與多程序的了解,依此,就可以開發簡易的主機批量管理工具了。

    顯然批量管理主機時,程式如果能并發執行功能是最好的,因為這樣可以最大程度地利用cpu的性能,是以這就需要使用python多線程或者多程序,基于學習的需要,這裡主要使用多程序來進行開發,當然,這會存在一定問題,後面會說。

    主要内容如下:

1

2

3

4

5

6

7

<code>1</code><code>.主機批量管理工具功能</code>

<code>2</code><code>.設計架構</code>

<code>3</code><code>.實作:資料庫資訊與程式源代碼</code>

<code>4</code><code>.實戰示範</code>

<code>5</code><code>.程式的不足</code>

<code>6</code><code>.在寫程式過程中的經驗教訓</code>

<code>7</code><code>.往後的改進思路</code>

1.主機批量管理工具功能

    這裡的主機主要是指linux伺服器,需要的功能如下:

(1)批量指令執行

    能夠通過該程式對管理清單中的主機批量執行管理者輸入的指令。

(2)批量檔案分發

    對于多台伺服器主機需要同一檔案時,可以通過該程式遠端批量分發指定的檔案。

(3)支援自定義端口

    實作(1)(2)的功能都依賴于paramiko子產品,而paramiko子產品是基于ssh來完成的,雖然大多數linux伺服器的ssh端口号都預設使用22,但出于安全的考慮,也有修改預設端口号的情況,比如将ssh遠端端口号修改為52113等。

(4)自定義使用者

    這裡的自定義使用者主要是指該程式的使用者,把該程式了解為一個批量管理系統,要使用該系統就必然要有該系統的賬号與使用者名,而每個賬号與使用者名根據權限的需要,都應該有自己可以管理的主機清單,比如普通運維人員隻能管理部分伺服器主機,而運維總監則應該可以管理更多的主機,并且他們的管理權限也應該是不一樣的,是以,他們分别對應的管理系統的賬号的權限就不一樣了。

(5)日志記錄功能

    運維人員登陸該系統後,對遠端伺服器主機進行了什麼操作、時間、成功與否等資訊都要以日志形式記錄下來。

2.設計架構

    基于上面幾個功能的需要,設計的思路如下:

【Python之旅】第六篇(七):開發簡易主機批量管理工具

3.實作:資料庫資訊與程式源代碼

    根據需求與設計架構,做如下的工作:

(1)資料庫資訊

1)管理系統登陸資訊資料庫

    這裡存放的是該系統可以登陸的使用者名密碼等資訊,隻有在這裡存在的使用者名才能進行登陸,如下:

建立了manager_system資料庫:

8

9

<code>mysql&gt; show databases;</code>

<code>+--------------------+</code>

<code>| database           |</code>

<code>| information_schema |</code>

<code>| manager_system     |</code>

<code>| mysql              |</code>

<code>3</code> <code>rows </code><code>in</code> <code>set</code> <code>(</code><code>0.03</code> <code>sec)</code>

在manager_system資料庫中建立了兩種類型不同的表:

10

<code>mysql&gt; </code><code>use</code> <code>manager_system</code>

<code>mysql&gt; show tables;</code>

<code>+--------------------------+</code>

<code>| tables_in_manager_system |</code>

<code>| manager1_server          |</code>

<code>| manager2_server          |</code>

<code>| users                    |</code>

<code>3</code> <code>rows </code><code>in</code> <code>set</code> <code>(</code><code>0.00</code> <code>sec)</code>

    表users用來存放使用者資訊,表manager1_server等就是用來存放使用者對應的可以管理的主機清單,下面會講。 

表users就是用來存放系統使用者資訊的:

<code>mysql&gt; describe users;</code>

<code>+-----------+------------------+------+-----+---------+----------------+</code>

<code>| field     | type             | </code><code>null</code> <code>| key | default | extra          |</code>

<code>| id        | </code><code>int</code><code>(</code><code>10</code><code>) unsigned | no   | pri | null    | auto_increment |</code>

<code>| username  | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>| password  | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>| real_name | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>4</code> <code>rows </code><code>in</code> <code>set</code> <code>(</code><code>0.01</code> <code>sec)</code>

表users中存放了兩個使用者資訊:

<code>mysql&gt; select * from users;</code>

<code>+----+----------+----------+-----------+</code>

<code>| id | username | password | real_name |</code>

<code>|  </code><code>1</code> <code>| manager1 | </code><code>123456</code>   <code>| zhangsan  |</code>

<code>|  </code><code>2</code> <code>| manager2 | </code><code>123456</code>   <code>| lisi      |</code>

<code>2</code> <code>rows </code><code>in</code> <code>set</code> <code>(</code><code>0.00</code> <code>sec)</code>

    也就是說,隻能使用者manager1和manager2才能登陸該系統,其他使用者除非向管理者申請注冊,否則是無法登陸該系統的。

2)管理系統使用者主機清單資料庫

    其實還是使用了manager_system的資料庫,隻是在該資料庫中建立了基于使用者的不同表,如下:

兩種類型不同的表:

表[name]_server就是用來存放使用者對應的主機清單:

11

12

<code>mysql&gt; describe manager1_server;</code>

<code>+-------------+------------------+------+-----+---------+----------------+</code>

<code>| field       | type             | </code><code>null</code> <code>| key | default | extra          |</code>

<code>| id          | </code><code>int</code><code>(</code><code>10</code><code>) unsigned | no   | pri | null    | auto_increment |</code>

<code>| ip          | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>| username    | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>| password    | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>| port        | </code><code>int</code><code>(</code><code>11</code><code>)          | no   |     | null    |                |</code>

<code>| server_type | char(</code><code>20</code><code>)         | no   |     | null    |                |</code>

<code>6</code> <code>rows </code><code>in</code> <code>set</code> <code>(</code><code>0.00</code> <code>sec)</code>

表中存放了使用者可以管理的主機相關資訊:

<code>mysql&gt; select * from manager1_server;</code>

<code>+----+---------------+-----------+----------+-------+-------------+</code>

<code>| id | ip            | username  | password | port  | server_type |</code>

<code>|  </code><code>1</code> <code>| </code><code>192.168</code><code>.</code><code>1.124</code> <code>| oldboy    | </code><code>123456</code>   <code>|    </code><code>22</code> <code>| dns server  |</code>

<code>|  </code><code>2</code> <code>| </code><code>192.168</code><code>.</code><code>1.134</code> <code>| yonghaoye | </code><code>123456</code>   <code>| </code><code>52113</code> <code>| dhcp server |</code>

    其中這裡的ip就是遠端主機的ip位址了,server_type就是伺服器類型,username和password是遠端主機ssh登陸的使用者密碼,這也說明,隻要管理系統使用者進入了管理系統,在對遠端主機進行管理時,就不需要輸入遠端主機的使用者名和密碼了,除了友善外,這也有一定的安全性。

    需要說明的是這裡的port端口号,可以看到這裡有台主機的port為22,而另一台則為52113,就是前面所說的自定義端口号了,是以,這需要管理者在添加主機時手動指定。

(2)程式源代碼

    有了上面的基本資料準備後,再看一下該程式的源代碼,其中部分注釋會給出,但是基于前面的介紹,代碼應該也是比較容易了解的:

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

<code>import</code> <code>mysqldb,os,paramiko,sys,time</code>

<code>from multiprocessing </code><code>import</code> <code>process,pool</code>

<code>#資料庫連接配接類</code>

<code>class</code> <code>connect_mysql:</code>

<code>    </code><code>conn = mysqldb.connect(host = </code><code>'localhost'</code><code>, user = </code><code>'root'</code><code>,passwd = </code><code>'123456'</code><code>, db = </code><code>'manager_system'</code><code>, port = </code><code>3306</code><code>)</code>

<code>    </code><code>cur = conn.cursor()</code>

<code>    </code><code>def __init__(self,username,password=</code><code>'null'</code><code>):</code>

<code>        </code><code>self.username = username</code>

<code>        </code><code>self.password = password</code>

<code>    </code><code>#contect to the login table    </code>

<code>    </code><code>def login_check(self):    #連接配接管理系統賬号資訊資料庫并驗證使用者名密碼資訊</code>

<code>        </code><code>try</code><code>:</code>

<code>            </code><code>self.cur.execute(</code><code>"select * from users where username = '%s' and password = '%s'"</code> <code>% (self.username,self.password))</code>

<code>            </code><code>qur_result = self.cur.fetchall()  #</code><code>return</code> <code>the tuple</code>

<code>            </code> 

<code>            </code><code>if</code> <code>qur_result == (): #database </code><code>do</code> <code>not have </code><code>this</code> <code>user</code>

<code>                </code><code>return</code> <code>0</code>        

<code>            </code><code>else</code><code>:</code>

<code>                </code><code>return</code> <code>1</code>            <code>#database has </code><code>this</code> <code>user</code>

<code>            </code><code>self.cur.close()</code>

<code>            </code><code>self.conn.close()</code>

<code>        </code><code>except mysqldb.error,e:</code>

<code>            </code><code>print </code><code>'\033[31;1mmysql error msg:%s\033[0m'</code> <code>% e</code>

<code>    </code><code>#contect to the server table</code>

<code>    </code><code>def return_server(self):    #連接配接使用者主機清單資料庫并傳回表資訊</code>

<code>        </code><code>self.cur.execute(</code><code>"select * from %s_server"</code> <code>%  self.username)</code>

<code>        </code><code>qur_result = self.cur.fetchall()</code>

<code>        </code><code>return</code> <code>qur_result</code>

<code>def ssh_run(host_info,cmd,sysname):    #批量遠端指令執行程式</code>

<code>    </code><code>ip,username,password,port= host_info[</code><code>1</code><code>],host_info[</code><code>2</code><code>],host_info[</code><code>3</code><code>],host_info[</code><code>4</code><code>]</code>

<code>    </code><code>date = time.strftime(</code><code>'%y_%m_%d'</code><code>)</code>

<code>    </code><code>date_detial = time.strftime(</code><code>'%y_%m_%d %h:%m:%s'</code><code>)    </code>

<code>    </code><code>f = file(</code><code>'./log/%s_%s_record.log'</code> <code>% (sysname,date),</code><code>'a+'</code><code>)    #記錄檔記錄,記錄程式所有目錄的/log目錄裡</code>

<code>    </code><code>try</code><code>:</code>

<code>        </code><code>s.connect(ip,</code><code>int</code><code>(port),username,password,timeout=</code><code>5</code><code>)</code>

<code>        </code><code>stdin,stdout,stderr = s.exec_command(cmd)</code>

<code>        </code><code>cmd_result = stdout.read(),stderr.read()</code>

<code>        </code><code>print </code><code>'\033[32;1m-------------%s--------------\033[0m'</code> <code>% ip</code>

<code>        </code><code>for</code> <code>line </code><code>in</code> <code>cmd_result:</code>

<code>            </code><code>print line,</code>

<code>        </code><code>print </code><code>'\033[32;1m-----------------------------\033[0m'</code>

<code>    </code><code>except:</code>

<code>        </code><code>log = </code><code>"time:%s | type:%s | detial:%s | server:%s | result:%s\n"</code> <code>% (date_detial,</code><code>'cmd batch'</code><code>,cmd,ip,</code><code>'failed'</code><code>)</code>

<code>        </code><code>f.write(log)</code>

<code>        </code><code>f.close()</code>

<code>        </code><code>print </code><code>'\033[31;1msomething is wrong of %s\033[0m'</code> <code>% ip</code>

<code>    </code><code>else</code><code>:</code>

<code>        </code><code>log = </code><code>"time:%s | type:%s | detial:%s | server:%s | result:%s\n"</code> <code>% (date_detial,</code><code>'cmd batch'</code><code>,cmd,ip,</code><code>'success'</code><code>)</code>

<code>        </code><code>return</code> <code>1</code>

<code>def distribute_file(host_info,file_name,sysname):    #批量檔案分發函數</code>

<code>    </code><code>ip,username,password,port = host_info[</code><code>1</code><code>],host_info[</code><code>2</code><code>],host_info[</code><code>3</code><code>],</code><code>int</code><code>(host_info[</code><code>4</code><code>])</code>

<code>    </code><code>date_detial = time.strftime(</code><code>'%y_%m_%d %h:%m:%s'</code><code>)</code>

<code>    </code><code>f = file(</code><code>'./log/%s_%s_record.log'</code> <code>% (sysname,date),</code><code>'a+'</code><code>)    #日志記錄</code>

<code>        </code><code>t = paramiko.transport((ip,port))</code>

<code>        </code><code>t.connect(username=username,password=password)</code>

<code>        </code><code>sftp = paramiko.sftpclient.from_transport(t)</code>

<code>        </code><code>sftp.put(file_name,</code><code>'/tmp/%s'</code> <code>% file_name)</code>

<code>        </code><code>t.close()</code>

<code>        </code><code>log = </code><code>"time:%s | type:%s | detial:%s | server:%s | result:%s\n"</code> <code>% (date_detial,</code><code>'distribute file'</code><code>,file_name,ip,</code><code>'failed'</code><code>)</code>

<code>        </code><code>log = </code><code>"time:%s | type:%s | detial:%s | server:%s | result:%s\n"</code> <code>% (date_detial,</code><code>'distribute file'</code><code>,file_name,ip,</code><code>'success'</code><code>)</code>

<code>        </code><code>print </code><code>"\033[32;1mdistribute '%s' to %s successfully!\033[0m"</code> <code>% (file_name,ip)</code>

<code>os.system(</code><code>'clear'</code><code>)</code>

<code>print </code><code>'\033[32;1mwelcome to the manager system!\033[0m'</code>

<code>while</code> <code>true:    #程式主程式</code>

<code>    </code><code>username = raw_input(</code><code>'username:'</code><code>).strip()</code>

<code>    </code><code>password = raw_input(</code><code>'password:'</code><code>).strip()</code>

<code>    </code><code>if</code> <code>len(username) &lt;= </code><code>3</code> <code>or len(password) &lt; </code><code>6</code><code>:</code>

<code>        </code><code>print </code><code>'\033[31;1minvalid username or password!\033[0m'</code>

<code>        </code><code>continue</code>

<code>    </code><code>#begin to login</code>

<code>    </code><code>p = connect_mysql(username,password)</code>

<code>    </code><code>mark = p.login_check()</code>

<code>    </code><code>if</code> <code>mark == </code><code>0</code><code>:        #login failed</code>

<code>        </code><code>print </code><code>'\033[31;1musername or password wrong!please try again!\033[0m'</code>

<code>    </code><code>elif mark == </code><code>1</code><code>:      #login success</code>

<code>        </code><code>print </code><code>'\033[32;1mlogin success!\033[0m'</code>

<code>        </code><code>print </code><code>'the server list are as follow:'</code>

<code>        </code><code>#seek </code><code>for</code> <code>the server list managed by the system user</code>

<code>        </code><code>p = connect_mysql(username)</code>

<code>        </code><code>server_list = p.return_server()</code>

<code>        </code><code>for</code> <code>server </code><code>in</code> <code>server_list:</code>

<code>            </code><code>print </code><code>'%s:%s'</code> <code>% (server[</code><code>5</code><code>],server[</code><code>1</code><code>])</code>

<code>        </code><code>while</code> <code>true:</code>

<code>            </code><code>print </code><code>''</code><code>'what </code><code>do</code> <code>you want to </code><code>do</code><code>?    #程式主菜單</code>

<code>1</code><code>.execute the command batch.</code>

<code>2</code><code>.distribute file(s) batch.</code>

<code>3</code><code>.exit.</code><code>''</code><code>'</code>

<code>            </code><code>choice = raw_input(</code><code>'\033[32;1myour choice:\033[0m'</code><code>).strip()</code>

<code>            </code><code>if</code> <code>'1'</code> <code>&lt;= choice &lt;= </code><code>'4'</code><code>:pass</code>

<code>            </code><code>else</code><code>:</code><code>continue</code>

<code>            </code><code>#execute the command batch.</code>

<code>            </code><code>if</code> <code>choice == </code><code>'1'</code><code>:    #批量執行指令程式塊</code>

<code>                </code><code>s = paramiko.sshclient()    #調用paramiko子產品</code>

<code>                </code><code>s.load_system_host_keys()</code>

<code>                </code><code>s.set_missing_host_key_policy(paramiko.autoaddpolicy())</code>

<code>                </code><code>p = pool(processes=</code><code>3</code><code>)    #設定程序池資料</code>

<code>                </code><code>result_list = []</code>

<code>                </code><code>while</code> <code>true:</code>

<code>                    </code><code>cmd = raw_input(</code><code>'\033[32;0menter the command(or quit to quit):\033[0m'</code><code>)</code>

<code>                    </code><code>if</code> <code>cmd == </code><code>'quit'</code><code>:</code><code>break</code>

<code>                    </code><code>for</code> <code>h </code><code>in</code> <code>server_list:</code>

<code>                        </code><code>result_list.append(p.apply_async(ssh_run,[h,cmd,username])) #the usename </code><code>is</code> <code>system name</code>

<code>            </code><code>#調用相關功能函數,并執行多程序并發</code>

<code>                    </code><code>for</code> <code>res </code><code>in</code> <code>result_list:</code>

<code>                        </code><code>res.</code><code>get</code><code>()</code>

<code>                </code><code>s.close()</code>

<code>            </code><code>#distribute file(s) batch.</code>

<code>            </code><code>elif choice == </code><code>'2'</code><code>:    #批量分發檔案程式塊</code>

<code>                </code><code>p = pool(processes=</code><code>3</code><code>)</code>

<code>                </code><code>result_list = []  #save the suanfa that come from the apply_async</code>

<code>                    </code><code>file_name = raw_input(</code><code>'the file you want to distribute(or quit to quit):'</code><code>).strip()</code>

<code>                    </code><code>start = time.time()</code>

<code>                    </code><code>if</code> <code>file_name == </code><code>'quit'</code><code>:</code><code>break</code>

<code>                    </code><code>file_chcek = os.path.isfile(file_name)</code>

<code>                    </code><code>log_list = []</code>

<code>                    </code><code>if</code> <code>file_chcek == false:</code>

<code>                        </code><code>print </code><code>'\033[31;1mthe file does not exist or it is a directory!\033[0m'</code>

<code>                        </code><code>continue</code>

<code>                        </code><code>result_list.append(p.apply_async(distribute_file,[h,file_name,username]))   #the list save the suanfa</code>

<code>                        </code><code>res.</code><code>get</code><code>()   #run the suanfa</code>

<code>                    </code><code>end = time.time()</code>

<code>                    </code><code>print </code><code>'\033[31;1mcost time:%ss\033[0m'</code> <code>% str(end - start)</code>

<code>            </code><code>#exit the system</code>

<code>            </code><code>elif choice == </code><code>'3'</code><code>:    #退出系統</code>

<code>                </code><code>sys.exit(</code><code>'\033[32;1mwelcome to use our system!\033[0m'</code><code>)</code>

    程式的代碼量不多,主要功能也有注釋,隻要的paramiko模拟的ssh指令執行與sftp檔案分發、python多程序以及程序池的使用有所了解,還是比較容易了解的。

4.實戰示範

    基于幾個主要功能:批量指令執行、檔案分發、日志記錄,下面來做個示範:

(1)登陸系統

【Python之旅】第六篇(七):開發簡易主機批量管理工具

(2)批量執行指令

【Python之旅】第六篇(七):開發簡易主機批量管理工具

(3)批量分發檔案

【Python之旅】第六篇(七):開發簡易主機批量管理工具

(4)退出系統

【Python之旅】第六篇(七):開發簡易主機批量管理工具

(5)使用者記錄檔檢視

檢視日志檔案清單:

【Python之旅】第六篇(七):開發簡易主機批量管理工具

    可以看到,記錄檔是基于不同使用者的(這裡會有manager2的日志檔案是我後來用manager2登陸後執行操作産生的,上面沒有給出manager2的操作過程),并且是按天生成的,隻要使用者登陸并執行相關操作,日志檔案是自動生成的。

檢視詳細日志内容:

【Python之旅】第六篇(七):開發簡易主機批量管理工具

    可以檢視該使用者在具體時間所做的具體操作,以及成功與否等更細節的資訊。

5.程式的不足

(1)細節上的問題

    由于未投入生産使用,是以會存在一些意想不到的bug,主要是在連接配接遠端主機時可能引發的各種異常。

(2)使用了多程序

    雖然使用了程序池了限制同一時間内并發的程序數,但仍然不可避免會出現檔案資源搶占的情況,隻是這裡因為并發的程序隻有兩個,是以影響并不會很大,基于該程式比較小型,是以往後也可繼續使用多程序,當然,使用多線程效果會更好,這裡隻是出于學習的需要而使用多程序。

(3)程式代碼簡潔程式

    程式的代碼簡潔程度可以進一步優化,比如兩個功能:批量執行指令和分發檔案,其實裡面就有很多重複的代碼,本來是考慮用類的方法來重寫的,但是對面向對象程式設計又不夠熟悉,是以就沒有使用了。

    當然,這其實隻是非常小的的一個程式,不過倒是可以作為以後開發特定功能的監控系統時的某一子產品來使用,不管怎麼說,架構在這裡的話應該還是正确的,由于時間和實際需要情況的考慮,在這裡并沒有做太多的界面優化,因為以後是考慮做成web界面的。當然,就從學習python的角度來考慮,這個程式還是可以練練手,各種小的積累才能沉澱出優秀的大型開源軟體。

6.在寫程式過程中的經驗教訓

    一開始函數有參數file作為我傳入的檔案字,但是函數中又使用了file來打開檔案,是以執行程式時一直顯示typeerror: 'str' object is not callable的錯誤,後來無意将file改變open發現又正常了,于是才很意識到這個錯誤。如果一開始就有這個意識,後面也不會浪費這麼多時間,是以這一點要尤其注意了。

7.往後的改進思路

(1)在以後學習了django的相關知識後,可以考慮将其寫成是基于web界面的管理系統。

(2)加強測試,盡可能找出程式中低級的bug。

(3)改用多線程來寫程式,并注意檔案資源的搶占問題。

(4)将功能重複的代碼用函數方式或面向對象程式設計方式來重寫。

    分享給大家,希望對同在python學習路上的新手帶來實際性的幫助!