一 应用场景描述

有这个么一个需求:对很多台服务器的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 >>
3

172.30.25.179 | success | rc=0 >>
19

172.30.25.180 | success | rc=0 >>
86

172.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.runner
from ansible.color import stringc

host_list='qa_servers.txt'
private_key_file='/root/.ssh/id_rsa'
pattern='*'
forks=10
timeout=30
module_name='shell'
# construct the ansible runner and execute on all hosts
results = 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 results
if 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               #3
172.30.25.179   #testttttttt179      #30
172.30.25.180   #testttttttt180      #45
172.30.25.181   #testttttttt181      #55
172.30.25.172   #test                #68
172.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命令






参考资料:

http://docs.ansible.com/ansible/developing_api.html