用Python开发主机批量管理工具

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

    通过前面对Paramiko模块的学习与使用,以及Python中多线程与多进程的了解,依此,就可以开发简易的主机批量管理工具了。

    显然批量管理主机时,程序如果能并发执行功能是最好的,因为这样可以最大程度地利用CPU的性能,因此这就需要使用Python多线程或者多进程,基于学习的需要,这里主要使用多进程来进行开发,当然,这会存在一定问题,后面会说。

    主要内容如下:

1
2
3
4
5
6
7
1 .主机批量管理工具功能
2 .设计框架
3 .实现:数据库信息与程序源代码
4 .实战演示
5 .程序的不足
6 .在写程序过程中的经验教训
7 .往后的改进思路


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

    这里的主机主要是指Linux服务器,需要的功能如下:

(1)批量命令执行

    能够通过该程序对管理列表中的主机批量执行管理员输入的命令。

(2)批量文件分发

    对于多台服务器主机需要同一文件时,可以通过该程序远程批量分发指定的文件。

(3)支持自定义端口

    实现(1)(2)的功能都依赖于Paramiko模块,而Paramiko模块是基于SSH来完成的,虽然大多数Linux服务器的SSH端口号都默认使用22,但出于安全的考虑,也有修改默认端口号的情况,比如将SSH远程端口号修改为52113等。

(4)自定义用户

    这里的自定义用户主要是指该程序的用户,把该程序理解为一个批量管理系统,要使用该系统就必然要有该系统的账号与用户名,而每个账号与用户名根据权限的需要,都应该有自己可以管理的主机列表,比如普通运维人员只能管理部分服务器主机,而运维总监则应该可以管理更多的主机,并且他们的管理权限也应该是不一样的,因此,他们分别对应的管理系统的账号的权限就不一样了。

(5)日志记录功能

    运维人员登陆该系统后,对远程服务器主机进行了什么操作、时间、成功与否等信息都要以日志形式记录下来。


2.设计框架

    基于上面几个功能的需要,设计的思路如下:

wKioL1Yfa6SxmBECAAFbZPi37UQ185.jpg


3.实现:数据库信息与程序源代码

    根据需求与设计框架,做如下的工作:

(1)数据库信息

1)管理系统登陆信息数据库

    这里存放的是该系统可以登陆的用户名密码等信息,只有在这里存在的用户名才能进行登陆,如下:

    

创建了manager_system数据库:

1
2
3
4
5
6
7
8
9
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| manager_system     |
| mysql              |
+--------------------+
3  rows  in  set  ( 0.03  sec)

    

在manager_system数据库中创建了两种类型不同的表:

1
2
3
4
5
6
7
8
9
10
mysql>  use  manager_system
mysql> show tables;
+--------------------------+
| Tables_in_manager_system |
+--------------------------+
| manager1_server          |
| manager2_server          |
| users                    |
+--------------------------+
3  rows  in  set  ( 0.00  sec)

    表users用来存放用户信息,表manager1_server等就是用来存放用户对应的可以管理的主机列表,下面会讲。 


表users就是用来存放系统用户信息的:

1
2
3
4
5
6
7
8
9
10
mysql> describe users;
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             |  Null  | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        |  int ( 10 ) unsigned | NO   | PRI | NULL    | auto_increment |
| username  | char( 20 )         | NO   |     | NULL    |                |
| password  | char( 20 )         | NO   |     | NULL    |                |
| real_name | char( 20 )         | NO   |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+
4  rows  in  set  ( 0.01  sec)

    

表users中存放了两个用户信息:

1
2
3
4
5
6
7
8
mysql> select * from users;
+----+----------+----------+-----------+
| id | username | password | real_name |
+----+----------+----------+-----------+
|   1  | manager1 |  123456    | zhangsan  |
|   2  | manager2 |  123456    | lisi      |
+----+----------+----------+-----------+
2  rows  in  set  ( 0.00  sec)

    也就是说,只能用户manager1和manager2才能登陆该系统,其他用户除非向管理员申请注册,否则是无法登陆该系统的。


2)管理系统用户主机列表数据库

    其实还是使用了manager_system的数据库,只是在该数据库中创建了基于用户的不同表,如下:


