[Python之旅]第六篇(七):开发简易主机批量管理工具


 通过前面对Paramiko模块的学习与使用,以及Python中多线程与多进程的了解,依此,就可以开发简易的主机批量管理工具了。
    显然批量管理主机时,程序如果能并发执行功能是最好的,因为这样可以最大程度地利用CPU的性能,因此这就需要使用Python多线程或者多进程,基于学习的需要,这里主要使用多进程来进行开发,当然,这会存在一定问题,后面会说。
    主要内容如下:
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.设计框架
    基于上面几个功能的需要, 
 
 
3.实现:数据库信息与程序源代码
    根据需求与设计框架,做如下的工作:
(1)数据库信息
1)管理系统登陆信息数据库
    这里存放的是该系统可以登陆的用户名密码等信息,只有在这里存在的用户名才能进行登陆,如下:
    
创建了manager_system数据库:
 
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| manager_system     |
| mysql              |
+--------------------+

 

 
3 rows in set (0.03 sec)
    
在manager_system数据库中创建了两种类型不同的表:
 
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就是用来存放系统用户信息的:
 
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中存放了两个用户信息:
 
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的数据库,只是在该数据库中创建了基于用户的不同表,如下:
 
两种类型不同的表:
 
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就是用来存放用户对应的主机列表:
 
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)
 
表中存放了用户可以管理的主机相关信息:
 
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)程序源代码
    有了上面的基本数据准备后,再看一下该程序的源代码,其中部分注释会给出,但是基于前面的介绍,代码应该也是比较容易理解的:
 
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 h 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('The file you want to distribute(or quit to quit):').strip()
                    start = time.time()
                    if file_name == 'quit':break
                    file_chcek = os.path.isfile(file_name)
                    log_list = []
                    if file_chcek == False:
                        print '\033[31;1mThe file does not exist or it is a directory!\033[0m'
                        continue
                    for h in server_list:
                        result_list.append(p.apply_async(distribute_file,[h,file_name,username]))   #the list save the suanfa
                    for res in result_list:
                        res.get()   #run the suanfa
                    end = time.time()
                    print '\033[31;1mCost time:%ss\033[0m' % str(end - start)
                s.close()

            #Exit the system
            elif choice == '3':    #退出系统
                sys.exit('\033[32;1mWelcome to use our system!\033[0m')

 

 
    程序的代码量不多,主要功能也有注释,只要的paramiko模拟的SSH命令执行与SFTP文件分发、Python多进程以及进程池的使用有所了解,还是比较容易理解的。
 

评论关闭