Ansible运维自动化——增强Telnet模块功能
注意:读者需要python3和ansible playbook基础
Ansible可以说在自动化运维领域,算是十分方便的利器了。其无代理(被运维主机不需要安装插件)、依靠SSH完成所有运维操作。而且Ansible拥有丰富的模块、插件,基本上已经覆盖了所有的运维功能需求。
Ansible使用python开发的,而且源代码开源,如果懂python进行二次开发也十分方便。
基于Ansible通过SSH实现运维的特点,对于网络工程师实现大规模网络的自动化运维来说,更是方便的利器。但是,现今环境,做网络运维仍旧绕不过Telnet协议,对于Ansible缺少telnet方面的功能,算是缺憾了。
Ansible自2.4版本之后,增加了telnet模块。不过这个模块,仅仅是实现了Telnet。对于实际生产环境应用,还有很大差距。
首先,telnet使用方法见官方文档:
https://docs.ansible.com/ansible/latest/modules/telnet_module.html#telnet-module
文档下方有例子。我仅仅讲讲这个模块的问题:
- 不支持cisco交换机的enable模式,单纯通过写playbook脚本进入enable模式很麻烦,甚至可以说基本不可能实现。
- 部分交换机telnet登录不需要输入用户名,仅需要输入密码。官方模块的实现,是固定流程,第一步便是检查用户名输入,跳不过去的。
- playbook的when检查不能在每一条telnet命令上进行。有时候,我们需要检查上一条命令的回显结果来判断是否继续执行。
针对上面的问题,我对源代码进行了修改,具体的在我的gitee仓库中:
https://gitee.com/liudashang/ansible_telnet_plugin_plus
我在这里讲讲上面三个问题的解决方式:
1.enable模式的问题:
host = self._task.args.get('host', self._play_context.remote_addr)
user = self._task.args.get('user', self._play_context.remote_user)
password = self._task.args.get('password', self._play_context.password)
#在源代码的该位置增加如下代码:
en_pass = self._task.args.get('enablepassword')
此处代码的目的,主要是playbook的telnet模块下,增加enablepassword:项,用来输入enable密码。后台进程会通过en_pass变量获取密码。
下一步是插入进入enable模式的语句:
if password:
tn.read_until(to_bytes(password_prompt))
tn.write(to_bytes(password + "\n"))
#增加cisco的enable输入:
# enablepassword: 密码
if en_pass:
tn.expect(list(map(to_bytes, prompts)))
tn.write(to_bytes("enable\n"))
tn.read_until(to_bytes(password_prompt))
tn.write(to_bytes(en_pass + "\n"))
找到源代码中的if password一行,在相应位置插入注释后的五行代码,注意格式。这样,就实现了cisco enable模式的控制。当playbook中有enablepassword项时,会自动进入交换机的enable模式。
2.实现跳过用户名输入项
该项很简单,讲用户名输入操作的两行源代码改为if判断包括即可。但是,要注意,user变量一直是有用户名的,即便你不在playbook中填写user项,ansible也会默认调用inventory中的用户名。所以,我这里增加了一个“no-user”检查。
#增加没有hostname时的输入选项,当不需要hostname时:
# user: no-user
if 'no-user' not in user:
tn.read_until(to_bytes(login_prompt))
tn.write(to_bytes(user + "\n"))
3.检查上一条命令的回显结果
这个问题比较麻烦,我的思路使用正则表达式对回显结果进行检查,在playbook中写入正则表达式,后台进程通过获取正则表达式并运行,如果上一条命令结果符合正则表达式就终止,不符合就继续。
但在实际写代码的过程中,因为ansible会把playbook中提取到的变量内容先转换为字符串并保存至变量中,这样会造成正则表达式失效。
最后的解决方法是直接使用eval方法,在利用re模块compile预编译方式,直接在前台playbook文件中写入re.compile(r’.*’)语句,在后台直接用eval(str)方法将字符串还原为python语句执行。
贴代码:
for cmd in commands:
#增加类似when语句的检查项,使用python的正则表达式编译语句:
#在command下输入:
# - telnetcheck:re.compile(r'.*')
#会检查command的上一条语句的回显内容,如果符合强制结束,否则继续执行下一条。
#如果希望检查上一条回显的结果符合这则表达式就继续进行,可在re.compile语句后加“#not”
check_result = re.match(r'^telnetcheck:(.*)$',cmd)
if check_result and check_result.group(1):
pattern = eval(check_result.group(1))
expertion = 'pattern.search(output[-1].decode(\'utf-8\'))'
if '#not' in check_result.group(1):
expertion = 'not '+expertion
if eval(expertion):
break
else:
continue
display.vvvvv('>>> %s' % cmd)
tn.write(to_bytes(cmd + "\n"))
index, match, out = tn.expect(list(map(to_bytes, prompts)))
display.vvvvv('<<< %s' % cmd)
output.append(out)
sleep(pause)
实际生产过程中,我们或许会遇到符合正则表达式就继续,不符合就终止的操作,我利用“#not”标记,在re.compile()方法后添加#not标记,在eval转化后,实际就是在本行末尾增加了#注释,并不影响re.compile()的执行。通过判断#not标记是否存在,我们可以将终止判断条件同样利用eval的方式翻转。
做出上述三项修改后,ansible的telnet协议在生产环境使用就基本没有什么阻碍了。但是telnet主要是为了应对现有环境中telnet方式访问的交换机、路由器,有条件的还是建议把访问方式改为SSH,一是安全,二是有现成的模块可以使用比telnet要强。