只跑了一部分,为什么就不是最终状态了
目标
学会用 tag 只运行 playbook 的一部分,并亲自测量这种便利与什么样的风险相伴。最后创建一个判定哪些 tag 选择可以完整跑完的工具。
为什么重要
playbook 会不断生长。总会有那么一天,task 有八十个,却只想修改配置中的一行,tag 就是放在这个位置上的工具。语法很简单,所以人们学会的第二天就会在生产环境中使用。事故就出在这里——tag 是截断执行的刀,而 playbook 并不是被设计成可以被截断的。前面 task 创建的目录被后面的 task 使用,修改了配置的 task 会叫 handler。只选一部分,这种连接就会断开,运气不好的话,甚至不会失败,而是留下一个只对了一半的状态。所以本实验由两半构成:一半是学习 tag 语法,另一半是亲眼看到截断的执行无法保证什么。
步骤
- 创建
/root/anstags/hosts.ini——在[web]中放入web1(ansible_host=127.0.0.1、ansible_port=2222),并通过[all:vars]设置ansible_user=root。创建/root/anstags/site.yml:包含 play 变量app_env(默认lab)、handlerreload app(在/root/anstags/out/reload.marker中写入一行reloaded),以及四个 task——배포 자리를 만든다(韩文,意为“创建部署位置”)以 0755 创建/root/anstags/app目录(tagsetup),설정을 쓴다(韩文,意为“写入配置”)以 0644 在/root/anstags/app/app.conf中写入一行env=<app_env>并notifyhandler(tagconfig),배포 번호를 정한다(韩文,意为“确定部署编号”)把release_id定为r-2026(tagprep,set_fact),배포 번호를 기록한다(韩文,意为“记录部署编号”)以 0644 把release_id写入/root/anstags/app/release.txt(tagdeploy)。整体收敛一次,把输出保存到/root/anstags/out/full.txt,接着用--tags config再运行一次,保存到/root/anstags/out/config.txt。 - 把
ansible-playbook -i hosts.ini site.yml --list-tags的输出保存到/root/anstags/out/list-tags.txt。然后把--list-tasks --tags config的输出保存到/root/anstags/out/list-config.txt。第二个文件中必须有설정을 쓴다(韩文,意为“写入配置”),而不能有배포 번호를 기록한다(韩文,意为“记录部署编号”)。 - 用
--skip-tags prep,deploy真正运行 playbook,把输出保存到/root/anstags/out/skip.txt。输出中必须有배포 자리를 만든다(韩文,意为“创建部署位置”)和설정을 쓴다(韩文,意为“写入配置”)的TASK [...]行,而不能有배포 번호를 정한다(韩文,意为“确定部署编号”)和배포 번호를 기록한다(韩文,意为“记录部署编号”)的行。并且把--list-tasks --skip-tags prep,deploy的输出保存到/root/anstags/out/list-skip.txt。 - 给 play 本身加上 tag
platform。然后在 task 末尾添加一个名为설정을 검증한다(韩文,意为“验证配置”)的 block,并给这个 block 加上 tagverify——block 内有两个没有加 tag 的 task:读取/root/anstags/app/app.conf的状态并以conf_statregister 的설정 파일의 상태를 읽는다(韩文,意为“读取配置文件的状态”),以及根据其结果断言文件存在的설정 파일이 있는지 단언한다(韩文,意为“断言配置文件存在”)。把--list-tasks的完整输出保存到/root/anstags/out/inherit.txt,把--list-tasks --tags verify的输出保存到/root/anstags/out/block.txt。 - 再添加两个 task。
어떤 선택에서도 남기는 표식(韩文,意为“在任何选择下都会留下的标记”)以 0644 在/root/anstags/out/always.marker中写入一行always,tag 只有always一个。함부로 돌면 안 되는 태스크(韩文,意为“不能随便运行的 task”)以 0644 在/root/anstags/out/never.marker中写入一行danger,tag 有never和danger两个。用--tags config真正运行,把输出保存到/root/anstags/out/always.txt——어떤 선택에서도 남기는 표식(韩文,意为“在任何选择下都会留下的标记”)必须一起运行。并且把--list-tasks --tags danger的输出保存到/root/anstags/out/danger.txt。/root/anstags/out/never.marker在本实验结束之前不能被创建。 - 创建
/root/anstags/tasks/common.yml——两个 task。공통 점검 하나(韩文,意为“公共检查一”)输出common-one,没有 tag。공통 점검 둘(韩文,意为“公共检查二”)输出common-two,带有 tagdeep。然后在site.yml末尾再添加两个 task——import 로 공통 점검을 끌어온다(韩文,意为“用 import 引入公共检查”,用import_tasks引入该文件,tag 为imported)和include 로 공통 점검을 끌어온다(韩文,意为“用 include 引入公共检查”,用include_tasks引入同一个文件,tag 为included)。把--list-tasks --tags imported的输出保存到/root/anstags/out/reuse-import.txt,把--list-tasks --tags included的输出保存到/root/anstags/out/reuse-include.txt。 - 先删除
/root/anstags/out/reload.marker。然后用--start-at-task "배포 번호를 정한다" -e app_env=prod(韩文,意为“确定部署编号”)真正运行 playbook,把输出保存到/root/anstags/out/startat.txt。结束之后,把/root/anstags/app/app.conf的内容和/root/anstags/out/reload.marker是否存在,接着记录到/root/anstags/out/aftermath.txt——第一行是conf=<app.conf 의 env 줄>(占位符为 app.conf 中的 env 行),第二行是marker=<yes 또는 no>(占位符为 yes 或 no)。 - 创建
/root/anstags/tag-check.sh <태그목록>(占位符为 tag 列表)。用该选择以检查模式运行site.yml,如果正常结束,就在首行输出SAFE <태그목록>(占位符为 tag 列表)并以 0 结束;如果没有正常结束,就在首行输出UNSAFE <태그목록> rc=<종료코드>(占位符依次为 tag 列表与退出码)并以 1 结束。然后依次运行./tag-check.sh deploy和./tag-check.sh prep,deploy,把两行依次追加保存到/root/anstags/out/tagcheck.txt。前者必须是 UNSAFE,后者必须是 SAFE。
参考
- 请先在第 1 步创建 inventory 和 playbook,并整体收敛一次。tag 是在这之后才使用的工具。
- 命令提示:
--list-tags告诉你有哪些 tag,--list-tasks [--tags X]告诉你该选择会运行什么。两者都不连接目标,所以在生产环境中也是安全的。 - 命令提示:task 名称中有空格,所以要像
--start-at-task '배포 번호를 정한다'(韩文,意为“确定部署编号”)这样用引号括起来。 - 常见错误:以为被
--tags过滤掉的 task 会显示为skipping。被过滤掉的 task 会整个从输出中消失,摘要中的 skipped 也仍然是 0。 - 常见错误:给
include_tasks加了 tag 并用该 tag 运行之后,因为“什么都没发生”而惊慌。 - 常见错误:看到从中间开始运行的执行以
failed=0结束,就判断部署已经完成。 --step是需要人按键才能继续的交互式模式,所以本实验不涉及。给 role 加 tag 的内容,由于 role 本身是下一门课程的主题,所以这里也不涉及。- 用 tag 只运行一部分 · 从中间开始运行 · import 与 include · handler · ansible-playbook 选项
给 task 加上 tag,并整体收敛一次
创建 /root/anstags/hosts.ini——在 [web] 中放入 web1(ansible_host=127.0.0.1、ansible_port=2222),并通过 [all:vars] 设置 ansible_user=root。创建 /root/anstags/site.yml:包含 play 变量 app_env(默认 lab)、handler reload app(在 /root/anstags/out/reload.marker 中写入一行 reloaded),以及四个 task——배포 자리를 만든다(韩文,意为“创建部署位置”)以 0755 创建 /root/anstags/app 目录(tag setup),설정을 쓴다(韩文,意为“写入配置”)以 0644 在 /root/anstags/app/app.conf 中写入一行 env=<app_env> 并 notify handler(tag config),배포 번호를 정한다(韩文,意为“确定部署编号”)把 release_id 定为 r-2026(tag prep,set_fact),배포 번호를 기록한다(韩文,意为“记录部署编号”)以 0644 把 release_id 写入 /root/anstags/app/release.txt(tag deploy)。整体收敛一次,把输出保存到 /root/anstags/out/full.txt,接着用 --tags config 再运行一次,保存到 /root/anstags/out/config.txt。
tag 是用于已经完整运行过一次的系统的工具。在新服务器上只加 --tags config 会因为没有目录而失败,所以第一次收敛必须整体运行——因此这一步要先做整体运行。tag 用 tags: [이름](占位符为 tag 名称)加到 task 上。请确认,在用 --tags config 运行的输出中,其余三个 task 的 TASK [...] 行根本不存在——被过滤掉的 task 不会显示为被跳过,而是整个从输出中消失。
运行之前,先用列表看会运行什么
把 ansible-playbook -i hosts.ini site.yml --list-tags 的输出保存到 /root/anstags/out/list-tags.txt。然后把 --list-tasks --tags config 的输出保存到 /root/anstags/out/list-config.txt。第二个文件中必须有 설정을 쓴다(韩文,意为“写入配置”),而不能有 배포 번호를 기록한다(韩文,意为“记录部署编号”)。
这两个工具既不连接目标,也不改变任何东西——在生产环境第一次使用 tag 时,最先要设置的就是它们。--list-tags 告诉你“这个 playbook 中有哪些 tag”,--list-tasks 按顺序告诉你“这次选择会运行什么”。给 --list-tasks 同时加上 --tags,选择就会原样反映出来。每个 task 所带的 tag 列表也出现在同一行,所以确认继承时也会用到。
改为用排除的方式选择
用 --skip-tags prep,deploy 真正运行 playbook,把输出保存到 /root/anstags/out/skip.txt。输出中必须有 배포 자리를 만든다(韩文,意为“创建部署位置”)和 설정을 쓴다(韩文,意为“写入配置”)的 TASK [...] 行,而不能有 배포 번호를 정한다(韩文,意为“确定部署编号”)和 배포 번호를 기록한다(韩文,意为“记录部署编号”)的行。并且把 --list-tasks --skip-tags prep,deploy 的输出保存到 /root/anstags/out/list-skip.txt。
选择的方法有两种——指名要包含的(--tags),或者指名要排除的(--skip-tags)。tag 增多时,后者往往更方便。也可以同时给出两者,那时排除的一方获胜。列表用逗号连接。运行之前先用列表确认的习惯,在 --skip-tags 上同样有价值。
加在 block 和 play 上的 tag 会向下流动
给 play 本身加上 tag platform。然后在 task 末尾添加一个名为 설정을 검증한다(韩文,意为“验证配置”)的 block,并给这个 block 加上 tag verify——block 内有两个没有加 tag 的 task:读取 /root/anstags/app/app.conf 的状态并以 conf_stat register 的 설정 파일의 상태를 읽는다(韩文,意为“读取配置文件的状态”),以及根据其结果断言文件存在的 설정 파일이 있는지 단언한다(韩文,意为“断言配置文件存在”)。把 --list-tasks 的完整输出保存到 /root/anstags/out/inherit.txt,把 --list-tasks --tags verify 的输出保存到 /root/anstags/out/block.txt。
tag 会从所附着的位置向下流动。加在 block 上,block 内所有 task 都拥有它,加在 play 上,该 play 的所有 task 都拥有它。这一点会原样体现在 --list-tasks 每行末尾的 TAGS: [...] 中——block 内的 task 明明什么都没加,却看到两个 tag,就是继承被看见了。读取状态的模块和断言前置条件的模块,如果想不起名称,请用 ansible-doc -l ansible.builtin | grep -iE 'stat|assert' 去找。
始终运行的 task 与绝不运行的 task
再添加两个 task。어떤 선택에서도 남기는 표식(韩文,意为“在任何选择下都会留下的标记”)以 0644 在 /root/anstags/out/always.marker 中写入一行 always,tag 只有 always 一个。함부로 돌면 안 되는 태스크(韩文,意为“不能随便运行的 task”)以 0644 在 /root/anstags/out/never.marker 中写入一行 danger,tag 有 never 和 danger 两个。用 --tags config 真正运行,把输出保存到 /root/anstags/out/always.txt——어떤 선택에서도 남기는 표식(韩文,意为“在任何选择下都会留下的标记”)必须一起运行。并且把 --list-tasks --tags danger 的输出保存到 /root/anstags/out/danger.txt。/root/anstags/out/never.marker 在本实验结束之前不能被创建。
特殊 tag 有五个——always、never、tagged、untagged、all。前两个在实际工作中会用到。always 加在无论怎样选择都不能被漏掉的 task(公共变量设置、收集 fact)上,never 加在需要以代码形式保留、但不能因为手滑而运行的 task 上——注释总有一天会被解开,而 never 不会。要调用带有 never 的 task,必须直接指名与它一起加上的另一个标签。直接运行 --list-tasks 时,那个 task 甚至不会出现在列表里——请先确认这一点。
import 会传递 tag,include 不会
创建 /root/anstags/tasks/common.yml——两个 task。공통 점검 하나(韩文,意为“公共检查一”)输出 common-one,没有 tag。공통 점검 둘(韩文,意为“公共检查二”)输出 common-two,带有 tag deep。然后在 site.yml 末尾再添加两个 task——import 로 공통 점검을 끌어온다(韩文,意为“用 import 引入公共检查”,用 import_tasks 引入该文件,tag 为 imported)和 include 로 공통 점검을 끌어온다(韩文,意为“用 include 引入公共检查”,用 include_tasks 引入同一个文件,tag 为 included)。把 --list-tasks --tags imported 的输出保存到 /root/anstags/out/reuse-import.txt,把 --list-tasks --tags included 的输出保存到 /root/anstags/out/reuse-include.txt。
两者引入的是同一个文件,但引入的时机不同。一个在读取 playbook 时就展开到那个位置(静态),另一个要到运行中才插进来(动态)。这个差别决定了 tag 的继承——预先展开的一方,tag 会附着到每一个 task 上,而运行中插进来的一方,tag 只附着在语句本身。请把两个列表文件并排放在一起,确认引入的文件内的 task 名称看得见的一侧和看不见的一侧。这就是“没有错误、什么也没发生”这种故障的真相。
从中间开始运行,handler 也不会运行
先删除 /root/anstags/out/reload.marker。然后用 --start-at-task "배포 번호를 정한다" -e app_env=prod(韩文,意为“确定部署编号”)真正运行 playbook,把输出保存到 /root/anstags/out/startat.txt。结束之后,把 /root/anstags/app/app.conf 的内容和 /root/anstags/out/reload.marker 是否存在,接着记录到 /root/anstags/out/aftermath.txt——第一行是 conf=<app.conf 의 env 줄>(占位符为 app.conf 中的 env 行),第二行是 marker=<yes 또는 no>(占位符为 yes 或 no)。
--start-at-task 会从该名称的 task 开始。它是长 playbook 在中途失败时,为了避免从第 1 个重新运行而使用的工具。然而它是在前面 task 本应创建的内容、前面 task 本应发出的 notify 都不存在的状态下开始的——所以即使以 failed=0 结束,系统也不是 playbook 所承诺的状态。请亲自确认,在同时给出了 -e app_env=prod 的情况下,配置文件变成了什么样,以及 handler 标记是否生成。这一步的答案不是命令,而是这两个事实。
判定哪些 tag 选择可以完整跑完的工具
创建 /root/anstags/tag-check.sh <태그목록>(占位符为 tag 列表)。用该选择以检查模式运行 site.yml,如果正常结束,就在首行输出 SAFE <태그목록>(占位符为 tag 列表)并以 0 结束;如果没有正常结束,就在首行输出 UNSAFE <태그목록> rc=<종료코드>(占位符依次为 tag 列表与退出码)并以 1 结束。然后依次运行 ./tag-check.sh deploy 和 ./tag-check.sh prep,deploy,把两行依次追加保存到 /root/anstags/out/tagcheck.txt。前者必须是 UNSAFE,后者必须是 SAFE。
배포 번호를 기록한다(韩文,意为“记录部署编号”)使用的是 배포 번호를 정한다(韩文,意为“确定部署编号”)所确定的值。去掉前者而只选后者,那个变量就是未定义的,这就是部分执行之所以危险的第一种方式。这个工具把这种风险从人的记忆变成了命令。判定时使用检查模式的原因是,它必须能够在生产环境中原样运行——为了判定而真的去改变,那就不是工具,而是事故。由于门禁会以 1 结束,所以把结果汇总到文件时,请不要让 shell 在那里停止。