Ansible 核心功能:循环、过滤器、判断与错误处理全解析
Ansible 核心功能:循环、过滤器、判断与错误处理全解析
本文基于Ansible实践场景,系统梳理循环迭代、数据过滤、条件判断及错误处理的核心用法,包含可直接复用的示例代码与关键注意事项,适用于Ansible自动化运维场景的学习与实战。
一、Ansible 循环迭代
循环用于批量执行重复任务(如安装多个软件、拷贝多个文件),Ansible支持传统with_*
系列迭代器与现代loop
(推荐配合过滤器使用)。
1. 传统 with_*
循环(适用于简单场景)
(1)with_items
:迭代列表
用途:批量处理列表中的元素(如安装多个软件包、创建多个文件)。
核心逻辑:{{ item }}
代表列表中的每个元素。
例
---
- name: with_items testhosts: node1tasks:- name: debug1debug:msg: "{{ item }}"with_items:- aaa- bbb- ccc
(2)with_dict
:迭代字典
用途:处理键值对数据(如配置网络参数、批量设置变量)。
核心逻辑:{{ item.key }}
取字典的键,{{ item.value }}
取字典的值。
# 示例:打印网络参数(键-值对应)
---
- name: with_dict testhosts: alltasks:- name: aaaadebug:msg: "{{ item.key }} | {{ item.value }}"with_dict:name: "{{ ansible_hostname }}"address: "{{ ansible_enp1s0.ipv4.address }}"dns: "{{ ansible_fqdn }}"
(3)with_fileglob
:迭代文件(按路径匹配)
用途:批量处理指定路径下的文件(如拷贝所有.sh
或.py
脚本)。
注意:路径为Ansible控制节点的路径,非受控节点。
# 示例:批量拷贝控制节点/tmp下的.sh和.py文件到受控节点/tmp
---
- name: with_fileglob testhosts: node1tasks:- name: copy .sh and .py filescopy:src: "{{ item }}" # item遍历匹配到的文件dest: /tmp/with_fileglob:- /tmp/*.sh- /tmp/*.py
(4)with_lines
:迭代命令输出(按行)
用途:将Linux命令的输出按行作为迭代元素(如处理find
找到的文件)。
# 示例:拷贝find命令找到的所有.yml文件到受控节点/tmp
---
- name: with_lines testhosts: node1tasks:- name: copy ansible yml filescopy:src: "{{ item }}"dest: /tmp/with_lines:- find /etc/ansible -name "*.yml" # 命令输出按行迭代
(5)with_nested
:嵌套迭代
用途:实现多列表的“笛卡尔积”组合(如A列表每个元素与B列表所有元素匹配)。
核心逻辑:{{ item[0] }}
取第一个列表元素,{{ item[1] }}
取第二个列表元素。
# 示例:列表[a,b]与[1,2,3]嵌套组合
- name: with_nested testhosts: node1tasks:- name: print combined pairsdebug:msg: "字母:{{ item[0] }} | 数字:{{ item[1] }}"with_nested:- [a, b] # 第一个列表(index 0)- [1, 2, 3] # 第二个列表(index 1)
# 输出结果:a&1、a&2、a&3、b&1、b&2、b&3
(6)with_sequence
:生成有序序列
用途:生成连续数字序列(如创建编号文件、批量命名)。
参数:start
(起始值)、end
(结束值)、stride
(步长)。
# 示例:生成1-5的序列(步长1)
- name: with_sequence testhosts: node1tasks:- name: print sequencedebug:msg: "当前数字:{{ item }}"with_sequence:start=1end=5stride=1 # 每次递增1
(7)with_random_choice
:随机取列表元素
用途:从列表中随机选择一个元素(如随机选择测试节点、随机生成值)。
# 示例:随机输出列表中的一个元素
- name: random choicehosts: node1tasks:- name: print random itemdebug:msg: "随机选中:{{ item }}"with_random_choice:- 1- 2- a- b- c
2. 现代 loop
(推荐)
loop
是Ansible推荐的新一代循环语法,需配合过滤器(如list
、dict2items
)使用,功能更灵活。
- 替代
with_items
:loop: [元素1, 元素2, ...]
- 替代
with_dict
:loop: "{{ 字典 | dict2items }}"
(此时item.key
/item.value
仍可用)
# 示例1:用loop替代with_items(批量安装软件)
---
- name: with_dict testhosts: alltasks:- name: aaaadebug:msg: "{{ item }}"loop:- 12345678- abcdefgh- ABCDEFGH- name: bbbbdebug:msg: "{{ item.key }} -> {{ item.value }}"loop: "{{{ 'name': ansible_hostname, 'address': ansible_default_ipv4.address, 'dns': ansible_fqdn } | dict2items }}"
二、Ansible 过滤器
过滤器用于数据转换与处理(如字符串大小写、数字类型转换、密码哈希),语法为{{ 变量 | 过滤器(参数) }}
。
1. 字符串相关过滤器
处理字符串格式、长度、截取等,常见场景:配置文件内容格式化、变量清洗。
-
upper
:将字符串转为纯大写。
示例代码:{{ "abc123ABC" | upper }}
,输出结果:ABC123ABC
。 -
lower
:将字符串转为纯小写。
示例代码:{{ "abc123ABC" | lower }}
,输出结果:abc123abc
。 -
capitalize
:将字符串首字母转为大写,其余字母转为小写。
示例代码:{{ "abc123ABC" | capitalize }}
,输出结果:Abc123abc
。 -
trim
:去除字符串首尾的空格(中间空格保留)。
示例代码:{{ " abc " | trim }}
,输出结果:abc
。 -
length
/count
:计算字符串的长度(字符个数),两者功能完全等效。
示例代码:{{ "abc123" | length }}
,输出结果:6
。 -
first
:获取字符串的第一个字符。
示例代码:{{ "abc123" | first }}
,输出结果:a
。 -
last
:获取字符串的最后一个字符。
示例代码:{{ "abc123" | last }}
,输出结果:3
。 -
center(width)
:将字符串居中显示,总长度固定为width
,不足部分用空格补全。
示例代码:{{ "abc" | center(10) }}
,输出结果:abc
(前后各3个空格,总长度10)。 -
list
:将字符串拆分为字符列表,每个字符作为列表的一个元素。
示例代码:{{ "1a2b" | list }}
,输出结果:['1','a','2','b']
。 -
shuffle
:先将字符串拆分为字符列表,再随机打乱列表中元素的顺序(每次执行结果可能不同)。
示例代码:{{ "1a2b" | shuffle }}
,可能的输出结果:['a','2','1','b']
。
2. 数字相关过滤器
处理数字类型转换、计算、随机数生成,解决Ansible中“字符串与数字无法直接运算”的问题。
-
int(default=值)
:将输入转为整数,若无法转换(如输入为非数字字符串),则返回默认值(默认值不指定时为0)。
示例1(正常转换):{{ "8" | int }}
,输出结果:8
;
示例2(无法转换):{{ "a" | int(6) }}
,输出结果:6
。 -
float(default=值)
:将输入转为浮点数,若无法转换,返回默认值(默认值不指定时为0.0)。
示例1(正常转换):{{ "8.5" | float }}
,输出结果:8.5
;
示例2(无法转换):{{ "a" | float(8.88) }}
,输出结果:8.88
。 -
abs
:获取数字的绝对值(对负数生效,正数不变)。
示例代码:{{ -10 | abs }}
,输出结果:10
。 -
round(n)
:对数字进行四舍五入,n
指定保留的小数位数(不指定时默认保留0位,即整数)。
示例1(保留整数):{{ 12.5 | round }}
,输出结果:13
;
示例2(保留3位小数):{{ 3.14159 | round(3) }}
,输出结果:3.142
。 -
random(start=值, step=值)
:生成随机数,默认范围是“0到过滤器前的数字”;start
指定随机数的起始值,step
指定随机数的递增步长(即随机数只能是“start + 整数×step”)。
示例1(0-100的随机整数):{{ 100 | random }}
,可能输出:45
;
示例2(5-15、步长3的随机数):{{ 15 | random(start=5, step=3) }}
,可能输出:5
、8
、11
、14
中的一个。
3. 文件/密码相关过滤器
用于文件校验、密码哈希(安全存储密码,避免明文),常见于用户创建、配置文件校验场景。
-
hash(算法)
:对字符串进行哈希计算,支持的算法包括md5
、sha1
、sha256
等,相同输入会生成固定的哈希值。
示例代码:{{ "123456" | hash("md5") }}
,输出结果为e10adc3949ba59abbe56e057f20f883e
(固定md5值)。 -
checksum
:功能与hash("md5")
完全等效,常用于文件内容的校验(判断文件是否被修改)。
示例代码:{{ "123456" | checksum }}
,输出结果与hash("md5")
一致,。 -
password_hash(算法, 盐)
:对密码进行哈希处理(推荐使用sha512
算法,安全性高),盐
为可选参数——不指定时自动生成随机盐(避免彩虹表破解),指定时用自定义字符串作为盐。
示例1(自动生成盐):{{ "redhat" | password_hash("sha512") }}
,输出结果为带随机盐的sha512哈希值(每次执行不同);
示例2(自定义盐):{{ "redhat" | password_hash("sha512", "mysalt") }}
,输出结果为固定的sha512哈希值(盐固定,结果固定)。
实战场景:创建用户并设置哈希密码(避免明文存储)
- name: create user with hashed passwordhosts: node1tasks:- name: create user chenyuuser:name: chenyupassword: "{{ 'redhat' | password_hash('sha512') }}" # 哈希存储密码,自动生成盐state: present
三、Ansible 条件判断(When)
when
用于根据条件决定任务是否执行,支持运算符、Tests(断言) 两种判断方式。
1. 常用运算符
用于比较变量值、组合条件:
- 比较运算符:
==
(等于)、!=
(不等于)、>
(大于)、<
(小于)、>=
(大于等于)、<=
(小于等于) - 逻辑运算符:
and
(且,两个条件同时满足)、or
(或,任意一个条件满足)、not
(非,否定条件) - 成员运算符:
in
(判断元素是否在列表/字符串中)、not in
(判断元素是否不在列表/字符串中)
# 示例:当变量aa等于11且变量bb包含"test"时,执行debug任务
- name: condition with operatorsdebug:msg: "条件满足,执行此任务"when: aa == 11 and "test" in bb
2. 常用 Tests(断言)
Tests 是Ansible预定义的判断逻辑,格式为变量 is Test
,覆盖变量状态、任务结果、路径类型等场景。
(1)变量相关 Tests
判断变量是否定义、是否为空,常用于避免“未定义变量导致剧本报错”:
-
defined
:变量已被定义(无论值是否为空),则条件为真。
示例代码:when: aa is defined
(若aa: 11
或aa: ""
,均满足条件)。 -
undefined
:变量未被定义(未在vars
或 inventory 中声明),则条件为真。
示例代码:when: bb is undefined
(若未声明bb
,满足条件)。 -
none
:变量已定义,但值为空(如空字符串、空列表、空字典),则条件为真。
示例代码:when: cc is none
(若cc: ""
或cc: []
,满足条件)。
# 示例:变量状态判断
- name: test variable statushosts: node1vars:aa: 11 # 已定义且有值cc: "" # 已定义但为空tasks:- debug: msg="aa已定义" when: aa is defined- debug: msg="bb未定义" when: bb is undefined- debug: msg="cc已定义但为空" when: cc is none
(2)任务结果相关 Tests
判断上一个任务的执行状态(需先用register
将任务结果注册为变量),常用于“根据前序任务结果决定后续操作”:
-
success
/succeeded
:前序任务执行成功(退出码为0),则条件为真,两者功能等效。
示例代码:when: task_result is success
。 -
failure
/failed
:前序任务执行失败(退出码非0),则条件为真,两者功能等效。
示例代码:when: task_result is failed
。 -
changed
:前序任务导致受控节点的系统状态发生变化(如创建文件、修改配置),则条件为真。
示例代码:when: task_result is changed
。 -
skipped
:前序任务因条件不满足被跳过(未执行),则条件为真。
示例代码:when: task_result is skipped
。
# 示例:判断ls /mnt任务的执行结果
- name: test task resulthosts: node1tasks:- name: list /mnt directoryshell: ls /mntregister: ls_result # 将任务结果注册为变量ls_result- debug: msg="ls /mnt执行成功" when: ls_result is success- debug: msg="ls /mnt执行失败" when: ls_result is failed- debug: msg="ls /mnt导致系统状态变化" when: ls_result is changed
(3)路径相关 Tests
判断Ansible控制节点上路径的类型(注意:不是受控节点!),常用于“执行文件操作前校验路径”:
file
:路径指向一个普通文件(非目录、非链接),则条件为真。
示例代码:when: /tmp/test.sh is file
。directory
:路径指向一个目录(非文件、非链接),则条件为真。
示例代码:when: /tmp/data is directory
。link
:路径指向一个软链接(无论链接目标是否存在),则条件为真。
示例代码:when: /tmp/link.sh is link
。mount
:路径是一个已挂载的挂载点(即mount
命令可查看到该路径),则条件为真。
示例代码:when: /boot is mount
。exists
:路径存在(无论路径是文件、目录还是链接),则条件为真。
示例代码:when: /tmp/test.sh is exists
。
创建相关文件
student@master tmp]$ touch test.sh
[student@master tmp]$ mkdir data
[student@master tmp]$ ln -s test.sh link.sh
[student@master tmp]$ ln test.sh hardlink.sh
编写剧本
---
- name: with_dict testhosts: node1tasks:- name: aaaadebug:msg: '/tmp/test.sh is file'when: "'/tmp/test.sh' is file"- name: bbbbdebug:msg: '/tmp/data is directory'when: "'/tmp/data' is directory"- name: ccccdebug:msg: '/tmp/link.sh is link'when: "'/tmp/link.sh' is link"- name: dddddebug:msg: '/tmp/hardlink.sh is link'when: "'/tmp/hardlink.sh' is link"- name: eeeedebug:msg: '/boot is mount'when: "'/boot' is mount "- name: ffffdebug:msg: '/tmp/test.sh is exists'when: "'/tmp/test.sh' is exists "
运行剧本
TASK [aaaa] ********************************************************************************
ok: [node1] => {"msg": "/tmp/test.sh is file"
}TASK [bbbb] ********************************************************************************
ok: [node1] => {"msg": "/tmp/data is directory"
}TASK [cccc] ********************************************************************************
ok: [node1] => {"msg": "/tmp/link.sh is link"
}TASK [dddd] ********************************************************************************
skipping: [node1]TASK [eeee] ********************************************************************************
ok: [node1] => {"msg": "/boot is mount"
}TASK [ffff] ********************************************************************************
ok: [node1] => {"msg": "/tmp/test.sh is exists"
}
可以看到只有hredlink被跳过这是因为hredlink是硬链接属于普通文件
(4)其他 Tests
覆盖字符串格式、数据类型等判断场景:
-
lower
:字符串中所有字母均为小写(非字母字符不影响),则条件为真。
示例代码:when: "abc123" is lower
(满足条件)、when: "aBc123" is lower
(不满足条件)。 -
upper
:字符串中所有字母均为大写(非字母字符不影响),则条件为真。
示例代码:when: "ABC123" is upper
(满足条件)、when: "ABc123" is upper
(不满足条件)。 -
string
:对象是字符串类型(即使内容是数字,如"123"
),则条件为真。
示例代码:when: "123" is string
(满足条件)、when: 123 is string
(不满足条件)。 -
number
:对象是数字类型(整数或浮点数,如123
、3.14
),则条件为真。
示例代码:when: 123 is number
(满足条件)、when: "123" is number
(不满足条件)。
四、块处理(block/rescue/always)
用于批量控制任务的错误处理,实现“主任务-失败恢复-最终执行”的逻辑,类似编程语言中的try-catch-finally
:
block
:定义“正常执行的主任务块”,若块内任意任务失败,直接跳转到rescue
。rescue
:定义“失败恢复任务块”,仅当block
执行失败时才执行。always
:定义“最终执行任务块”,无论block
成功或失败,都会执行(常用于清理、收尾操作)。
实战场景:创建逻辑卷(LV)的错误处理
需求:在research
卷组创建1500MiB的data
LV;若卷组不存在则提示错误;若空间不足则用800MiB创建,且最终需格式化LV为ext4。
# 前提:node1的research卷组大小为2G(可创建1500MiB LV),node2的research卷组大小为1G(仅可创建800MiB LV)
---
- name: aaaaaahosts: all tasks:# 核心块:创建LV并处理失败- name: creat datablock:# 主任务:尝试创建1500MiB的LV- name: create lv1lvol:vg: researchlv: datasize: 1500state: presentrescue:# 恢复逻辑:创建失败(空间不足),输出错误信息并改用800MiB创建- name: not 1500debug:msg: "Could not create logical volume of that size"- name: create lv2lvol:vg: researchlv: datasize: 800Malways:# 最终执行:无论LV是否创建成功,都将其格式化为ext4- name: filesystemfilesystem:dev: /dev/research/datafstype: ext4when: "'research' in ansible_facts.lvm.vgs" # 仅当卷组research存在时,执行该块# 卷组不存在时,输出提示信息- name: not researchdebug:msg: "Volume group does not exist"when: "'research' not in ansible_facts.lvm.vgs"
五、错误处理(fail/failed_when/ignore_errors/changed_when)
用于主动控制任务的执行状态(中断剧本、跳过错误、修改状态),确保剧本在异常场景下的可控性。
1. fail
模块:主动中断剧本
当满足特定条件时,主动中断当前剧本的执行,并输出自定义错误信息,常与when
配合使用(需明确中断条件)。
- name: interrupt playbook with fail modulehosts: node1tasks:# 执行shell命令,输出含"error"的内容- name: execute shell commandshell: echo "this is a test--error"register: cmd_result # 将命令输出注册为变量# 当输出中包含"error"时,中断剧本- name: fail and stop playbookfail:msg: "Found 'error' in command output, stopping playbook now" # 自定义错误信息when: "'error' in cmd_result.stdout" # 中断条件:输出含"error"# 上方任务中断后,此任务不会执行- name: unreachable taskdebug:msg: "This task will not run because playbook is stopped"
运行效果
PLAY [aaaaaa] ******************************************************************************TASK [Gathering Facts] *********************************************************************
ok: [node5]
ok: [node3]
ok: [node4]
ok: [node1]
ok: [node2]TASK [create lv1] **************************************************************************
skipping: [node3]
skipping: [node4]
skipping: [node5]
fatal: [node1]: FAILED! => {"changed": false, "err": " Volume group \"research\" has insufficient free space (249 extents): 375 required.\n", "msg": "Creating logical volume 'data' failed", "rc": 5}
changed: [node2]TASK [dddd] ********************************************************************************
ok: [node1] => {"msg": "Could not create logical volume of that size"
}TASK [create lv2] **************************************************************************
changed: [node1]TASK [filesystem] **************************************************************************
skipping: [node3]
skipping: [node4]
skipping: [node5]
changed: [node1]
changed: [node2]TASK [cccc] ********************************************************************************
skipping: [node1]
skipping: [node2]
ok: [node3] => {"msg": "Volume group does not exist"
}
ok: [node4] => {"msg": "Volume group does not exist"
}
ok: [node5] => {"msg": "Volume group does not exist"
}
2. failed_when
:自定义任务失败条件
强制将“命令执行成功(退出码为0)的任务”标记为失败,无需依赖fail
模块,直接通过条件控制任务状态。
- name: custom failed condition with failed_whenhosts: node1tasks:# 执行shell命令(退出码为0,默认标记为成功)- name: execute shell commandshell: echo "this is a test--error"register: cmd_result# 自定义失败条件:输出含"error"时,将任务标记为失败failed_when: "'error' in cmd_result.stdout"# 上一步任务被标记为失败,剧本默认中断,此任务不执行- name: unreachable taskdebug:msg: "This task will not run because previous task failed"
3. ignore_errors: yes
:跳过错误
当任务执行失败时(如命令退出码非0、变量未定义),忽略该错误并继续执行后续任务,不中断整个剧本。
- name: ignore task errors with ignore_errorshosts: node1tasks:# 正常执行:打印受控节点的FQDN(ansible_fqdn为Ansible内置变量,已定义)- name: print controlled node's FQDNdebug:msg: "FQDN: {{ ansible_fqdn }}"# 执行失败:打印未定义的变量ansible_ip,但忽略错误- name: print non-existent variable (ignore error)debug:msg: "IP: {{ ansible_ip }}" # ansible_ip未定义,任务默认失败ignore_errors: yes # 忽略此任务的失败,继续执行后续任务# 正常执行:创建/tmp/abc文件(因上一步错误被忽略,此任务可执行)- name: create /tmp/abc filefile:path: /tmp/abcstate: touch
4. changed_when
:自定义任务状态
强制修改任务的changed
状态(Ansible默认根据“任务是否修改系统”判断changed
):
changed_when: true
:无论任务是否修改系统,强制将状态标记为changed
。changed_when: false
:无论任务是否修改系统,强制将状态标记为ok
。
- name: custom task status with changed_whenhosts: node1tasks:# 强制标记为changed:debug任务默认不修改系统(状态为ok),此处强制改为changed- name: debug with forced changed statusdebug:msg: "FQDN: {{ ansible_fqdn }}"changed_when: true# 强制标记为ok:shell任务(ls /tmp)默认不修改系统(状态为ok),此处明确强制为ok- name: list /tmp directory with forced ok statusshell: ls /tmpchanged_when: false
小练习
从 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,它将使用上述模板在 node1 主机组的主机上
生成文件/etc/newhosts。
#下载模板
[student@master ansible]$ curl -O http://ansible.example.com/materials/newhosts.j2
#修改模板
{% for cy in groups.all %}
{{ hostvars[cy].ansible_default_ipv4.address}} {{ hostvars[cy].ansible_fqdn }} {{ hostvars[cy].ansible_hostname}}
{% endfor %}
编写剧本---
- name: get facthosts: all
- name: cp filehosts: node1tasks:- name: cp filetemplate:src: /home/student/ansible/newhosts.j2dest: /etc/newhosts
#运行剧本
#查看node1是否获取到了ip域名和主机名
[student@master ansible]$ ansible node1 -m shell -a 'cat /etc/newhosts'
node1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6192.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/newissue.yml,满足下列要求:
1)在所有清单主机上运行,替换/etc/issue 的内容
2)对于 test01 主机组中的主机,/etc/issue 文件内容为 test01
3)对于 test02 主机组中的主机,/etc/issue 文件内容为 test02
4)对于 web 主机组中的主机,/etc/issue 文件内容为 Webserver
inventory
[test01]
node1[test02]
node2[web]
node3
node4[test05]
node5[webtest:children]
web
创建剧本
---
- name: replace filehosts: alltasks:- name: test1copy:content: |{% if 'test01' in group_names %}test01{% elif 'test02' in group_names %}test02{% elif 'web' in group_names %}Webserver{% endif %}dest: /etc/issue
运行剧本后查看
[student@master ansible]$ ansible all -m shell -a 'cat /etc/issue'
node1 | CHANGED | rc=0 >>
test01
node5 | CHANGED | rc=0 >>node4 | CHANGED | rc=0 >>
Webserver
node3 | CHANGED | rc=0 >>
Webserver
node2 | CHANGED | rc=0 >>
test02