一 应用场景描述
有这个么一个需求:对很多台服务器的Java程序所占用的端口数量与正常的值进行对比确认。
可以使用如下命令得到结果:
netstat -tulnp|grep java|wc -l
但是服务器很多,每台手动去执行这条命令是不现实的。于是想到使用Ansible批量去执行,Ansible使用paramiko去ssh登录服务器执行命令,并使用mutilprocessing实现多进程ssh登录。
二 代码实现
如果直接使用Ansible去执行,不作任何处理的话。执行情况是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 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调用命令执行的详细信息。
完整代码如下:
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
|
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的内容如下:
1
2
3
4
5
6
|
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识别主机时只识别第一列。
执行代码的结果如下:
1
2
3
4
5
6
7
8
9
10
11
|
+-------------------+----------------------+-------------+-----------+--------+
| 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很有启示作用,如果自己以后想要对输出进行颜色处理可以直接参照这里的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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