TT Lab
开始
学习 学习路径 课程

Ansible 基础

报告上写着 37 台成功 — 把失败设计进去

在 TT Lab 中继续学习

目标

在一个只让其中一台失败的三主机 inventory 中,逐一打开处理失败的旋钮,并通过标记文件亲自确认每次是什么停止了、什么继续运行。

为什么重要

失败是会发生的。要设计的不是消除失败,而是事先规定失败时会发生什么。Ansible 的默认值是“失败的主机悄悄退出,其余的继续”,对于逐台独立修理服务器的工作,这是好的默认值,但如果 40 台共同构成一个服务,这就是坏的默认值。所以本实验依次处理三个问题——这个失败是真正的失败吗(failed_when),失败的那台主机怎么处理(ignore_errors、block/rescue),其他主机怎么处理(any_errors_fatal、max_fail_percentage)。再加上在开始之前拦截的 assert,以及与失败分在不同列统计的 unreachable,就能读懂部署报告了。最后一步要亲手创建这份报告。

步骤

  1. 在 /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。
  2. 把 /root/ans/err/p02.yml 做成与第 1 步相同的结构,但给失败的 task 加上 ignore_errors,并把标记留在 /root/ans/err/a2/reached-<호스트이름>(占位符为主机名)。输出保存到 /root/ans/err/out/run2.txt。这次三台主机都必须留下标记,并且 PLAY RECAP 的 ignored 列必须增加。
  3. 把 /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。
  4. 用 /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。
  5. 把 /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。
  6. 把 /root/ans/err/p06.yml 做成与第 1 步相同的结构,但在 play 上开启 any_errors_fatal,并把标记留在 /root/ans/err/a6/reached-<호스트이름>(占位符为主机名)。输出保存到 /root/ans/err/out/run6.txt。这次任何主机都不能留下标记。
  7. 用 /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。
  8. 把 /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/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 输出中也混有相似的文字。评分器也会用它自己创建的记录文件来运行这个脚本,所以把答案写死在里面的脚本无法通过。