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

Ansible 基础

只跑了一部分,为什么就不是最终状态了

在 TT Lab 中继续学习

目标

学会用 tag 只运行 playbook 的一部分,并亲自测量这种便利与什么样的风险相伴。最后创建一个判定哪些 tag 选择可以完整跑完的工具。

为什么重要

playbook 会不断生长。总会有那么一天,task 有八十个,却只想修改配置中的一行,tag 就是放在这个位置上的工具。语法很简单,所以人们学会的第二天就会在生产环境中使用。事故就出在这里——tag 是截断执行的刀,而 playbook 并不是被设计成可以被截断的。前面 task 创建的目录被后面的 task 使用,修改了配置的 task 会叫 handler。只选一部分,这种连接就会断开,运气不好的话,甚至不会失败,而是留下一个只对了一半的状态。所以本实验由两半构成:一半是学习 tag 语法,另一半是亲眼看到截断的执行无法保证什么。

步骤

  1. 创建 /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。
  2. 把 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。第二个文件中必须有 설정을 쓴다(韩文,意为“写入配置”),而不能有 배포 번호를 기록한다(韩文,意为“记录部署编号”)。
  3. 用 --skip-tags prep,deploy 真正运行 playbook,把输出保存到 /root/anstags/out/skip.txt。输出中必须有 배포 자리를 만든다(韩文,意为“创建部署位置”)和 설정을 쓴다(韩文,意为“写入配置”)的 TASK [...] 行,而不能有 배포 번호를 정한다(韩文,意为“确定部署编号”)和 배포 번호를 기록한다(韩文,意为“记录部署编号”)的行。并且把 --list-tasks --skip-tags prep,deploy 的输出保存到 /root/anstags/out/list-skip.txt。
  4. 给 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。
  5. 再添加两个 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 在本实验结束之前不能被创建。
  6. 创建 /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。
  7. 先删除 /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)。
  8. 创建 /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。

参考

给 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 在那里停止。