Linux

变量与流程控制

RHEL 9 Ansible:facts 与自定义变量、loop 批量循环、when 条件判断、block/rescue/always 错误处理。

会写「固定剧本」之后,考试会要求:不同机器不同配置、一次建多个用户、满足条件才执行。这就是变量、循环和条件。本篇按「变量从哪来 → 怎么循环 → 怎么判断 → 出错怎么办」讲。

系列:从 RHCSA 到 RHCE · 第 16 / 17 篇 上一篇:Playbook 与模块 下一篇:Role 与 Vault 总目录:系列索引

环境说明

1. 变量从哪来

剧本里用 两个花括号 引用变量:

{{ 变量名 }}

来源常见四类:

来源谁定义典型用途
factsAnsible 自动采集节点信息主机名、内存、磁盘
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. 速查表

目标写法
引用变量{{ 变量名 }}
看 factsansible 主机 -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 为准;内存阈值按机器调整。

小结

  1. 变量四来源:facts、inventory、vars、vars_files;引用一律 {{ }}。
  2. 批量用 loop + item;字典用 item.键名。
  3. 条件用 when,条件里不要花括号;is defined 判断有没有这个变量。
  4. 失败要兜底用 block / rescue / always。

下一篇收尾:Role 角色化复用,以及 Vault 加密敏感文件。


系列导航:总目录 · 上一篇:Playbook 与模块 · 下一篇:Role 与 Vault

相关阅读

全部文章 →
← 返回列表更多「Linux」