S_lion's Studio

(十)ansible异常处理

字数统计: 1.7k阅读时长: 7 min
2021/12/02 Share

在日常使用ansible的过程中会发现,执行shell或command模块时,Ansible只认为0退出状态码是正确的,其它所有退出状态码都是失败的,但我们自己知道非0退出状态码并非一定代表着失败。

比如下例:

1
2
3
4
5
6
7
8
- hosts: localhost
gather_facts: false
tasks:
- shell: >
ls /home/aa
register: res
- debug:
var: res.stdout

执行结果会直接报错:

1
2
3
4
5
TASK [shell] *****************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "ls /home/aa\n", "delta": "0:00:00.034093", "end": "2021-12-02 14:16:45.029276", "msg": "non-zero return code", "rc": 2, "start": "2021-12-02 14:16:44.995183", "stderr": "ls: 无法访问/home/aa: 没有那个文件或目录", "stderr_lines": ["ls: 无法访问/home/aa: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}

PLAY RECAP *******************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

默认情况下,Ansible端无法连接某个节点时、某节点执行某个任务失败时,Ansible都会将这个节点从活动节点列表中(即play_hosts变量中)移除,以避免该节点继续执行之后的任务。用户可以去修改Ansible对这种异常现象的默认处理方式,比如遇到错误也不让该节点退出舞台,而是继续执行后续任务,又或者某节点执行任务失败并让整个play都失败。

fail模块

使用fail模块,可以人为制造一个失败的任务。

1
2
3
4
5
6
7
8
9
---
- hosts: test
gather_facts: false
tasks:
- fail:
msg: "已手动设置失败"
when: inventory_hostname == '192.168.100.10'
- debug:
msg: "成功了"

上面的fail会任务失败,并使得此节点不会执行后续任务,但其它节点会继续执行任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PLAY [test] ******************************************************************************************************

TASK [fail] ******************************************************************************************************
fatal: [192.168.100.10]: FAILED! => {"changed": false, "msg": "已手动设置失败"}
skipping: [192.168.100.11]

TASK [debug] *****************************************************************************************************
ok: [192.168.100.11] => {
"msg": "成功了"
}

PLAY RECAP *******************************************************************************************************
192.168.100.10 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
192.168.100.11 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

assert模块

assert与其他编程语言中的功能一致,断言功能,对于当满足某某条件时就失败的逻辑,可直接使用assert模块实现。

例如上面的例子可改为:

1
2
3
4
5
6
7
8
9
---
- hosts: test
gather_facts: false
tasks:
- assert:
that:
- inventory_hostname != '192.168.100.10'
fail_msg: "失败了"
success_msg: "成功了"

其中that参数接收一个列表,用于定义一个或多个条件,如果条件全为true,则任务成功,只要有一个条件为false,则任务失败。fail_msg(或其别名参数msg)定义任务失败时的信息,success_msg定义任务成功时的信息。

ignore_errors

当某个任务执行失败(或被Ansible认为失败,比如通过返回值判断)时,如果不想让这个失败的任务导致节点退出,可以使用ignore_errors指令来忽略失败。

例如开头的那个例子,加上ignore_errors指令:

1
2
3
4
5
6
7
8
9
- hosts: localhost
gather_facts: false
tasks:
- shell: >
ls /home/aa
ignore_errors: yes
register: res
- debug:
var: res.stdout

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
PLAY [localhost] *************************************************************************************************

TASK [shell] *****************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "ls /home/aa\n", "delta": "0:00:00.034821", "end": "2021-12-02 14:41:13.638790", "msg": "non-zero return code", "rc": 2, "start": "2021-12-02 14:41:13.603969", "stderr": "ls: 无法访问/home/aa: 没有那个文件或目录", "stderr_lines": ["ls: 无法访问/home/aa: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"res.stdout": ""
}

PLAY RECAP *******************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1

可以看到,加上ignore_errors后成功执行了debug任务,最后的任务汇总处的ignored=1证明忽略了一个错误。但是从结果中可以看到,虽然确实忽略了错误,但红红报错信息仍然提醒在终端上,让不了解此机制的人感觉很慌。这时可以使用failed_when解决。

failed_when

failed_when指令可以让用户自己定义任务何时失败:当条件表达式为true时任务强制失败,当条件表达式为false时,任务强制不失败。

接着改下上面的例子:

1
2
3
4
5
6
7
8
9
- hosts: localhost
gather_facts: false
tasks:
- shell: >
ls /home/aa
failed_when: false
register: res
- debug:
var: res.stdout

此时查看输出,已经没有相关报错了:

1
2
3
4
5
6
7
8
9
10
11
12
PLAY [localhost] *************************************************************************************************

TASK [shell] *****************************************************************************************************
changed: [localhost]

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"res.stdout": ""
}

PLAY RECAP *******************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

failed_when经常会和shell或command模块以及register指令一起使用,用来手动定义失败的退出状态码。比如,退出状态码为0 1 2都认为任务成功执行,其它状态码都认为认为执行失败。

1
2
3
4
5
6
7
8
9
- hosts: localhost
gather_facts: false
tasks:
- shell: >
ls /home/aa
failed_when: res.rc not in (0,1,2)
register: res
- debug:
var: res.stdout

failed_whenwhen一样都可以将多个条件表达式写成列表的形式来表示逻辑与。

rescue和always

Ansible允许在任务失败的时候,去执行某些任务,还允许不管任务失败与否,都执行某些任务。

  1. rescue和always都是block级别的指令
  2. rescue表示block中任意任务失败后,都执行rescue中定义的任务,但如果block中没有任务失败,则不执行rescue中的任务
  3. always表示block中任务无论失败与否,都执行always中定义的任务

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
---
- hosts: localhost
gather_facts: false
tasks:
- block:
- fail:
- debug: msg="hello world"
rescue:
- debug: msg="rescue1"
- debug: msg="rescue2"
always:
- debug: msg="always1"
- debug: msg="always2"

输出为:

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
PLAY [localhost] *************************************************************************************************

TASK [fail] ******************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"msg": "rescue1"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"msg": "rescue2"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"msg": "always1"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"msg": "always2"
}

PLAY RECAP *******************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0

block中的fail任务会失败,于是跳转到rescue中开始执行任务,然后再跳转到always中执行任务。

如果注释掉block中的fail模块任务,则block中没有任务失败,于是rescue中的任务不会执行,但是在执行完block中所有任务后会跳转到always中继续执行任务。

处理连接失败(unreachable)的异常

如果Ansible突然和某个节点无法连接上,会将此节点设置为UNREACHABLE状态,并从活动节点列表(play_hosts)中删除。

如果想要忽略连接失败的节点,可设置ignore_unreachable: true指令,该指令是Ansible 2.7添加的,可设置在play、Role、block、task级别上。

当Ansible遇到UNREACHABLE时,会进行连接重试。重试次数可在Ansible配置文件中配置:

1
2
[root@slions_pc1 ansible_poc]# cat /etc/ansible/ansible.cfg |grep -w retries
#retries = 3
CATALOG
  1. 1. fail模块
  2. 2. assert模块
  3. 3. ignore_errors
  4. 4. failed_when
  5. 5. rescue和always
  6. 6. 处理连接失败(unreachable)的异常