报告上写着 37 台成功 — 把失败设计进去
目标
在一个只让其中一台失败的三主机 inventory 中,逐一打开处理失败的旋钮,并通过标记文件亲自确认每次是什么停止了、什么继续运行。
为什么重要
失败是会发生的。要设计的不是消除失败,而是事先规定失败时会发生什么。Ansible 的默认值是“失败的主机悄悄退出,其余的继续”,对于逐台独立修理服务器的工作,这是好的默认值,但如果 40 台共同构成一个服务,这就是坏的默认值。所以本实验依次处理三个问题——这个失败是真正的失败吗(failed_when),失败的那台主机怎么处理(ignore_errors、block/rescue),其他主机怎么处理(any_errors_fatal、max_fail_percentage)。再加上在开始之前拦截的 assert,以及与失败分在不同列统计的 unreachable,就能读懂部署报告了。最后一步要亲手创建这份报告。
步骤
- 在
/root/ans/err/hosts.ini中写入web组(web1、web2)、db组(db1)、ghosts组(ghost1)。四台主机都是ansible_host=127.0.0.1、ansible_user=root,端口只有ghost1是2223,其余都是2222。然后用/root/ans/err/p01.yml创建一个在web:db上运行的 play——第一个 task 只在web2上失败,第二个 task 留下/root/ans/err/a1/reached-<호스트이름>(占位符为主机名)。把运行输出保存到/root/ans/err/out/run1.txt。 - 把
/root/ans/err/p02.yml做成与第 1 步相同的结构,但给失败的 task 加上ignore_errors,并把标记留在/root/ans/err/a2/reached-<호스트이름>(占位符为主机名)。输出保存到/root/ans/err/out/run2.txt。这次三台主机都必须留下标记,并且PLAY RECAP的ignored列必须增加。 - 把
/root/ans/err/sample.conf创建为listen_port=8080、env=prod、workers=4三行。然后用/root/ans/err/p03.yml在web1上创建一个统计该文件中nosuchkey出现多少次的 task——因为只是查询,所以不要把它算作变更,并且要定下标准,使因找不到而出现的退出码不算失败。接着把该退出码以一行留到/root/ans/err/a3/grep-rc.txt,并把输出保存到/root/ans/err/out/run3.txt。 - 用
/root/ans/err/p04.yml创建一个在web1上运行的 play。在block内放一个失败的 task,以及其后创建/root/ans/err/a4/never.txt的 task,让rescue把失败 task 的名称写入/root/ans/err/a4/rescued.txt,让always留下/root/ans/err/a4/always.txt。输出保存到/root/ans/err/out/run4.txt。never.txt不能被创建,并且PLAY RECAP的rescued必须为 1。 - 把
/root/ans/err/p05.yml做成在localhost上运行的 play,但只放一个asserttask。检查deploy_env是否是dev、stage、prod之一,fail_msg要以허용되지 않은 배포 환경입니다(韩文,意为“不允许的部署环境”)开头,并一并显示当前值。把用-e deploy_env=prod运行的输出保存到/root/ans/err/out/assert-ok.txt,把用-e deploy_env=qa运行的输出保存到/root/ans/err/out/assert-fail.txt。 - 把
/root/ans/err/p06.yml做成与第 1 步相同的结构,但在 play 上开启any_errors_fatal,并把标记留在/root/ans/err/a6/reached-<호스트이름>(占位符为主机名)。输出保存到/root/ans/err/out/run6.txt。这次任何主机都不能留下标记。 - 用
/root/ans/err/p07.yml创建一个在web:db上运行的 play。max_fail_percentage为50,变量run_tag的默认值为continue,变量doomed的默认值只有web2一个。第一个 task 创建/root/ans/err/a7/<run_tag>目录,下一个 task 只在doomed中包含的主机上失败,最后一个 task 留下/root/ans/err/a7/<run_tag>/reached-<호스트이름>(占位符为主机名)。用默认值运行一次,保存到/root/ans/err/out/run7-continue.txt,再把run_tag设为abort,并在doomed中放入web2和db1运行一次,保存到/root/ans/err/out/run7-abort.txt。 - 把
/root/ans/err/p08.yml做成在all上运行的 play——第一个 task 是ping,但要忽略连接不上的情况,第二个 task 留下/root/ans/err/a8/reached-<호스트이름>(占位符为主机名)。输出保存到/root/ans/err/out/run8.txt。然后编写/root/ans/err/recap.sh:以一个运行记录文件为参数,输出一行hosts=N ok=N changed=N unreachable=N failed=N skipped=N rescued=N ignored=N,并用它创建/root/ans/err/out/failure-report.json。键有六个——default_failed(第 1 步运行的 failed 之和)、ignored(第 2 步)、rescued(第 4 步)、unreachable(第 8 步)、fatal_reached(a6中的标记数)、maxfail_reached(a7/continue中的标记数),值都是数字。
参考
- 工作目录是
/root/ans/err。inventory 中的主机连接的是这个 Pod 内 127.0.0.1:2222 的 sshd,只有ghost1指向没有任何程序在监听的 2223。 - 失败的 playbook 会以非 0 的值结束。保存输出到文件时,请不要让 shell 在那里停止。
PLAY RECAP有七列——ok、changed、unreachable、failed、skipped、rescued、ignored。- 常见错误:指望
ignore_errors对连接不上的主机起作用。那不是 task 的失败,而是连接的失败。 - 常见错误:没有创建标记目录就运行,导致无法区分“是空的”和“不存在”。
- 常见错误:只把被
rescue救回来的运行看作绿灯,而没有在任何地方留下回滚发生过的事实。 - 错误处理指南 · 用 block 组合 task · assert 模块 · fail 模块 · 执行策略与 forks
默认行为——只有失败的主机退出
在 /root/ans/err/hosts.ini 中写入 web 组(web1、web2)、db 组(db1)、ghosts 组(ghost1)。四台主机都是 ansible_host=127.0.0.1、ansible_user=root,端口只有 ghost1 是 2223,其余都是 2222。然后用 /root/ans/err/p01.yml 创建一个在 web:db 上运行的 play——第一个 task 只在 web2 上失败,第二个 task 留下 /root/ans/err/a1/reached-<호스트이름>(占位符为主机名)。把运行输出保存到 /root/ans/err/out/run1.txt。
由于所有主机连接的都是同一个 sshd,所以“只有这一台失败”要用 inventory_hostname 条件来制造,而不是靠文件。ghost1 的端口是没有任何程序在监听的编号,所以在第 8 步中充当连接不上的主机。由于 playbook 会以非 0 的值结束,保存输出时请不要让脚本在那里停止。
只忽略结果会有什么不同
把 /root/ans/err/p02.yml 做成与第 1 步相同的结构,但给失败的 task 加上 ignore_errors,并把标记留在 /root/ans/err/a2/reached-<호스트이름>(占位符为主机名)。输出保存到 /root/ans/err/out/run2.txt。这次三台主机都必须留下标记,并且 PLAY RECAP 的 ignored 列必须增加。
ignore_errors 不改变判定,只是忽略结果。所以 task 仍然被记录为失败,但主机不会退出。请亲自查看在 PLAY RECAP 中,增加的不是 failed,而是哪一列。
亲自决定把什么视为失败
把 /root/ans/err/sample.conf 创建为 listen_port=8080、env=prod、workers=4 三行。然后用 /root/ans/err/p03.yml 在 web1 上创建一个统计该文件中 nosuchkey 出现多少次的 task——因为只是查询,所以不要把它算作变更,并且要定下标准,使因找不到而出现的退出码不算失败。接着把该退出码以一行留到 /root/ans/err/a3/grep-rc.txt,并把输出保存到 /root/ans/err/out/run3.txt。
要找的词语不存在时是 1,文件不存在或参数错误时是 2 以上。前者是答案,后者是错误。请用 register 接收的结果的退出码来写标准。这与用 ignore_errors 盖住是两回事——这个 playbook 必须以 failed=0 干干净净地结束。
用 block、rescue、always 把回滚做成结构
用 /root/ans/err/p04.yml 创建一个在 web1 上运行的 play。在 block 内放一个失败的 task,以及其后创建 /root/ans/err/a4/never.txt 的 task,让 rescue 把失败 task 的名称写入 /root/ans/err/a4/rescued.txt,让 always 留下 /root/ans/err/a4/always.txt。输出保存到 /root/ans/err/out/run4.txt。never.txt 不能被创建,并且 PLAY RECAP 的 rescued 必须为 1。
失败 task 的名称放在只能在 rescue 内使用的变量中——名称里包含 failed 和 task。如果 rescue 一直成功到最后,这台主机就被视为没有失败,成为绿灯。所以把回滚发生过的证据留成文件,就是这个结构的一半。
前提不符,就在碰任何东西之前停止
把 /root/ans/err/p05.yml 做成在 localhost 上运行的 play,但只放一个 assert task。检查 deploy_env 是否是 dev、stage、prod 之一,fail_msg 要以 허용되지 않은 배포 환경입니다(韩文,意为“不允许的部署环境”)开头,并一并显示当前值。把用 -e deploy_env=prod 运行的输出保存到 /root/ans/err/out/assert-ok.txt,把用 -e deploy_env=qa 运行的输出保存到 /root/ans/err/out/assert-fail.txt。
默认的失败消息只有一行,所以在有多个条件时,无法知道是哪一个为假。如果把当前值直接写进消息,一行日志就能解决。命令行变量比 play 的 vars 优先级更高,所以同一个 playbook 只改变值,就可以运行两次。评分器也会用同样的方法直接运行这个 playbook。
只要有一台失败,就全部停止
把 /root/ans/err/p06.yml 做成与第 1 步相同的结构,但在 play 上开启 any_errors_fatal,并把标记留在 /root/ans/err/a6/reached-<호스트이름>(占位符为主机名)。输出保存到 /root/ans/err/out/run6.txt。这次任何主机都不能留下标记。
这个旋钮加在 play 级别(而不是 task)。没有失败的主机也不会执行剩下的 task,所以标记目录会是空的。请看 PLAY RECAP 中,没有失败的主机的 ok 列变成了什么。标记目录必须事先创建好,才能区分“是空的”和“不存在”。
最多能容忍几台——用比例来定
用 /root/ans/err/p07.yml 创建一个在 web:db 上运行的 play。max_fail_percentage 为 50,变量 run_tag 的默认值为 continue,变量 doomed 的默认值只有 web2 一个。第一个 task 创建 /root/ans/err/a7/<run_tag> 目录,下一个 task 只在 doomed 中包含的主机上失败,最后一个 task 留下 /root/ans/err/a7/<run_tag>/reached-<호스트이름>(占位符为主机名)。用默认值运行一次,保存到 /root/ans/err/out/run7-continue.txt,再把 run_tag 设为 abort,并在 doomed 中放入 web2 和 db1 运行一次,保存到 /root/ans/err/out/run7-abort.txt。
三台中有一台是 33%,两台是 66%。是否超过标准,就是分岔口。在命令行中覆盖列表变量时,整个用 JSON 传入会比较方便。创建标记目录的 task 必须放在失败的 task 之前,这样在停止的运行中,也会留下空目录。
连接不上的主机,以及把六次运行汇总成一份
把 /root/ans/err/p08.yml 做成在 all 上运行的 play——第一个 task 是 ping,但要忽略连接不上的情况,第二个 task 留下 /root/ans/err/a8/reached-<호스트이름>(占位符为主机名)。输出保存到 /root/ans/err/out/run8.txt。然后编写 /root/ans/err/recap.sh:以一个运行记录文件为参数,输出一行 hosts=N ok=N changed=N unreachable=N failed=N skipped=N rescued=N ignored=N,并用它创建 /root/ans/err/out/failure-report.json。键有六个——default_failed(第 1 步运行的 failed 之和)、ignored(第 2 步)、rescued(第 4 步)、unreachable(第 8 步)、fatal_reached(a6 中的标记数)、maxfail_reached(a7/continue 中的标记数),值都是数字。
连接不上不是 task 的失败,而是连接的失败,所以另有忽略它的参数。这个参数只对加了它的 task 起作用,而且即使决定忽略,playbook 的退出码也不是 0。求和必须只读 PLAY RECAP 行之下的内容——上面的 task 输出中也混有相似的文字。评分器也会用它自己创建的记录文件来运行这个脚本,所以把答案写死在里面的脚本无法通过。