Ansible 循环、过滤器与判断逻辑
一、Ansible 循环
循环用于批量执行重复任务,减少代码冗余,Ansible 提供多种迭代方式适配不同场景,核心类型及用法如下:
循环类型 | 功能说明 | 关键特点 |
---|---|---|
with_items / loop | 列表循环 | loop 可完全替代 with_items,适用于简单列表(如用户、软件包) |
with_dict | 迭代字典 | 需同时使用键(key)和值(value),如 “路径 + 权限” 组合场景 |
with_fileglob | 迭代本地文件 | 支持通配符(如 *.conf),用于批量处理本地配置文件 |
with_lines | 迭代命令输出的每一行 | 处理命令行执行结果,如清理旧文件、读取日志内容 |
with_nested | 嵌套迭代(多重循环) | 多列表组合迭代,如 “用户名 + 用户组” 关联配置 |
with_sequence | 生成有序序列 | 支持数字 / 字母序列,如创建 test1-test5 系列测试文件 |
with_random_choice | 随机选择列表中的元素执行 | 从列表中随机挑选一个元素执行,如抽样测试节点连通性 |
二、Ansible 过滤器
过滤器用于对变量值进行加工处理(格式转换、加密等),核心语法为 {{ 变量名 | 过滤功能 }},支持链式调用(如 {{ 变量 | lower | trim }})。
2.1 基础过滤器
过滤器名称 | 功能说明 | 示例 |
---|---|---|
upper | 将字符串转换为纯大写 | {{ “Ansible” | upper}} |
lower | 将字符串转换为纯小写 | {{ “ANSIBLE” | lower}} |
trim | 去除字符串首尾空格(含换行符) | {{ " hello ansible " | trim}} |
length | 计算字符串长度或列表元素个数 | {{ “ansible” | length}} |
2.2 安全过滤器(字符串哈希加密)
主要用于用户密码加密,避免明文存储,支持 sha512、md5 等哈希算法。
示例:创建用户并设置 SHA512 加密密码
---
- name: 创建用户 sl 并设置加密密码
hosts: node1
tasks:
- name: 新建用户 sl,密码为 123456(SHA512 加密)
user:
name: sl
password: "{{ '123456' | password_hash('sha512') }}" # 核心加密逻辑
state: present
三、Ansible 判断逻辑
通过 when 关键字实现条件控制,根据 “变量状态、任务结果、路径属性” 等决定是否执行任务,支持多种内置测试器。
3.1 核心判断场景及测试器
3.1.1 变量状态判断
用于检查变量是否定义、是否为空值:
测试器 | 功能说明 | 示例(when 条件) |
---|---|---|
defined | 变量已定义则执行任务 | when: test_var is defined |
undefined | 变量未定义则执行任务 | when: test_var is undefined |
none | 变量已定义但为空值则执行任务 | when: test_var is none |
3.1.2 任务执行结果判断
先通过 register 注册任务结果,再根据结果状态执行后续操作:
测试器 | 功能说明 | 示例(when 条件) |
---|---|---|
success | 任务执行成功则执行 | when: task_result is success |
failed | 任务执行失败则执行 | when: task_result is failed |
changed | 任务导致主机状态变化则执行 | when: task_result is changed |
skipped | 任务被跳过则执行 | when: task_result is skipped |
3.1.3 路径属性判断
判断文件 / 目录 / 链接等路径的属性:
测试器 | 功能说明 | 示例(when 条件) |
---|---|---|
file | 路径是普通文件则执行 | when: “/tmp/test.txt” is file |
directory | 路径是目录则执行 | when: “/tmp/logs” is directory |
link | 路径是软链接则执行 | when: “/tmp/current” is link |
mount | 路径是挂载点则执行 | when: “/mnt/data” is mount |
exists | 路径存在(无论类型)则执行 | when: “/tmp/test.txt” is exists |
3.1.4 字符串与数据类型判断
测试器 | 功能说明 | 示例(when 条件) |
---|---|---|
lower | 字符串是纯小写则执行 | when: “ansible” is lower |
upper | 字符串是纯大写则执行 | when: “ANSIBLE” is upper |
string | 对象是字符串类型则执行 | when: test_var is string |
number | 对象是数字类型(int/float)则执行 | when: test_var is number |
3.2 高级判断:block-rescue-always(异常捕获)
类似 try-except-finally,用于捕获任务执行异常,实现 “正常执行→异常处理→收尾操作” 的逻辑:
block:正常尝试执行的任务块;
rescue:block 执行失败时触发的异常处理块;
always:无论 block 成功与否,都会执行的收尾块。
示例:逻辑卷创建与异常处理
---
- name: 逻辑卷创建(含异常处理)
hosts: all
tasks:
- name: 逻辑卷操作主逻辑
block: # 正常尝试创建 1500MiB 逻辑卷
- name: 在 research 卷组创建 1500MiB 逻辑卷 data
lvol:
vg: research
lv: data
size: 1500MiB
state: present
rescue: # block 失败(如空间不足)时执行
- name: 输出错误消息
debug:
msg: "Could not create logical volume of that size"
# 降级创建 800MiB 逻辑卷
- name: 改为创建 800MiB 逻辑卷 data
lvol:
vg: research
lv: data
size: 800MiB
state: present
always: # 无论成败,都格式化逻辑卷为 ext4
- name: 格式化逻辑卷为 ext4 文件系统
filesystem:
dev: /dev/research/data
fstype: ext4
when: "'research' in ansible_lvm.vgs" # 仅卷组存在时执行
# 卷组不存在时输出提示
- name: 卷组不存在提示
debug:
msg: "Volume group does not exist"
when: "'research' not in ansible_lvm.vgs"
3.3 任务状态控制(手动干预结果)
通过关键字手动控制任务执行结果,适配特殊场景:
关键字 / 模块 | 功能说明 | 示例 |
---|---|---|
ignore_errors: yes | 忽略任务错误,继续执行后续任务 | 执行 “读取不存在文件” 命令时,忽略错误不中断剧本 |
changed_when: 条件 | 自定义任务 “是否变化” 的判断 | changed_when: false → 强制标记任务 “无变化” |
failed_when: 条件 | 自定义任务 “是否失败” 的判断 | failed_when: “‘error’ in result.stdout” → 输出含 error 则标记失败 |
fail 模块 | 手动中断剧本,输出自定义错误 | 关键变量为空时,中断剧本并提示 |
四、前提准备:LVM 卷组部署(lvm2.yml)
在 node1、node2 上添加硬盘后,通过剧本安装 LVM 工具并创建卷组:
---
# 所有节点安装 lvm2 软件
- name: 安装 lvm2 工具
hosts: all
tasks:
- name: 安装 lvm2 包
yum:
name: lvm2
state: present
# node1 创建 2G 卷组 research
- name: 为 node1 创建卷组
hosts: node1
tasks:
- name: 分区 /dev/vdb(10MiB-2010MiB 为主分区)
parted:
device: /dev/vdb
number: 1
part_type: primary
part_start: 10MiB
part_end: 2010MiB
state: present
- name: 创建物理卷(PV)
pvcreate:
devices: /dev/vdb1
state: present
- name: 创建卷组(VG)research
lvg:
vg: research
pvs: /dev/vdb1
state: present
# node2 创建 1G 卷组 research
- name: 为 node2 创建卷组
hosts: node2
tasks:
- name: 分区 /dev/vdb(10MiB-1010MiB 为主分区)
parted:
device: /dev/vdb
number: 1
part_type: primary
part_start: 10MiB
part_end: 1010MiB
state: present
- name: 创建物理卷(PV)
pvcreate:
devices: /dev/vdb1
state: present
- name: 创建卷组(VG)research
lvg:
vg: research
pvs: /dev/vdb1
state: present
五、实战练习
主机清单说明
[test01]
node1
[test02]
node2
[web]
node3
node4
[test05]
node5
[webtest:children]
web # webtest 组包含 web 组的所有节点
5.1 练习 1:动态生成主机清单(newhosts.yml)
需求:从 http://ansible.example.com/materials/newhosts.j2 下载模板文件
完成该模板文件,用来生成新主机清单(主机的显示顺序没有要求),结构如下:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.122.10 node1.example.com node1
192.168.122.20 node2.example.com node2
192.168.122.30 node3.example.com node3
192.168.122.40 node4.example.com node4
192.168.122.50 node5.example.com node5
创建剧本/home/student/ansible/newhosts.yml,它将使用上述模板在 test01 主机组的主机上
生成文件/etc/newhosts。
5.1.1 步骤 1:下载并编写模板(newhosts.j2)
- 下载模板:curl -o http://ansible.example.com/materials/newhosts.j2
- 编辑模板内容:
#cat newhosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for sl in groups.all %}
{{ hostvars[sl].ansible_default_ipv4.address }} {{ hostvars[sl].ansible_fqdn }} {{ hostvars[sl].ansible_hostname }}
{% endfor %}
5.2.2 步骤 2:编写剧本(newhosts.yml)
---# 先收集所有主机信息(需访问 all 主机组)
- name: 收集所有主机变量
hosts: all
# 在 test01 组生成主机清单
- name: 生成 /etc/newhosts 文件
hosts: test01
tasks:
- name: 部署模板文件
template:
src: /home/student/ansible/newhosts.j2
dest: /etc/newhosts
5.2.3 验证命令
ansible test01 -m shell -a 'cat /etc/newhosts'
5.3 练习 3:差异化修改 /etc/issue(newissue.yml)
需求:创建剧本 /home/student/ansible/newissue.yml,满足下列要求:
1)在所有清单主机上运行,替换/etc/issue 的内容
2)对于 test01 主机组中的主机,/etc/issue 文件内容为 test01
3)对于 test02 主机组中的主机,/etc/issue 文件内容为 test02
4)对于 web 主机组中的主机,/etc/issue 文件内容为 Webserver
---
- name: 差异化修改 /etc/issue
hosts: all
tasks:
- name: 设置 /etc/issue 内容
copy:
content: |
{% if "test01" in group_names %}
test01
{% elif "test02" in group_names %}
test02
{% elif "web" in group_names %}
Webserver
{% endif %}
dest: /etc/issue
验证命令
ansible all -m shell -a 'cat /etc/issue'