两种类型不同的表:

1
2
3
4
5
6
7
8
9
10
mysql>  use  manager_system
mysql> show tables;
+--------------------------+
| Tables_in_manager_system |
+--------------------------+
| manager1_server          |
| manager2_server          |
| users                    |
+--------------------------+
3  rows  in  set  ( 0.00  sec)


表[name]_server就是用来存放用户对应的主机列表:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> describe manager1_server;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             |  Null  | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          |  int ( 10 ) unsigned | NO   | PRI | NULL    | auto_increment |
| ip          | char( 20 )         | NO   |     | NULL    |                |
| username    | char( 20 )         | NO   |     | NULL    |                |
| password    | char( 20 )         | NO   |     | NULL    |                |
| port        |  int ( 11 )          | NO   |     | NULL    |                |
| server_type | char( 20 )         | NO   |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
6  rows  in  set  ( 0.00  sec)


表中存放了用户可以管理的主机相关信息:

1
2
3
4
5
6
7
8
mysql> select * from manager1_server;
+----+---------------+-----------+----------+-------+-------------+
| id | ip            | username  | password | port  | server_type |
+----+---------------+-----------+----------+-------+-------------+
|   1  192.168 . 1.124  | oldboy    |  123456    |     22  | DNS Server  |
|   2  192.168 . 1.134  | yonghaoye |  123456    52113  | DHCP Server |
+----+---------------+-----------+----------+-------+-------------+
2  rows  in  set  ( 0.00  sec)

    其中这里的ip就是远程主机的IP地址了,server_type就是服务器类型,username和password是远程主机ssh登陆的用户密码,这也说明,只要管理系统用户进入了管理系统,在对远程主机进行管理时,就不需要输入远程主机的用户名和密码了,除了方便外,这也有一定的安全性。

    需要说明的是这里的port端口号,可以看到这里有台主机的port为22,而另一台则为52113,就是前面所说的自定义端口号了,因此,这需要管理员在添加主机时手动指定。


(2)程序源代码

    有了上面的基本数据准备后,再看一下该程序的源代码,其中部分注释会给出,但是基于前面的介绍,代码应该也是比较容易理解的:

1
2
3
4
5
6
7
8
9
10
11
12
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
import  MySQLdb,os,paramiko,sys,time
from multiprocessing  import  Process,Pool
 
#数据库连接类
class  Connect_mysql:
     conn = MySQLdb.connect(host =  'localhost' , user =  'root' ,passwd =  '123456' , db =  'manager_system' , port =  3306 )
     cur = conn.cursor()
     def __init__(self,username,password= 'NULL' ):
         self.username = username
         self.password = password
     #contect to the login table    
     def login_check(self):    #连接管理系统账号信息数据库并验证用户名密码信息
         try :
             self.cur.execute( "select * from users where username = '%s' and password = '%s'"  % (self.username,self.password))
             qur_result = self.cur.fetchall()  # return  the tuple
             
             if  qur_result == (): #database  do  not have  this  user
                 return  0        
             else :
                 return  1             #database has  this  user
             self.cur.close()
             self.conn.close()
 
         except MySQLdb.Error,e:
             print  '\033[31;1mMysql Error Msg:%s\033[0m'  % e
     #contect to the server table
     def return_server(self):    #连接用户主机列表数据库并返回表信息
         self.cur.execute( "select * from %s_server"  %  self.username)
         qur_result = self.cur.fetchall()
         return  qur_result
 
def ssh_run(host_info,cmd,sysname):    #批量远程命令执行程序
     ip,username,password,port= host_info[ 1 ],host_info[ 2 ],host_info[ 3 ],host_info[ 4 ]
     date = time.strftime( '%Y_%m_%d' )
     date_detial = time.strftime( '%Y_%m_%d %H:%M:%S' )    
     f = file( './log/%s_%s_record.log'  % (sysname,date), 'a+' )    #操作日志记录,记录程序所有目录的/log目录里
     try :
         s.connect(ip, int (port),username,password,timeout= 5 )
         stdin,stdout,stderr = s.exec_command(cmd)
 
         cmd_result = stdout.read(),stderr.read()
 
         print  '\033[32;1m-------------%s--------------\033[0m'  % ip
         for  line  in  cmd_result:
             print line,
         print  '\033[32;1m-----------------------------\033[0m'
     except:
         log =  "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n"  % (date_detial, 'cmd batch' ,cmd,ip, 'failed' )
         f.write(log)
         f.close()
         print  '\033[31;1mSomething is wrong of %s\033[0m'  % ip
     else :
         log =  "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n"  % (date_detial, 'cmd batch' ,cmd,ip, 'success' )
         f.write(log)
         f.close()
         return  1
 
