变量与流程控制
RHEL 9 Ansible:facts 与自定义变量、loop 批量循环、when 条件判断、block/rescue/always 错误处理。
本页目录
会写「固定剧本」之后,考试会要求:不同机器不同配置、一次建多个用户、满足条件才执行。这就是变量、循环和条件。本篇按「变量从哪来 → 怎么循环 → 怎么判断 → 出错怎么办」讲。
系列:从 RHCSA 到 RHCE · 第 16 / 17 篇 上一篇:Playbook 与模块 下一篇:Role 与 Vault 总目录:系列索引
环境说明
- 系统:RHEL 9
- 前置:Ansible 快速上手、Playbook 与模块
- 工作目录:
/home/alice/ansible - 约定:inventory 里已有
node1…,组名test01/web等与前文一致
1. 变量从哪来
剧本里用 两个花括号 引用变量:
{{ 变量名 }}
来源常见四类:
| 来源 | 谁定义 | 典型用途 |
|---|---|---|
| facts | Ansible 自动采集节点信息 | 主机名、内存、磁盘 |
| inventory 变量 | 写在主机清单里 | 每台机器不同的 IP、序号 |
| play 内 vars | 写在剧本 vars: | 整场戏共用的默认值 |
| vars_files | 单独的 YAML 文件 | 变量多时拆文件 |
2. facts:自动采到的变量
执行剧本时默认有一步 Gathering Facts。也可单独看:
ansible node1 -m setup | less
/关键词 搜索,q 退出。
常用 facts
| 变量名 | 含义(示例) |
|---|---|
| ansible_hostname | 主机名 node1 |
| ansible_fqdn | 完全合格域名 |
| ansible_memtotal_mb | 总内存 MB |
| ansible_memfree_mb | 剩余内存 MB |
| ansible_architecture | 如 x86_64 |
| ansible_devices.vdb | 第二块盘信息(可能未定义) |
| ansible_enp1s0.ipv4.address | 某网卡 IPv4(网卡名因机器而异) |
在剧本里用 facts
vim facts.yml
---
- hosts: all
tasks:
- copy:
content: "{{ ansible_fqdn }} {{ ansible_memfree_mb }}"
dest: /tmp/facts.txt
- user:
name: "{{ ansible_hostname }}"
state: present
- file:
path: /tmp/{{ ansible_hostname }}.txt
state: touch
ansible-playbook facts.yml
效果:每台机器用自己的主机名建用户、建文件——这就是变量的意义。
写法:YAML 里整段含变量时,用一对引号把整段包起来即可,例如
"{{ ansible_fqdn }} {{ ansible_memfree_mb }}"。 不要写成{{ "ansible_fqdn" }}(变量名被当成普通字符串)。
另外:password_hash 若不固定 salt,每次生成的哈希可能不同,任务会反复 changed。考试一般可接受;若要严格幂等,可加固定 salt(见 ansible-doc user)。
3. inventory 里写变量
编辑清单:
vim inventory
[test01]
node1 x=1 y=2
[test02]
node2 x=11 y=22
[web]
node3 x=111 y=222
node4 x=1111 y=2222
[test05]
node5
[webtest:children]
web
[web:vars]
con=helloweb
[test01:vars]
con=hellotest01
| 写法 | 含义 |
|---|---|
| node1 x=1 y=2 | 主机级变量 |
| [组:vars] | 组级变量 |
| 组变量会作用到组内主机 |
剧本读取:
vim invent.yml
---
- hosts: node1,node2,node3,node4
tasks:
- copy:
content: "{{ x }} {{ y }}"
dest: /tmp/inventory.txt
- hosts: web,test01
tasks:
- copy:
content: "{{ con }}"
dest: /tmp/groupvars.txt
ansible-playbook invent.yml
# 到 node1 / node2 看 /tmp/inventory.txt 内容是否不同
考点:「每台机器不同配置」→ inventory 主机变量;「整组统一」→
[组:vars]。
4. play 内定义 vars
vim vars.yml
---
- hosts: all
vars:
iname: "cloud"
ipass: "123456"
tasks:
- user:
name: "{{ iname }}"
password: "{{ ipass | password_hash('sha512') }}"
也可以用字典(一组相关变量)。注意:字典名不要和模块名 user 撞车,下面用 account:
---
- hosts: all
vars:
account:
iname: "cloud2"
ipass: "123456"
tasks:
- user:
name: "{{ account.iname }}"
password: "{{ account.ipass | password_hash('sha512') }}"
5. vars_files:变量放单独文件
vim users.yml
users:
iname: "cloud3"
ipass: "123456"
vim filevar.yml
---
- hosts: all
vars_files:
- users.yml
tasks:
- user:
name: "{{ users.iname }}"
password: "{{ users.ipass | password_hash('sha512') }}"
易错:
vars_files的路径相对于剧本所在目录(或绝对路径);文件名写错会直接失败。
6. 魔法变量(内置,不用自己定义)
| 变量 | 含义 |
|---|---|
| inventory_hostname | 当前主机在清单里的名字 |
| hostvars['主机名'] | 取其它主机的变量(含 facts) |
| groups['all'] | 所有主机列表 |
| groups['web'] | 某一组的主机列表 |
vim magic.yml
---
- hosts: all
tasks:
- debug:
msg: "{{ ansible_fqdn }}"
- debug:
msg: "inventory hostname is: {{ inventory_hostname }}"
- debug:
msg: "All group contains: {{ groups['all'] }}"
- debug:
msg: "Web group contains: {{ groups['web'] }}"
- debug:
msg: "{{ hostvars['node1'].ansible_fqdn }}"
ansible-playbook magic.yml
debug 模块专门用来在屏幕上打印,排错极常用。
考点:
hostvars['其它机器'].变量做跨主机取值;inventory_hostname比 facts 主机名更「清单视角」。
7. loop:一次做很多遍
user / file 一次通常处理一个对象。要建 3 个用户,用 loop。
列表循环
vim loopuser.yml
---
- hosts: node1
tasks:
- user:
name: "{{ item }}"
password: "{{ '123456' | password_hash('sha512') }}"
loop:
- loopuser1
- loopuser2
- loopuser3
执行时每轮 item 取列表里下一个值:
changed: [node1] => (item=loopuser1)
changed: [node1] => (item=loopuser2)
changed: [node1] => (item=loopuser3)
字典列表(用户名密码各不相同)
---
- hosts: node1
tasks:
- user:
name: "{{ item.iname }}"
password: "{{ item.ipass | password_hash('sha512') }}"
loop:
- { 'iname': 'myuser1', 'ipass': '123456' }
- { 'iname': 'myuser2', 'ipass': '654321' }
- { 'iname': 'testus3', 'ipass': 'testpw' }
| 要点 | 说明 |
|---|---|
| loop | 关键字,不可改 |
| item | 每一轮的当前元素,关键字 |
| loop 与模块 | 必须同一缩进层级(对齐) |
易错:
loop缩进到模块里面去;应与user:/file:对齐。 易错:还想用item当字符串,实际是字典,应写item.iname。
8. when:满足条件才执行
- 模块:
...
when: 条件
条件里不要再写 {{ }}(和模块参数不同)。
按主机名
用 inventory 里的名字(清单视角,更推荐):
vim when1.yml
---
- hosts: all
tasks:
- user:
name: "node1user"
when: inventory_hostname == "node1"
ansible_hostname 是 facts 采集到的系统主机名,多数情况与清单名一致,但不一致时以 inventory_hostname 为准更稳妥。
按数值
- yum:
name: unzip
state: present
when: ansible_memfree_mb > 900
| 运算 | 例子 |
|---|---|
| 相等 | == |
| 不等 | != |
| 大于/小于 | > >= < <= |
变量是否存在
- file:
path: /tmp/vdb.txt
state: touch
when: ansible_devices.vdb is defined
- file:
path: /tmp/novdb.txt
state: touch
when: ansible_devices.vdb is not defined
字符串包含
- file:
path: /tmp/x86.txt
state: touch
when: "'x86' in ansible_architecture"
not in 表示不包含。
考点:
when与任务对齐;条件里用裸变量名。 易错:写成when: "{{ ansible_hostname }} == node1"(多了花括号、少引号)。
9. block / rescue / always:出错时怎么办
语法:
tasks:
- block:
- 模块1
- 模块2
rescue:
- 失败后执行
always:
- 无论成败都执行
理解成:
A 计划(block)
├─ 成功 → 跳过 rescue,跑 always
└─ 失败 → 跑 rescue(B 计划),再跑 always
实验:优先 2G 分区,失败则 500M
背景:node2 的 vdc 剩余空间较多,node5 很少——同一条剧本,结果不同。
vim block.yml
---
- hosts: node2,node5
tasks:
- block:
- parted:
device: /dev/vdc
number: 1
part_end: 2GiB
state: present
rescue:
- parted:
device: /dev/vdc
number: 1
part_end: 500MiB
state: present
always:
- filesystem:
dev: /dev/vdc1
fstype: xfs
- mount:
path: /xixi
src: /dev/vdc1
fstype: xfs
state: mounted
ansible-playbook block.yml
读法:先尝试 2GiB;不行就 500MiB;最后总是格式化并挂载。
考点:
block/rescue/always三层结构;缩进对齐。 易错:把rescue写成else(这是 YAML/Ansible 专有关键字,不是编程语言 if/else)。
10. 推荐练习(约 45 分钟)
cd /home/alice/ansible
# 1) facts
vim facts.yml
# 见第 2 节
ansible-playbook facts.yml
ansible node1 -m command -a "cat /tmp/facts.txt"
ansible node2 -m command -a "cat /tmp/facts.txt"
# 2) inventory 变量
vim inventory
# 给 node1 写 x=1 y=2,node2 写 x=11 y=22
vim invent.yml
ansible-playbook invent.yml
# 3) loop
vim loopuser.yml
ansible-playbook loopuser.yml
ansible node1 -m command -a "id myuser1"
# 4) when
vim when1.yml
ansible-playbook when1.yml
ansible node1 -m command -a "id node1user"
ansible node2 -m command -a "id node1user"
# node2 上应不存在 node1user
# 5) debug 看魔法变量
vim magic.yml
ansible-playbook magic.yml
期望
/tmp/facts.txt每台内容不同(各自 FQDN)- loop 三个用户都建好
node1user只出现在inventory_hostname == node1的机器上
11. 速查表
| 目标 | 写法 |
|---|---|
| 引用变量 | {{ 变量名 }} |
| 看 facts | ansible 主机 -m setup |
| inventory 主机变量 | node1 x=1 |
| 组变量 | [web:vars] / con=value |
| play 变量 | vars: |
| 变量文件 | vars_files: - xx.yml |
| 循环 | loop: + {{ item }} |
| 字典循环 | item.iname |
| 条件 | when: 变量 == 值(不要 {{}}) |
| 存在性 | is defined / is not defined |
| 包含 | 'x86' in ansible_architecture |
| 错误分支 | block / rescue / always |
| 打印 | - debug: msg: "..." |
考点与易错
考点:按主机名/内存/磁盘是否存在写
when;loop批量建用户;inventory 变量区分节点。 易错:when里写了{{ }}。 易错:loop与模块没对齐。 易错:facts 变量名写错(如ansible_hostname写成hostname)。 易错:block/rescue/always缩进错,YAML 解析失败。 练习环境:磁盘名vdb/vdc以题面lsblk为准;内存阈值按机器调整。
小结
- 变量四来源:facts、inventory、vars、vars_files;引用一律
{{ }}。 - 批量用 loop +
item;字典用item.键名。 - 条件用 when,条件里不要花括号;
is defined判断有没有这个变量。 - 失败要兜底用 block / rescue / always。
下一篇收尾:Role 角色化复用,以及 Vault 加密敏感文件。
系列导航:总目录 · 上一篇:Playbook 与模块 · 下一篇:Role 与 Vault