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

Ansible 基础

标签与局部执行:被切开的运行不承诺最终状态

在 TT Lab 中继续学习

一句话总结

tag 是用来选择“这次只运行这个”的装置,并且会从所附着的位置向下继承。然而用 tag 截取出来的执行,并不保证达到 playbook 所承诺的最终状态——知道这一点,比 tag 的语法更重要。

为什么需要它

playbook 会不断生长。起初只有五个 task,半年后变成八十个。总会有那么一天,只想修改配置文件中的一行。全部运行的话,从收集 fact 到确认软件包要花十五分钟,这期间会有二十个不想碰的 task 被执行。

tag 就是对这个问题的回答。先给 task 贴上标签,再用 --tags config 只运行带有那个标签的 task。反过来,也可以用 --skip-tags slow 只排除慢的那些。语法很简单,所以人们学会语法的第二天就会在生产环境里使用。

事故就出在这里。tag 是截断执行的刀,而 playbook 并不是被设计成可以被截断的。第 8 个 task 创建的目录被第 12 个 task 使用,第 20 个 task 修改的配置由 handler 接收后让服务重新读取。如果用 --tags config 只运行第 12 个,就会因为没有目录而失败,或者更糟,假装成功,却留下一个只对了一半的状态。

所以本模块的主题有两层。tag 的语法和继承是一层,如何辨认可以被截断的 playbook 是另一层。

工作原理

tag 可以附着在四个位置,并向下继承。

附着位置 继承范围
task 仅该 task
block block 内的所有 task
play 该 play 的所有 task
role / import_tasks / include_tasks 见下文说明

运行一下 --list-tasks,继承就看得见了。如果给 play 加上 playtag,所有 task 的 tag 列表中都会跟着出现 playtag。如果给 block 加上 blocky,block 内的两个 task 就各自拥有 blocky。

import_tasks 与 include_tasks 在这里出现分歧。import_tasks 是静态的——读取 playbook 时,task 就被展开到那个位置,加在 import 语句上的 tag 会被继承到每一个展开的 task 上。include_tasks 是动态的——要到运行中 task 才会插进来。所以加在 include 语句上的 tag 只附着在 include 语句本身,不会继承到里面。

这个差别造成的结果,初次见到会让人困惑。给出 --tags included 时,include 语句因为 tag 匹配而被执行,而从中插进来的 task 没有 included tag,所以全部被过滤掉。没有任何错误,什么也没有发生。官方文档建议,如果想给 include 里面的 task 也加 tag,就使用 apply 关键字,或者干脆使用 import_tasks。

有五个特殊 tag。

tag 含义
always 除非用 --skip-tags always 明确排除,否则始终运行
never 除非直接指名,否则绝不运行
tagged 带有至少一个 tag 的所有 task
untagged 一个 tag 都没有的所有 task
all 全部(默认值)

always 加在 inventory 的 fact 收集或公共变量设置这类无论怎样选择都不能被漏掉的 task 上。never 用于危险的 task——删除数据库或重新部署全部内容——把它们写在 playbook 中,但不会因为手滑而被运行。如果给带有 never 的 task 同时加上 danger 之类的标签,就只有用 --tags danger 指名时才会运行。

有一个地方要注意。如果给 play 加上 tag,那个 play 的所有 task 都成了“有 tag 的”。这样一来,--tags untagged 什么也选不中,而 --tags tagged 会选中全部。继承就这样连特殊 tag 的含义也改变了。

运行之前先看列表。--list-tags 显示这个 playbook 中有哪些 tag,--list-tasks 按顺序显示这次选择会运行什么。两者都不会连接目标,也不会改变任何东西。如果同时加上 --tags,就会得到反映了这个选择的列表——在生产环境第一次使用 tag 时,先看这个的习惯,可以让事故减半。

也有从中间开始运行的路径。--start-at-task "태스크 이름"(占位符为 task 名称)会从该名称的 task 开始。用于长 playbook 在第 40 个失败时,避免从第 1 个重新运行。--step 会在每个 task 处询问人再继续——由于是交互式的,不能用于自动化,但在手工跟着看第一次见到的 playbook 时很有用。

--start-at-task 比 tag 更危险。因为它是在前面 task 创建的内容和前面 task 发出的 notify 全都不存在的状态下,从中间开始的。即使结束时出现 failed=0,系统也不是 playbook 所承诺的状态。

总结一下部分执行之所以危险的两种方式。

第一,依赖的 task 被漏掉。第 7 个 task 要使用第 3 个 task 的 set_fact 所确定的值,但用 --tags 只选了第 7 个,那个变量就是未定义的。运气好会立即失败,运气不好则会以默认值填进莫名其妙的东西。

第二,handler 不运行。handler 必须收到 notify 才会运行。如果修改配置的 task 被排除在选择之外,handler 就没有人叫它,悄悄地过去了。反过来,在只选了配置 task 的运行中,handler 会正常运行——也就是说,handler 是否运行由“谁 notify 了”决定,而不是由“tag”决定。混淆这两者,就会在错误的地方寻找“加了 tag 之后服务没有重启”的原因。

在现场相遇的样子

第一,用 --tags 做首次部署。在新服务器上只加 --tags config,会因为没有目录而失败。tag 是用于已经完整运行过一次的系统的工具。第一次收敛要整体运行。

第二,tag 取代了文档。tag 有三十个时,没有人知道该加哪一个才安全。tag 要保持在五个左右,成套使用的组合,可以用脚本或 Makefile 起名保存下来。

第三,没有加 never 而出了事故。有的团队把“全部重新安装”的 task 注释掉。注释总有一天会被解开。如果加上 never tag,它就以代码形式保留下来,又不会被误运行。

第四,从中间开始运行之后,相信绿灯。用 --start-at-task 恢复的部署即使以成功结束,前面的 task 本应留下的东西也并不存在。恢复之后,再整体运行一次并确认 changed=0,才是真正的结束条件。

第五,这个实验环境的如实局限。--step 是需要人按键才能继续的交互式模式,评分器无法判定,所以从实验中去掉了。给 role 加 tag 的内容,由于 role 本身是 ansible-advanced 的主题,所以这里也不涉及。

参考文档

下一项实验要做什么

给 task 加上 tag,用 --tags 缩小范围,用 --list-tags 和 --list-tasks --tags 在运行之前先看会运行什么,再用 --skip-tags 排除。给 block 和 play 加上 tag,确认继承在列表中如何体现,加上 always 和 never,亲自测量特殊 tag 的含义,把 import_tasks 与 include_tasks 并排放置,用列表区分 tag 被继承的一侧和没有被继承的一侧。用 --start-at-task 从中间运行之后,确认配置仍是旧值、handler 没有运行,最后亲手创建一个判定哪些 tag 选择可以完整跑完的工具。