def distribute_file(host_info,file_name,sysname):    #批量文件分发函数
     ip,username,password,port = host_info[ 1 ],host_info[ 2 ],host_info[ 3 ], int (host_info[ 4 ])
     date = time.strftime( '%Y_%m_%d' )
     date_detial = time.strftime( '%Y_%m_%d %H:%M:%S' )
     f = file( './log/%s_%s_record.log'  % (sysname,date), 'a+' )    #日志记录
     try :
         t = paramiko.Transport((ip,port))
         t.connect(username=username,password=password)
         sftp = paramiko.SFTPClient.from_transport(t)
         sftp.put(file_name, '/tmp/%s'  % file_name)
         t.close()
     except:
         log =  "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n"  % (date_detial, 'distribute file' ,file_name,ip, 'failed' )
         f.write(log)
         f.close()
         print  '\033[31;1mSomething is wrong of %s\033[0m'  % ip
     else :
         log =  "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n"  % (date_detial, 'distribute file' ,file_name,ip, 'success' )
         f.write(log)
         f.close()
         print  "\033[32;1mDistribute '%s' to %s Successfully!\033[0m"  % (file_name,ip)
 
os.system( 'clear' )
print  '\033[32;1mWelcome to the Manager System!\033[0m'
 
while  True:    #程序主程序
     username = raw_input( 'Username:' ).strip()
     password = raw_input( 'Password:' ).strip()
     if  len(username) <=  3  or len(password) <  6 :
         print  '\033[31;1mInvalid username or password!\033[0m'
         continue
     #Begin to login
     p = Connect_mysql(username,password)
     mark = p.login_check()
     if  mark ==  0 :        #login failed
         print  '\033[31;1mUsername or password wrong!Please try again!\033[0m'
     elif mark ==  1 :      #login success
         print  '\033[32;1mLogin Success!\033[0m'
         print  'The server list are as follow:'
         #seek  for  the server list managed by the system user
         p = Connect_mysql(username)
         server_list = p.return_server()
         for  server  in  server_list:
             print  '%s:%s'  % (server[ 5 ],server[ 1 ])
         while  True:
             print  '' 'What  do  you want to  do ?    #程序主菜单
1 .Execute the command batch.
2 .Distribute file(s) batch.
3 .Exit. '' '
             choice = raw_input( '\033[32;1mYour choice:\033[0m' ).strip()
             if  '1'  <= choice <=  '4' :pass
             else : continue
 
             #Execute the command batch.
             if  choice ==  '1' :    #批量执行命令程序块
                 s = paramiko.SSHClient()    #调用Paramiko模块
                 s.load_system_host_keys()
                 s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
                 p = Pool(processes= 3 )    #设定进程池数据
 
                 result_list = []
                 while  True:
                     cmd = raw_input( '\033[32;0mEnter the command(or quit to quit):\033[0m' )
                     if  cmd ==  'quit' : break
                     for  in  server_list:
                         result_list.append(p.apply_async(ssh_run,[h,cmd,username])) #the usename  is  system name
             #调用相关功能函数,并执行多进程并发
                     for  res  in  result_list:
                         res. get ()
                 s.close()
 
             #Distribute file(s) batch.
             elif choice ==  '2' :    #批量分发文件程序块
                 s = paramiko.SSHClient()    #调用Paramiko模块
                 s.load_system_host_keys()
                 s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
                 p = Pool(processes= 3 )
 
                 result_list = []  #save the suanfa that come from the apply_async
                 while  True:
                     file_name = raw_input(