前言
当ansible
与设备进行交互时,会将对应的运行日志打印出来,日志内容对ansible脚本的设计人员和运维人员来讲可能还可以理解,但是如果将设计好需脚本提交给用户,纯粹的打印ansible日志,会导致ansible脚本的使用体验极差。
本方案通过简单的例子,讲解如何通过ansible内嵌的jinja2模块,使ansible脚本更加人性化。
过程
场景模拟
假设我们要对某服务器进行某些操作(例如: 打印hello world),我们需要尽可能的针对场景可能出现的所有情况进行处理,例如:当脚本运行成功时,这正常结束,当脚本运行失败时,打印或者抛出异常,使用户可以更好的定位问题,同时,由于无法一次性将所有可能发生的意外结果考虑到,脚本的设计需要具备一定的可扩展性。
理论分析ansible
与服务器(设备)进行交互时,根据不同的触发事件(例如: 运行成功
、执行异常退出
、设备登录超时
、用户名密码错误
等)可以生成产生对应不同的运行日志和执行状态,我们只需要捕捉到这些状态,即可手动打印对应报错信息,已提高脚本的可读性。
理论存在,开始实现
场景模拟:
ansible
控制被控制机执行成功并结束ansible
控制被控制机执行命令失败,抛出异常并打印失败信息ansible
控制被控制机执行,但是设备登录失败,抛出异常并打印信息ansible
控制被控制机执行,但是设备登录是用户名密码错误,抛出异常并打印信息环境信息(本地虚拟机演练)
控制机:172.160.100.40
被控制机:
ip:172.160.100.20
user:josen
场景演示
1. ansible
控制被控制机执行成功并结束
我们设计一个简单脚本,使被控制机打印hello world
并正常结束,内容如下:
---
- hosts: all
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "123456"
#gather_facts: false
tasks:
- name: hello
shell: "echo hello world"
register: run1
ignore_errors: True
执行结果如下:
同样,我们可以通过loop
使ansible一次性执行多条命令,同时使用debug
将执行结果打印出来
---
- hosts: all
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "123456"
run_commands:
- "echo hello world"
- "echo hihihi josen"
- "date "
#gather_facts: false
tasks:
- name: hello
shell: "{{item}}"
register: run1
ignore_errors: True
loop: "{{run_commands}}"
- name: debug
debug:
msg: "{{item.stdout}}"
loop: "{{run1.results}}"
执行结果如下:
2. ansible
控制被控制机执行命令失败,抛出异常并打印失败信息
我们采用exit 1
来模拟设备执行命令失败,ignore_errors
参数可以使ansible在执行到异常命令时忽略报错,继续向下执行,通过fail
主动抛出报错并将失败的命令打印出来
---
- hosts: all
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "123456"
run_commands:
- "echo hello world"
- "echo hihihi josen && exit 1"
- "date "
- "hi this is a error command"
- "echo 'this is a succeed command'"
#gather_facts: false
tasks:
- name: hello
shell: "{{item}}"
register: run1
ignore_errors: True
loop: "{{run_commands}}"
- name: fail the result
fail:
msg: "{{(item.stderr != '') | ternary(item.stderr, item.stdout) }}"
loop: "{{run1.results}}"
when: item.failed is defined and item.failed == true
执行结果如下:
相关场景已经模拟出来了,但是从结果看来,这么多的报错日志先让对用户而言不是很友好,对此,我们可以使用jinja2
来对结果进行优化改造,是其使用起来更加人性化
---
- hosts: all
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "123456"
run_commands:
- "echo hello world"
- "echo hihihi josen && exit 1"
- "date "
- "hi this is a error command"
- "echo 'this is a succeed command'"
#gather_facts: false
tasks:
- name: hello
shell: "{{item}}"
register: run1
ignore_errors: True
loop: "{{run_commands}}"
- name: fail the result
vars:
error_msg: "
{% if run1.results is defined %}
{% for ri in run1.results %}
{% if ri.failed is defined and ri.failed == true %}
执行【{{ri.item}}】命令时失败,具体报错信息为: 【{{ri.msg}}】
{% endif %}
{% endfor %}
{% elif run1.failed is defined and run1.failed == true %}
脚本执行异常,请联系管理员,异常信息如下: {{run1.msg}}
{% endif %}
"
fail:
msg: "{{error_msg}}"
when: (error_msg | trim) != ""
结果如下:
PS: 通过trim
过滤器可以移除字符串前后的所有空白字符
3. ansible
控制被控制机执行,但是设备登录失败,抛出异常并打印信息
当设备登录失败时,ansible并不会触发failed
异常,而是触发unreachable
异常,同时对应设备的所有未执行的task将全部结束,若要继续向下执行则需要做一定改造。
---
- hosts: all
vars:
ansible_connection: local
#gather_facts: false
tasks:
- name: hello
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "123456"
shell: "echo hello world"
register: run1
ignore_errors: True
ignore_unreachable: True
- name: fail the result
vars:
ansible_connection: local
error_msg: "
{% if run1.unreachable is defined and run1.unreachable == true %}
设备登录失败,请检查设备是否登录可达
{% endif %}
"
fail:
msg: "{{error_msg}}"
when: (error_msg | trim) != ""
我们运行脚本时输入一个错误的ip以触发登录失败报错,结果如下:
4. ansible
控制被控制机执行,但是设备登录是用户名密码错误,抛出异常并打印信息
设备登录用户名密码错误时,同样会触发unreachable
错误,因此可以在上一步的基础上添加一层if
判断
---
- hosts: all
vars:
ansible_connection: local
#gather_facts: false
tasks:
- name: hello
vars:
ansible_connection: ssh
ansible_ssh_user: "josen"
ansible_ssh_pass: "111111"
shell: "echo hello world"
register: run1
ignore_errors: True
ignore_unreachable: True
- name: debug
debug:
msg: "{{run1}}"
- name: fail the result
vars:
ansible_connection: local
error_msg: "
{% if run1.unreachable is defined and run1.unreachable == true %}
{% if 'Failed to connect to the host' in run1.msg %}
设备登录失败,请检查设备是否登录可达
{% elif 'Permission denied' in run1.msg %}
设备用户名密码错误,请检查!!!
{% else %}
设备登录异常,请联系管理员,异常信息【{{run1.msg}}】
{% endif %}
{% endif %}
"
fail:
msg: "{{error_msg}}"
when: (error_msg | trim) != ""
执行结果:
结论
本方案对脚本设计者和运维者而言没有特别大的作用,毕竟排查问题时,直接通过日志信息排查会更加准确,但是当你的脚本是给其他用户使用,尤其是对ansible不是特别了解的用户使用时,可以是用户快速的知道问题的大概范围(例如: 使用不当
、输入有误
、系统问题
等),从而对自身行为作出修正,降低对设计者的依赖度,使设计开发者可以全身心的投入到新的生产中。