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

Ansible 基础

权限 640 变成 1204 的那一天 — 把文件当作状态来管

在 TT Lab 中继续学习

目标

把文件和目录当作状态而不是命令来声明。亲自测量没有引号的八进制数留下的权限,用备份和验证加上回退的依据和对损坏配置的拦截,并创建带标记的区块、链接、状态报告和路径检查器。

为什么重要

如果把 mkdir、cp、chmod、sed -i 这四行 shell 原样搬过来,看起来像自动化,但有三件事会同时崩塌。第二次运行与第一次不同,覆盖了什么哪里也没有留下,损坏的配置原样上线。Ansible 的文件模块分别用 state、backup 和 validate 回答这三个问题。再加上安全管理多行区块的 blockinfile 标记、部署所用的两种链接,以及什么都不改变、只读取事实的 stat,配置部署的骨架就完整了。本实验会一次一块地亲手搭建这个骨架,最后亲自创建一个判定“这条路径现在是什么状态”的工具。

步骤

  1. 在 /root/ans/files/hosts.ini 中写入 web 组的主机 web1(ansible_host=127.0.0.1、ansible_port=2222、ansible_user=root),并用 /root/ans/files/p01.yml 创建四个目录——/root/ans/files/tree/conf、/root/ans/files/tree/logs 和 /root/ans/files/out 为 0755,/root/ans/files/tree/secrets 为 0700。
  2. 用 /root/ans/files/p02.yml 创建三个文件——/root/ans/files/tree/secrets/api.key 为 "0600",/root/ans/files/tree/conf/motd.txt 为 "0644",而 /root/ans/files/scratch/decimal.txt 要以不带引号的 mode: 644 来写。然后把 stat -c %a 对 decimal.txt 给出的值,写成一行到 /root/ans/files/out/mode-trap.txt。
  3. 用 /root/ans/files/p03.yml 放置 /root/ans/files/tree/conf/app.conf。内容是 listen_port={{ app_port }} 和 env=prod 两行,app_port 的默认值为 8080,权限为 "0644",并开启 backup。让同一个 playbook 也创建 /root/ans/files/tree/conf/deprecated.conf 和 /root/ans/files/tree/logs/archive/old.log。先用默认值运行一次,再用 -e app_port=9090 重新运行,使其生成备份,并让该备份路径保留在 /root/ans/files/out/backup-path.txt 中。
  4. 用 /root/ans/files/p04.yml 放置 /root/ans/files/tree/conf/app.json。内容来自 app_json 变量,默认值是 service 为 web、port 为 8080 的 JSON,并给 copy 加上 validate 来检查 JSON 语法。然后用 -e "app_json=not json at all" 把同一个 playbook 再运行一次,把失败的输出保留到 /root/ans/files/out/validate-fail.txt。
  5. 用 /root/ans/files/p05.yml 处理 /root/ans/files/tree/conf/hosts.block。只在文件不存在时,才用一行 127.0.0.1 localhost 创建它(已存在则不覆盖),并用 blockinfile,在使用标记 # {mark} ANSIBLE MANAGED BLOCK: web pool 的区块中放入 10.10.0.11 web1 和 10.10.0.12 web2 两行。即使把 playbook 运行两次,区块也必须只有一份。
  6. 用 /root/ans/files/p06.yml 做四件事——只在不存在时创建 /root/ans/files/tree/logs/app.log,让 /root/ans/files/tree/logs/latest.log 成为指向该文件的符号链接,让 /root/ans/files/tree/conf/motd.hard 成为 /root/ans/files/tree/conf/motd.txt 的硬链接,最后给 latest.log 路径设置 "0640",并让它跟随链接,使 app.log 的权限发生改变。
  7. 用 /root/ans/files/p07.yml 通过 stat 确认 /root/ans/files/tree/conf/app.conf 的状态,校验和用 sha256 获取,并在 /root/ans/files/out/stat-report.json 中留下包含 path、exists、mode、size、checksum 五个键的 JSON。接着用 fetch 取回 /root/ans/files/tree/conf/app.json,使其落成 /root/ans/files/fetched/app.json 这一个文件。
  8. 用 /root/ans/files/p08.yml 让 /root/ans/files/tree/conf/deprecated.conf 和 /root/ans/files/tree/logs/archive 处于不存在的状态。然后编写 /root/ans/files/audit.sh:以路径列表文件为参数,对每条路径按 <경로> LINK <가리키는 곳> · <경로> DIR <권한> · <경로> FILE <권한> · <경로> ABSENT(占位符依次为路径、指向位置与权限)中的一种,各输出一行。在 /root/ans/files/paths.txt 中写入上面的两条路径,以及 /root/ans/files/tree/conf/app.conf、/root/ans/files/tree/logs/latest.log、/root/ans/files/tree/secrets,并把结果保存到 /root/ans/files/out/state-report.txt。

参考

把目录树声明为状态

在 /root/ans/files/hosts.ini 中写入 web 组的主机 web1(ansible_host=127.0.0.1、ansible_port=2222、ansible_user=root),并用 /root/ans/files/p01.yml 创建四个目录——/root/ans/files/tree/conf、/root/ans/files/tree/logs 和 /root/ans/files/out 为 0755,/root/ans/files/tree/secrets 为 0700。

在 ansible.builtin.file 中,表示目录的 state 值只要一个,就会连中间路径一起创建。mode 请务必写成带引号的字符串——下一步会亲自测量为什么要这样。评分器会把 playbook 再以检查模式运行一次,确认变更是否为 0。

亲自测量没有引号的八进制数留下的权限

用 /root/ans/files/p02.yml 创建三个文件——/root/ans/files/tree/secrets/api.key 为 "0600",/root/ans/files/tree/conf/motd.txt 为 "0644",而 /root/ans/files/scratch/decimal.txt 要以不带引号的 mode: 644 来写。然后把 stat -c %a 对 decimal.txt 给出的值,写成一行到 /root/ans/files/out/mode-trap.txt。

YAML 会把前面没有 0 的数字读作十进制。这个整数被原样当作权限位时会变成什么值,不要在脑子里计算,而要实际创建出来,用 stat 测一下。scratch 目录也要在同一个 playbook 中一起创建。

保留覆盖之前的内容

用 /root/ans/files/p03.yml 放置 /root/ans/files/tree/conf/app.conf。内容是 listen_port={{ app_port }} 和 env=prod 两行,app_port 的默认值为 8080,权限为 "0644",并开启 backup。让同一个 playbook 也创建 /root/ans/files/tree/conf/deprecated.conf 和 /root/ans/files/tree/logs/archive/old.log。先用默认值运行一次,再用 -e app_port=9090 重新运行,使其生成备份,并让该备份路径保留在 /root/ans/files/out/backup-path.txt 中。

copy 的备份功能会把覆盖之前的内容留在同一个目录中。返回值中包含该路径,所以用 register 接收下来,就可以在下一个 task 中直接使用。第一次运行时没有可覆盖的内容,不会生成备份,所以请给记录路径的 task 加上 is defined 条件。

只把通过检查的配置放到位

用 /root/ans/files/p04.yml 放置 /root/ans/files/tree/conf/app.json。内容来自 app_json 变量,默认值是 service 为 web、port 为 8080 的 JSON,并给 copy 加上 validate 来检查 JSON 语法。然后用 -e "app_json=not json at all" 把同一个 playbook 再运行一次,把失败的输出保留到 /root/ans/files/out/validate-fail.txt。

validate 字符串中的 %s 位置会填入临时文件路径。检查命令只要读取 JSON,有问题时以非 0 的值结束就行。保存失败运行的输出时,必须同时接收标准错误,并且不要让脚本因为失败的命令而停止。请确认失败之后原来的文件是否完好。

只替换带标记的区块

用 /root/ans/files/p05.yml 处理 /root/ans/files/tree/conf/hosts.block。只在文件不存在时,才用一行 127.0.0.1 localhost 创建它(已存在则不覆盖),并用 blockinfile,在使用标记 # {mark} ANSIBLE MANAGED BLOCK: web pool 的区块中放入 10.10.0.11 web1 和 10.10.0.12 web2 两行。即使把 playbook 运行两次,区块也必须只有一份。

copy 中有一个开关可以避免覆盖已经存在的文件。如果去掉它,第二次运行时区块会整个消失,然后再重新附上。标记字符串中的 {mark} 位置分别填入 BEGIN 和 END——如果以后修改这段文字,区块就会变成两份。评分器会把 playbook 再以检查模式运行一次,确认变更是否为 0。

符号链接、硬链接,以及 follow

用 /root/ans/files/p06.yml 做四件事——只在不存在时创建 /root/ans/files/tree/logs/app.log,让 /root/ans/files/tree/logs/latest.log 成为指向该文件的符号链接,让 /root/ans/files/tree/conf/motd.hard 成为 /root/ans/files/tree/conf/motd.txt 的硬链接,最后给 latest.log 路径设置 "0640",并让它跟随链接,使 app.log 的权限发生改变。

符号链接和硬链接的 state 值不同。两者都接收 src 和 dest。最后一个 task 使用只调整已存在文件属性的 state,并明确开启决定是否跟随链接的参数。在 Linux 上,符号链接本身的权限没有意义,所以权限是加在链接末端的文件上,而不是链接上。

读取状态并留下报告,再取回控制节点

用 /root/ans/files/p07.yml 通过 stat 确认 /root/ans/files/tree/conf/app.conf 的状态,校验和用 sha256 获取,并在 /root/ans/files/out/stat-report.json 中留下包含 path、exists、mode、size、checksum 五个键的 JSON。接着用 fetch 取回 /root/ans/files/tree/conf/app.json,使其落成 /root/ans/files/fetched/app.json 这一个文件。

stat 什么都不改变,只返回事实。校验和必须指定算法,才会放入返回值。fetch 默认会创建主机名目录并原样重现原路径,所以要让它落成一个文件,必须开启关闭这一行为的参数。报告只要创建一个字典并使用转换成 JSON 的过滤器,一个 task 就够了。

清理也是状态——并创建路径检查器

用 /root/ans/files/p08.yml 让 /root/ans/files/tree/conf/deprecated.conf 和 /root/ans/files/tree/logs/archive 处于不存在的状态。然后编写 /root/ans/files/audit.sh:以路径列表文件为参数,对每条路径按 <경로> LINK <가리키는 곳> · <경로> DIR <권한> · <경로> FILE <권한> · <경로> ABSENT(占位符依次为路径、指向位置与权限)中的一种,各输出一行。在 /root/ans/files/paths.txt 中写入上面的两条路径,以及 /root/ans/files/tree/conf/app.conf、/root/ans/files/tree/logs/latest.log、/root/ans/files/tree/secrets,并把结果保存到 /root/ans/files/out/state-report.txt。

权限按 stat -c %a 给出的原样写。判定顺序就是这一步的全部——-d 和 -f 会跟随符号链接,所以如果后看链接,链接就会伪装成目录或文件。评分器也会用它自己另外创建的路径列表来运行这个脚本,所以把答案写死在里面的脚本无法通过。