精读笔记(RHCA 英文教材)· DO447 Chapter 4 Transforming Data with Filters and Plugins
精读笔记(RHCA 英文教材)· DO447 Chapter 4 Transforming Data with Filters and Plugins
教材原文:RHCA 官方英文教材(教材第 135~198 页)(OCR 整书版已从本站移除,本页为章节精读) 关联知识:
RHCE9精读笔记\精读-第25章-Shell变量、02-08-Ansible自动化(RH294DO447);本系列配套00-RHCA教材精读-导航与学习法.md章节结构:4.1 用过滤器处理变量 → 4.2 用 lookup 注入外部数据 → 4.3 高级循环 → 4.4 网络地址过滤器 → LabTransforming Data with Filters and Plugins
Chapter Goal / Objectives(原文+译)
- GOAL: Populate, manipulate, and manage data in variables.(填充、加工与管理变量中的数据)
- OBJECTIVES:① 用过滤器(filters)格式化/解析/设定变量值;② 用 lookup 插件把文件、命令、URL、K8s 等外部数据引入模板;③ 组合过滤器与 lookup 对复杂数据结构做高级循环;④ 用 ipaddr/dig 等处理网络地址类数据。
- 贯穿全章的两个原则:① filters 与 lookup 都在 Ansible 控制节点(controller)上执行,不是受管端;② 需要额外 Python 库的过滤器/插件要先在控制节点装包(如 python3-netaddr、python3-dns)。
4.1 Processing Variables Using Filters(用过滤器处理变量)
Jinja2 表达式与过滤器基本用法
- Ansible 用 Jinja2 表达式把变量值替换进 playbook/模板:
{{ variable }};过滤器跟在变量后用竖线|串联,可带括号参数并可连续链式调用:{{ myname | capitalize }}(首字母大写){{ mynumber | string }}(强制转字符串){{ [1, 4, 2, 2] | unique | sort }}→[1, 2, 4]
assert模块测试表达式是否为真,配合 Jinja2 测试is eq(期望值)写断言(教材大量用它演示过滤器结果)。- IMPORTANT:过滤器只处理“值”,不改变变量本身存储的内容;要保留结果就赋值给新变量或直接用在参数里。
变量类型速记(过滤器输入决定一切)
- 字符串:默认类型,可不用引号;
|保留换行、>把换行折成空格(多行字符串 YAML 写法)。 - 数字:整数 42 / 浮点 42.0 / 科学计数 0.42e+2 / 十六进制 0x2A;加引号就成了字符串("20")。
- 布尔:yes/no/y/n/on/off/true/false(不区分大小写,官方建议小写)。
- 日期:ISO-8601 字符串会被解析为日期类型(2019-05-30T21:15:32.42+02:00)。
- Null:
null或~→ 变量未定义。 - 列表/数组:
['a','b']或每行- a;下标从 0 开始:my_list[1]。 - 字典:
{k: v}或冒号多行;取键my_dict['key'];不推荐my_dict.key点号写法(会与 Python 字典属性/方法保留名冲突)。
常用过滤器分组速查
| 分组 | 过滤器与要点 | | 定义检查 | mandatory:未定义即失败中止;default(x):未定义给默认值;default(x, True):空串/False 也给默认;default(omit):未定义就“不传该参数”(user 模块 groups 典型用法);特殊值 omit | | 数学 | + - / // % * **(// 整除、% 取余);先转类型:{{ (ansible_facts['date_time']['hour'] \| int) + 1 }};还有 log/pow/root/abs/round | | 列表分析 | length、first、last、random、max、min、sum | | 列表排序 | sort、reverse、shuffle;去重 unique(集合场景最常用) | | 集合运算 | union(并)、intersect(交)、difference(差:第一集合有第二没有)、symmetric_difference(对称差) | | 列表合并 | flatten(递归展平嵌套列表,可 flatten(levels=1) 只展一层) | | 字典合并/变形 | combine(后者优先级高);dict2items(字典→[{key,value}])、items2dict(逆变换) | | 字符串哈希 | hash('sha1') 等校验和;password_hash('sha512') 生成密码散列(user 模块 password 常用) | | 编码 | b64encode / b64decode(Base64);quote:给 shell 传参前加引号防注入 | | 大小写 | lower、upper、capitalize | | 文本替换 | replace('ar','**') 全量替换;正则 regex_search、regex_replace(如 'arthur up' \| regex_replace('ar(\\S*)r', '\\1mb') → 'thumb up') | | JSON | json_query('[*].name') 取 JSON 路径;to_json/to_yaml(紧凑)与 to_nice_json/to_nice_yaml(格式化输出);from_json/from_yaml 把文本解析成结构 |
- 组合技巧:
lookup('file', ...) | from_yaml读 YAML 文件成结构化变量。
Guided Exercise 1 要点(lab: data-filters)
lab data-filters start → clone data-filters 仓库 → 部署 haproxy 角色后,在两个角色任务里补过滤器处理变量(补全 ellipsis ...)→ lab data-filters finish。典型填空点:default(omit)、default(..., true)、lower、combine 等。
4.2 Templating External Data Using Lookups(lookup 插件注入外部数据)
lookup 与 query
- lookup 插件是 Jinja2 模板语言的扩展,调用语法类似过滤器:
{{ lookup('插件名', 参数) }}。 - 同一插件可用
query(...)调用:lookup 返回逗号分隔字符串,query 永远返回列表(更易解析/循环)。 - 多参数:
lookup('file', '/etc/hosts', '/etc/issue')多个文件内容用逗号拼接;query('file', ...)则列表元素各含一个文件内容。
常用插件一览
| 插件 | 用途 | 示例 |
|---|---|---|
file | 读控制节点本地文件(相对路径默认在 playbook 的 files/) | lookup('file', '~/.ssh/id_rsa.pub') |
template | 读模板文件并先渲染 Jinja2(相对路径默认在 templates/) | lookup('template', 'my.template.j2');注意与 template 模块区分 |
env | 读控制节点环境变量(容器注入密钥场景) | lookup('env','MY_PASSWORD') |
pipe | 在控制节点执行命令返回原始输出 | query('pipe', 'ls files') |
lines | 在控制节点执行命令并按行拆成列表 | query('lines', 'cat files/my.file') |
url | 抓取 URL 内容 | lookup('url', 'https://my.site.com/my.file') |
k8s | 通过 openshift Python 库访问 K8s/OpenShift API(只读信息;改名 openshift 同义);kind/namespace/resource_name/label_selector 过滤 | lookup('k8s', kind='Deployment', namespace='ns', resource_name='my_res') |
fileglob | 按通配符在控制节点匹配文件(返回逗号串,配 query 得列表) | query('fileglob', '~/.bash*') |
dig / dnstxt | 查 DNS(见 4.4) | lookup('dig', 'example.com/MX') |
- 完整清单与文档:
ansible-doc -t lookup -l、ansible-doc -t lookup 插件名。 - 自定义插件:把 Python 脚本放进 playbook 旁的
lookup_plugins/目录即可被发现(插件开发超出本课程范围)。
错误处理(errors 参数)
- lookup 默认
errors='strict':底层失败 → playbook 致命错误中止。 errors='warn':只告警并返回空串/空列表,可再配default(...)兜底,如{{ lookup('file', 'my.file', errors='warn') | default("Default file content") }}。errors='ignore':静默忽略并返回空值。
Guided Exercise 2 要点(lab: data-lookups)
lab data-lookups start(脚本装 Python 依赖并准备文件)→ 两部分:① 用 file lookup 读纯文本用户列表 → 为受管主机批量建用户;② 用其它 lookup(url/template 等)把外部数据拼进 playbook → lab data-lookups finish。
4.3 Implementing Advanced Loops(高级循环)
loop 与 with_* 家族
loop关键字(Ansible 2.5+)遍历扁平列表;2.5 之前用with_*(等价于 with_list=某 lookup 插件)。简单列表统一用loop。- 迁移收益:不用背各种 with_* 关键字,只需掌握插件+过滤器;
ansible-doc -t lookup可查。with_* 官方未废弃,但新写代码优先 loop。 - 易错:loop 只收列表、不收字符串 → 分清 lookup(串)与 query(列表);
loop: "{{ lookup('list', mylist) }}"可显式转列表。 - 官方迁移指引:能只用过滤器转换的 with_* 都可以安全改 loop;若必须依赖 lookup 才能转换,保留 with_* 反而更清晰。
常见复杂迭代场景 → loop 等价写法
| 旧语法 | 语义 | loop 等价 | | with_items: [listA, listB] | 隐式做一层展平后迭代 | loop: "{{ 三层嵌套列表 \| flatten(levels=1) }}"(loop 不隐式展平!) | | with_dict: "{{ users }}" | 迭代字典,item 含 .key/.value | loop: "{{ users \| dict2items }}"(结果同构) | | with_subelements / 嵌套 | 字典列表 × 其内部某 key 的列表组合 | loop: "{{ users \| subelements('authorized') }}" → 每项是 [user_dict, 单个子项],任务里用 item.0/item.1 | | with_fileglob: ["~/.bash*"] | 遍历匹配的文件 | loop: "{{ query('fileglob', '~/.bash*') }}" |
- subelements 例:变量
users的每个用户含authorized:公钥列表 →loop: "{{ users | subelements('authorized') }}"+user: "{{ item.0.name }}"、key: "{{ lookup('file', item.1) }}"。 - dict2items 例:
users是字典(键=登录名,值含 name/mail)→user: name: "{{ item.key }}" comment: "{{ item.value.name }}"。
Guided Exercise 3 要点(lab: data-loops)
lab data-loops start → ~/DO447/labs/data-loops,3 个 scenario 把复杂数据结构的迭代重构为 loop:Scenario 1 用 dict2items 替换 with_dict 遍历 IdM 用户字典(group_vars/all/users.yml);其余 scenario 依次练习 subelements 等嵌套结构与文件通配迭代 → lab data-loops finish。
4.4 Working with Network Addresses Using Filters(网络地址处理)
收集网络 facts
- setup 默认收集大量网络 facts;只要网络部分:显式 task 用
gather_subset: ['!all', 'network']减少开销。 - 常用网络 facts:
ansible_facts['interfaces'](接口名列表)、每个接口一个字典(如ansible_facts['enp11s0'],含 MAC/IPv4/IPv6/内核模块)、['dns']['nameservers']、['domain']、['all_ipv4_addresses']、['all_ipv6_addresses']、['fqdn']、['hostname'](短主机名)。
ipaddr 过滤器(需控制节点 python3-netaddr)
- 依赖:控制节点安装
python3-netaddr包,否则报错。 - 基础:无参数
{{ value | ipaddr }}→ 合法 IP 原样返回、非法返回 False;列表输入则只留合法项。 - 参数用法表: | 参数 | 作用/返回 | | --- | --- | |
address| 校验并剥离前缀 | |network| 校验网段并返回 CIDR 形式 | |prefix| 返回掩码位数(/23 的 23) | |host/prefix、network/prefix| 分别校验“主机+前缀”或“网络地址+前缀”格式 | |public/private| 校验是否 IANA 公网 / 私网地址 | |size| 网段含 IP 总数 | | 整数 n | 返回网段内第 n 个地址(负数从末尾倒数) | |netmask/network/broadcast| 由 host/prefix 换算子网掩码 / 网络地址 / 广播地址 | |subnet| 返回主机所在子网 | |ipv4/ipv6| 校验并转 IPv4 / IPv6 格式 | - 例子:
{{ '10.0.0.1/23' | ipaddr('netmask') }}→ 255.255.254.0;{{ lookup('dig', ...) | ipaddr('private') }}判断主机地址是否私网。
dig / dnstxt lookup(需控制节点 python3-dns)
- 依赖:
python3-dns包。 lookup('dig', 'example.com'):查默认 DNS 的 A 记录;查类型用qtype=MX或后缀/MX(lookup('dig', 'example.com/MX'));指定服务器:lookup('dig', 'example.com', '@4.4.8.8,4.4.4.4')。- 多值记录:dig 默认返回逗号拼接串;要列表用
query('dig', 'example.com/MX')。 - TXT 专用:
lookup('dnstxt', ['test.example.com'])更简洁。 - 生态插件示例:
aws_service_ip_ranges(AWS EC2 IP 段)、nios_next_ip(Infoblox 取下一可用 IP)。
Guided Exercise 4 要点(lab: data-netfilters)
lab data-netfilters start(装 python3-netaddr/python3-dns 等依赖)→ 补全 roles/netfilters/tasks/main.yml 里的事实定义(ellipsis 填空),多数基于 ansible_facts.default_ipv4(含 address/netmask/network/broadcast 等键),用 ipaddr 与 dig 补出期望值 → lab data-netfilters finish。
Lab 要点(Transforming Data with Filters and Plugins / data-review)
- 目标:用过滤器+lookup 重构 playbook 项目(前端 haproxy 负载均衡 + 后端 web 池),让 firewall 角色更灵活。
- 要求:
firewall_rules列表逐条渲染时——① state 未定义默认 enabled;② zone 未定义则省略 zone 参数(default(omit));③ 端口规则的 protocol 未定义默认 tcp,且总是转小写(lower)。 - 流程:
lab data-review start→ clone data-review.git → 只改 firewall 角色 tasks(不改 test_firewall_role.yml)→ 跑测试剧本无错 →lab data-review grade→lab data-review finish。
命令速查表
| 场景 | 写法 | | 链式过滤 | {{ value \| filter1 \| filter2 }} | | 未定义兜底 | {{ x \| default('默认值') }}、{{ x \| default('', true) }}、{{ x \| default(omit) }} | | 必填校验 | {{ x \| mandatory }} | | 字典变形 | {{ users \| dict2items }}、{{ items \| items2dict }}、{{ a \| combine(b) }} | | 列表处理 | unique \| sort、flatten(levels=1)、union/intersect/difference | | 文本处理 | replace、regex_replace、quote、b64encode/b64decode、password_hash('sha512') | | 结构序列化 | to_nice_yaml、to_json、from_yaml | | 读外部文件/命令 | lookup('file','path')、query('lines','cmd')、lookup('env','VAR') | | 网络过滤 | {{ ip \| ipaddr('private') }}、{{ net \| ipaddr('netmask') }}、{{ cidr \| ipaddr('size') }} | | DNS 查询 | lookup('dig','example.com/MX')、query('dig','host','@8.8.8.8')、lookup('dnstxt',[...]) | | 依赖包(控制节点) | dnf install python3-netaddr python3-dns | | 实验 | lab data-filters / data-lookups / data-loops / data-netfilters / data-review start\|grade\|finish |
核心词汇表
| 英文 | 中文速记 |
|---|---|
| filter | 过滤器(Jinja2 表达式里用 | 串联) |
| Jinja2 expression / template | 表达式 / 模板 |
| lookup vs query | 返回字符串 vs 永远返回列表 |
| lookup plugin | lookup 插件(file/template/env/pipe/lines/url/k8s/fileglob/dig…) |
| mandatory / default / omit | 必填 / 兜底 / 省略参数特殊值 |
| assert module | 断言模块(配合 is eq) |
| combine | 字典合并(后者覆盖) |
| dict2items / items2dict | 字典↔[{key,value}] 列表互转 |
| flatten(levels=1) | 展平(一层,等价 with_items 隐式行为) |
| subelements | 嵌套结构展开(item.0/item.1) |
| unique / union / intersect / difference | 去重 / 并 / 交 / 差 |
| password_hash / hash | 密码散列 / 校验和 |
| to_json / to_yaml / from_yaml | JSON/YAML 序列化与解析 |
| ipaddr | 网络地址过滤器(需 python3-netaddr) |
| CIDR / netmask / broadcast | 前缀 / 掩码 / 广播地址 |
| dig / dnstxt lookup | DNS 查询插件(需 python3-dns) |
| gather_subset | setup 按子集收集 facts |
| loop vs with_* | 新循环关键字 vs 旧迭代语法 |
| control node | 控制节点(filter/lookup 执行地) |
本章自测
- 过滤器会改变原变量吗?链式写法怎么拼?assert 模块配合哪个 Jinja2 测试?
- 数字加引号后是什么类型?YAML 多行字符串 | 与 > 区别?
- default / default(x, true) / default(omit) 三者行为差异?mandatory 什么时候用?
- 把嵌套列表/字典/“字典列表×子列表”转成可循环数据,分别用哪三个过滤器?
- lookup 与 query 返回差异?哪些场景必须用 query?
- file/template/env/pipe/lines/url/k8s 六个 lookup 各读什么?在哪个节点执行?
- lookup errors 三档取值与行为?配合什么过滤器做兜底?
- ipaddr 至少说出 6 种参数;控制节点要装什么包?
- dig 如何指定记录类型与 DNS 服务器?多值记录怎么拿列表?
- 章末 SUMMARY 五条中,哪一条强调“控制节点”与“额外安装包”?
