一 应用场景描述

有这个么一个需求:对很多台服务器的Java程序所占用的端口数量与正常的值进行对比确认。

可以使用如下命令得到结果:

netstat -tulnp|grep java|wc -l

但是服务器很多,每台手动去执行这条命令是不现实的。于是想到使用Ansible批量去执行,Ansible使用paramiko去ssh登录服务器执行命令,并使用mutilprocessing实现多进程ssh登录。

二 代码实现

如果直接使用Ansible去执行,不作任何处理的话。执行情况是这样的:

# ansible -i qa_servers.txt all  --private-key=/root/.ssh/id_rsa  -m shell -a "netstat -tulnp|grep java|wc -l"172.30.25.71 | success | rc=0 >>3172.30.25.179 | success | rc=0 >>19172.30.25.180 | success | rc=0 >>86172.30.25.181 | success | rc=0 >>82

ansible就是Ansible命令执行的命令, -i 单独指定hosts文件   --private-key指定ssh私钥路径   -m指定需要调用的模块,这里调用shell模块

-a 指定给模块传递的参数,这里既是要执行的命令

这里出现了几个问题:

a.每台服务器上的正常Java端口数量不同,并且需要和正常的值进行比对和确认,最好可以打印输出结果

b.在命令行直接调用ansible虽然对每台服务器都执行了命令但是输出结果很不规范,需要进一步处理。

Ansible本来就是Python开发的,安装完Ansible后所有的代码都位于/usr/lib/python2.6/site-packages/ansible/目录下,所以可以当作阅读正常python代码来查看Ansible源代码。

再通过查看Ansible官网的代码示例得知Ansible执行命令都是通过

/usr/lib/python2.6/site-packages/ansible/runner/__init__.py 代码中的Runner类的run函数去执行

所以想要对Ansible的输出结果进一步处理,首先要获取Ansible调用命令执行的详细信息。

完整代码如下:

import ansible.runnerfrom ansible.color import stringchost_list='qa_servers.txt'private_key_file='/root/.ssh/id_rsa'pattern='*'forks=10timeout=30module_name='shell'# construct the ansible runner and execute on all hostsresults = ansible.runner.Runner(host_list=host_list,private_key_file=private_key_file,pattern=pattern,forks=forks,timeout=timeout,module_name=module_name,module_args=module_args                               ).run()#print resultsif results is None:   print "No hosts found"   sys.exit(1)print stringc("+-------------------+----------------------+-------------+-----------+--------+",color='cyan')print stringc("|   HOST            |   HOSTNAME           | CHICKED NUM | RIGHT NUM | STATUS |",color='cyan')print stringc("+-------------------+----------------------+-------------+-----------+--------+",color='cyan')for (hostname, result) in results['contacted'].items():    if not 'failed' in result:        num_default=int(subprocess.Popen(''' awk '/%s/{print $3}' %s|sed -n 's/#//p' ''' %(hostname,host_list),shell=True,stdout=subprocess.PIPE).communicate()[0].split('\n')[0])        num=int(result['stdout'].split('\n')[0])        host_name=result['stdout'].split('\n')[1]        if num==num_default:           status=stringc('PASS',color='green')        else:           status=stringc('WARN',color='red')        print stringc("| %-17s | %-20s | %-11d | %-9d | %-12s   ",color='cyan') %(hostname,host_name,num,num_default,status) + stringc("|",color='cyan')        print stringc("+-------------------+----------------------+-------------+-----------+--------+",color='cyan')#        print "%s >>> %s" % (hostname, result['stdout'])#print "FAILED *******"#for (hostname, result) in results['contacted'].items():#    if 'failed' in result:#        print "%s >>> %s" % (hostname, result['msg'])#print "DOWN *********"#for (hostname, result) in results['dark'].items():#    print "%s >>> %s" % (hostname, result)

qa_servers.txt的内容如下:

172.30.25.71    #testt               #3172.30.25.179   #testttttttt179      #30172.30.25.180   #testttttttt180      #45172.30.25.181   #testttttttt181      #55172.30.25.172   #test                #68172.30.25.173   #test                #7

第一列是需要执行命令的主机IP或者主机名,第二列是主机名,第三列就是正常的Java程序占用端口数量。第二列和第三列必须要注释掉。Ansible识别主机时只识别第一列。

执行代码的结果如下:

+-------------------+----------------------+-------------+-----------+--------+|   HOST            |   HOSTNAME           | CHICKED NUM | RIGHT NUM | STATUS |+-------------------+----------------------+-------------+-----------+--------+| 172.30.25.180     | testtesttesttt       | 86          | 45        | WARN   |+-------------------+----------------------+-------------+-----------+--------+| 172.30.25.181     | testttttttt181       | 82          | 55        | WARN   |+-------------------+----------------------+-------------+-----------+--------+| 172.30.25.179     | testttttttt179       | 19          | 30        | WARN   |+-------------------+----------------------+-------------+-----------+--------+| 172.30.25.71      | testt                | 3           | 3         | PASS   |+-------------------+----------------------+-------------+-----------+--------+

代码中要点:

a.直接使用Ansible的代码进行模块调用。

b.使用Ansible的color.py对输出结果进行颜色处理,PASS是绿色显示,WARN是红色显示。这里的color.py很有启示作用,如果自己以后想要对输出进行颜色处理可以直接参照这里的代码

codeCodes = {    'black':     '0;30', 'bright gray':    '0;37',    'blue':      '0;34', 'white':          '1;37',    'green':     '0;32', 'bright blue':    '1;34',    'cyan':      '0;36', 'bright green':   '1;32',    'red':       '0;31', 'bright cyan':    '1;36',    'purple':    '0;35', 'bright red':     '1;31',    'yellow':    '0;33', 'bright purple':  '1;35',    'dark gray': '1;30', 'bright yellow':  '1;33',    'normal':    '0'}def stringc(text, color):    """String in color."""    if ANSIBLE_COLOR:        return "\033["+codeCodes[color]+"m"+text+"\033[0m"    else:        return text

c.使用python的subprocess模块调用shell命令

参考资料: