一、文档核心定位
本文档聚焦Ansible自动化运维中的两大核心功能——循环与判断,通过“功能说明+完整Playbook代码”的形式,覆盖循环迭代场景(列表、字典、文件等)、数据处理过滤器(字符串、数字、加密等)、条件判断逻辑(变量、任务结果、路径等)及错误处理方案,所有代码均针对Linux环境设计,可直接在Ansible控制端编辑执行。
二、Ansible循环:批量执行重复任务
Ansible循环通过with_*
系列关键字或loop
(配合过滤器)实现,核心是用{{ item }}
指代迭代对象,适配不同批量操作场景,以下为所有循环类型的详细拆解。
(一)with_items:迭代列表(最常用场景)
功能定义
遍历列表中的元素,将每个元素作为{{ item }}
代入任务,实现“一次定义,多次执行”,典型场景为批量安装软件包、创建多个文件或用户。
应用场景
在Linux受控节点(如node1)批量安装httpd
(网页服务)、samba
(文件共享服务)、samba-client
(samba客户端),需先配置本地yum源(依赖光盘挂载)。
完整代码与逐行说明
# 编辑Playbook文件:vim b.yml
---
- name: install packages # Play名称:明确任务目标(安装软件包)
hosts: node1 # 目标受控节点:指定任务在node1上执行
tasks: # 任务列表:包含依赖配置与核心安装任务
# 任务1:配置BaseOS本地yum源(为安装软件提供包来源)
- name: yum_repo1 # 任务名:区分不同yum源配置
yum_repository: # 模块:用于管理yum仓库配置
file: server # 配置文件名:最终生成/etc/yum.repos.d/server.repo
name: baseos # 仓库标识名:唯一识别该仓库
description: rhel8 # 仓库描述:说明仓库用途(RHEL8系统源)
baseurl: file:///mnt/BaseOS # 包源路径:本地光盘挂载后的BaseOS目录
enabled: yes # 启用仓库:允许通过该仓库安装软件
gpgcheck: no # 关闭GPG校验:本地源无需校验包完整性
# 任务2:配置AppStream本地yum源(补充软件包来源)
- name: yum_repo2
yum_repository:
file: server # 与BaseOS共用一个配置文件
name: appstream # 仓库标识名:与BaseOS区分
description: appstream # 仓库描述:RHEL8应用流源
baseurl: file:///mnt/AppStream # 包源路径:光盘挂载后的AppStream目录
enabled: yes
gpgcheck: no
# 任务3:挂载光盘(为yum源提供本地文件支持)
- name: mount cdrom # 任务名:挂载光盘
mount: # 模块:用于管理文件系统挂载
src: /dev/cdrom # 源设备:Linux中光盘的设备文件
path: /mnt # 挂载点:将光盘挂载到/mnt目录
fstype: iso9660 # 文件系统类型:ISO镜像固定格式
state: mounted # 挂载状态:确保光盘已挂载(若未挂载则自动挂载)
# 任务4:核心循环安装软件(with_items迭代列表)
- name: install pks # 任务名:安装目标软件包
yum: # 模块:用于管理RPM软件包
name: "{{ item }}" # 软件名:{{ item }}依次取列表中的软件名
state: present # 安装状态:确保软件已安装(若未安装则自动安装)
with_items: # 迭代列表:需安装的软件包集合
- httpd
- samba
- samba-client
执行与验证
- 执行命令:
ansible-playbook b.yml
- 验证方式:在node1上执行
rpm -qa | grep -E "httpd|samba"
,确认3个软件均已安装。
(二)with_dict:迭代字典(键值对关联场景)
功能定义
遍历字典的“键-值”对,通过item.key
调用字典的键(如配置项名称),item.value
调用对应的值(如配置项内容),适用于批量处理关联数据(如网络配置、服务端口映射)。
应用场景
打印网络配置中的address
(IP地址)、netmask
(子网掩码)、gateway
(网关)的键值对,验证字典迭代逻辑。
完整代码与说明
# 编辑Playbook文件:vim c.yml
---
- name: test # Play名称:测试字典迭代
hosts: node1
tasks:
- name: debug # 任务名:输出调试信息
debug: # 模块:用于输出变量或文本信息
msg: "{{ item.key }} & {{ item.value }}" # 输出格式:键 & 值
with_dict: # 迭代字典:网络配置键值对
address: 1 # 键:address,值:1(示例值,可替换为实际IP如192.168.1.10)
netmask: 2 # 键:netmask,值:2(示例值,可替换为255.255.255.0)
gateway: 3 # 键:gateway,值:3(示例值,可替换为192.168.1.1)
执行结果示例
TASK [debug] *****************************************************************
ok: [node1] => (item={'key': 'address', 'value': 1}) => {
"msg": "address & 1"
}
ok: [node1] => (item={'key': 'netmask', 'value': 2}) => {
"msg": "netmask & 2"
}
ok: [node1] => (item={'key': 'gateway', 'value': 3}) => {
"msg": "gateway & 3"
}
(三)with_fileglob:迭代文件(批量文件操作)
功能定义
匹配Ansible控制端指定路径下的文件(支持通配符*
),批量处理文件操作(如拷贝、删除),需注意:仅识别控制端文件,不识别受控端文件。
应用场景
将控制端/tmp
目录下所有.sh
(Shell脚本)和.py
(Python脚本)文件,批量拷贝到受控端node1的/tmp
目录。
完整代码与说明
# 编辑Playbook文件:vim d.yml
---
- name: test # Play名称:测试文件迭代拷贝
hosts: node1
tasks:
- name: cp file # 任务名:拷贝文件
copy: # 模块:用于文件拷贝(控制端→受控端)
src: "{{ item }}" # 源文件路径:{{ item }}依次取匹配的控制端文件
dest: /tmp/ # 目标路径:受控端的/tmp目录(保持原文件名)
with_fileglob: # 控制端文件匹配规则
- /tmp/*.sh # 匹配控制端/tmp下所有.sh后缀的文件
- /tmp/*.py # 匹配控制端/tmp下所有.py后缀的文件
(四)with_lines:迭代命令输出行(基于命令结果操作)
功能定义
执行Linux命令(在控制端执行),将命令输出按“行”拆分,每一行作为{{ item }}
代入任务,适用于基于命令结果的批量操作(如拷贝命令找到的特定文件)。
应用场景
查找控制端/etc/ansible
目录下所有.yml
(Ansible Playbook)文件,批量拷贝到受控端node1的/tmp
目录。
完整代码与说明
# 编辑Playbook文件:vim e.yml
---
- name: test # Play名称:测试命令输出迭代
hosts: node1
tasks:
- name: cp file # 任务名:拷贝.yml文件
copy:
src: "{{ item }}" # 源文件路径:find命令输出的每一行(单个.yml文件路径)
dest: /tmp/
with_lines: # 执行命令并迭代输出行
- find /etc/ansible -name "*.yml" # 命令:查找ansible目录下所有.yml文件
(五)with_nested:嵌套迭代(多列表组合场景)
功能定义
实现多列表的“笛卡尔积”迭代,即第一个列表的每个元素与第二个列表的所有元素逐一组合,通过item[0]
调用第一个列表元素,item[1]
调用第二个列表元素。
应用场景
将列表[a, b]
(如服务名称)与列表[1, 2, 3]
(如实例编号)组合,生成a&1
、a&2
、a&3
、b&1
、b&2
、b&3
等组合,用于批量生成服务实例名称。
完整代码与说明
# 编辑Playbook文件:vim f.yml
---
- name: test # Play名称:测试嵌套迭代
hosts: node1
tasks:
- name: debug # 任务名:输出组合结果
debug:
msg: "{{ item[0] }} & {{ item[1] }}" # 输出格式:第一个列表元素 & 第二个列表元素
with_nested: # 嵌套迭代的两个列表
- [a, b] # 第一个列表:基础元素
- [1, 2, 3] # 第二个列表:组合元素
(六)with_sequence:排序列(有序数字生成)
功能定义
生成指定范围、步长的有序数字序列,支持3个核心参数:
start
:序列起始值(默认从0开始)end
:序列结束值(必填)stride
:序列步长(默认1,即连续数字)
应用场景
生成1-5的连续数字序列(步长1),用于批量创建带编号的资源(如文件file1
-file5
、用户user1
-user5
)。
完整代码与说明
# 编辑Playbook文件:vim g.yml
---
- name: test # Play名称:测试有序序列生成
hosts: node1
tasks:
- name: debug # 任务名:输出序列数字
debug:
msg: "{{ item }}" # 输出每个序列数字
with_sequence: # 序列参数配置
start=1 # 起始值:1
end=5 # 结束值:5
stride=1 # 步长:1(生成1、2、3、4、5)
(七)with_random_choice:随机取值(随机操作场景)
功能定义
从指定列表中随机选择一个元素执行任务,每次运行Playbook的结果可能不同,适用于需要随机化的场景(如随机选择测试节点、随机生成测试数据)。
应用场景
从列表[1, 2, a, b, c]
中随机选择一个元素输出,验证随机迭代逻辑。
完整代码与说明
# 编辑Playbook文件:vim h.yml
---
- name: test # Play名称:测试随机取值
hosts: node1
tasks:
- name: debug # 任务名:输出随机元素
debug:
msg: "{{ item }}" # 输出随机选中的元素
with_random_choice: # 随机选择的列表
- 1
- 2
- a
- b
- c
(八)Loop与过滤器:数据处理增强
功能定义
loop
是Ansible推荐的新版循环方式,需配合过滤器实现数据处理(如字符串转换、数字计算、密码加密),过滤器通过|
调用,可直接作用于变量或迭代结果。
1. 常用字符串过滤器(处理文本数据)
功能说明
过滤器 | 作用 | 示例 | 输出结果 |
---|---|---|---|
upper |
字符串转全大写 | `“abc123ABC 666” | upper` |
lower |
字符串转全小写 | `“abc123ABC 666” | lower` |
trim |
去除首尾空格 | `" abc " | trim` |
length |
计算字符串长度(含空格) | `“abc123ABC 666” | length` |
完整代码
---
- name: test # Play名称:测试字符串过滤器
hosts: node1
vars: # 定义测试变量
testvar: "abc123ABC 666" # 含大小写、数字、空格的字符串
testvar1: " abc " # 含首尾空格的字符串
tasks:
- name: debug1 # 测试upper过滤器
debug:
msg: "{{ testvar | upper }}"
- name: debug2 # 测试lower过滤器
debug:
msg: "{{ testvar | lower }}"
- name: debug3 # 测试trim过滤器
debug:
msg: "{{ testvar1 | trim }}"
- name: debug4 # 测试length过滤器
debug:
msg: "{{ testvar | length }}"
2. 补充字符串过滤器
完整代码与说明
# 编辑文件:vim filterstr.yml
---
- name: 过滤器 # Play名称:补充字符串过滤器测试
hosts: servera # 目标节点:servera(可替换为node1)
vars:
testvar: "abc123ABC 666"
testvar1: " abc "
testvar2: "123456789"
testvar3: "1a2b,@#$%^&" # 含特殊字符的字符串
tasks:
- name: 将字符串转换成纯大写
debug: msg="{{ testvar | upper }}"
- name: 将字符串转换成纯小写
debug: msg="{{ testvar | lower }}"
- name: 将字符串首字母大写,之后的所有字母纯小写(capitalize)
debug: msg="{{ testvar | capitalize }}" # 输出:Abc123abc 666
- name: 返回字符串的第一个字符(first)
debug: msg="{{ testvar | first }}" # 输出:a
- name: 返回字符串的最后一个字符(last)
debug: msg="{{ testvar | last }}" # 输出:6
- name: 将字符串开头和结尾的空格去除(trim)
debug: msg="{{ testvar1 | trim }}"
- name: 将字符串居中,总长度30,两边用空格补齐(center)
debug: msg="{{ testvar1 | center(width=30) }}" # 输出:中间为abc,两边共27个空格
- name: 返回字符串长度(length,与count等效)
debug: msg="{{ testvar2 | length }}" # 输出:9
- name: 将字符串转换成列表,每个字符为元素(list)
debug: msg="{{ testvar3 | list }}" # 输出:['1','a','2','b',',','@','#','$','%','^','&']
- name: 将字符串转列表并随机打乱(shuffle,“洗牌”效果)
debug: msg="{{ testvar3 | shuffle }}" # 输出:随机排序的字符列表
3. 数字操作过滤器(处理数值数据)
[root@foundation0 ansible]# cat filterdata.yml
---
- name: this playbook
hosts: servera
vars:
testvar4: -1
tasks:
- name: 转int并计算(字符串与数字不可直接计算)
debug: msg="{{ 8+('8' | int) }}"
- name: 转int,无法转换返回默认值6
debug: msg="{{ 'a' | int(default=6) }}"
- name: 转float
debug: msg="{{ '8' | float }}"
- name: 转float,无法转换返回8.88
debug: msg="{{ 'a' | float(8.88) }}"
- name: 取绝对值(abs)
debug: msg="{{ testvar4 | abs }}"
- name: 四舍五入(round 四舍五入偶数)
debug: msg="{{ 12.5 | round }}" ##输出为12
- name: 保留5位小数(round(5))
debug: msg="{{ 3.1415926 | round(5) }}"
- name: 0-100随机数(random)
debug: msg="{{ 100 | random }}"
- name: 5-10随机数(start=5)
debug: msg="{{ 10 | random(start=5) }}"
- name: 5-15随机数,步长3(step=3)
debug: msg="{{ 15 | random(start=5,step=3) }}"
- name: 0-15随机数,5的倍数(step=5)
debug: msg="{{ 15 | random(step=5) }}"
功能说明
过滤器 | 作用 | 示例 | 输出结果 |
---|---|---|---|
int |
转整数,无法转换时返回默认值(默认0) | `“8” | int、 “a” |
float |
转浮点型,无法转换时返回默认值(默认0.0) | `“8” | float、 “a” |
abs |
取绝对值 | `-1 | abs` |
round |
四舍五入,可指定小数位数 | `12.5 | round、 3.1415926 |
random |
生成随机数,支持 |
4. 文件 / 目录类过滤器(含加密)
- 代码:
[root@foundation0 ansible]# cat filterfile.yml
---
- name: 文件或目录类的过滤器
hosts: servera
tasks:
- name: sha1哈希
debug: msg="{{ '123456' | hash('sha1') }}"
- name: md5哈希
debug: msg="{{ '123456' | hash('md5') }}"
- name: 校验和(与md5一致)
debug: msg="{{ '123456' | checksum }}"
- name: sha256哈希(随机盐)
debug: msg="{{ '123456' | password_hash('sha256') }}"
- name: sha256哈希(指定盐mysalt)
debug: msg="{{ '123456' | password_hash('sha256','mysalt') }}"
- name: sha512哈希(随机盐)
debug: msg="{{ '123123' | password_hash('sha512') }}"
- name: sha512哈希(指定盐ebzL.U5cjaHe55KK)
debug: msg="{{ '123123' | password_hash('sha512','ebzL.U5cjaHe55KK') }}"
5. 加密算法应用(创建带哈希密码的用户)
- 代码:
---
- name: create user
hosts: node1
tasks:
- name: create chenyu
user:
name: chenyu
password: "{{'redhat' | password_hash('sha512')}}"
- 说明:创建用户
chenyu
,密码redhat
用 SHA512 哈希加密存储。
二、Ansible判断
通过when
关键字实现条件执行,支持变量、任务结果、路径等多维度判断,核心运算符:==
、!=
、>
、<
、>=
、<=
、and
、or
、not
、is
、in
。
(一)判断变量的tests
- 功能:用
defined
(已定义)、undefined
(未定义)、none
(已定义为空)判断变量状态。 - 代码:
Vim test.yml
---
- name: test
hosts: node1
vars:
aa: 11
cc:
tasks:
- name: debug1(aa已定义)
debug:
msg:a
when: aa is defined
- name: debug2(bb未定义)
debug:
msg: ab
when: bb is undefined
- name: debug3(cc为空)
debug:
msg: abc
when: cc is none
(二)判断执行结果的tests
- 功能:用
success
(成功)、failed
(失败)、changed
(状态变更)、skipped
(跳过)判断任务结果,需先register
注册结果。 - 代码:
Vim test2.yml
---
- name: test
hosts: node1
vars:
aa: 11
tasks:
- name: shell(aa==11时执行ls /mnt,注册结果到dd)
shell:
cmd: ls /mnt
when: aa == 11
register: dd
- name: debug1(任务成功)
debug:
msg: chenyu success
when: dd is success
- name: debug2(任务失败)
debug:
msg: chenyu failed
when: dd is failed
- name: debug3(任务变更)
debug:
msg: chenyu changed
when: dd is changed
- name: debug4(任务跳过)
debug:
msg: chenyu skip
when: dd is skip
(三)判断路径的tests
- 功能:用
file
(文件)、directory
(目录)、link
(软链接)、mount
(挂载点)、exists
(存在)判断路径状态,仅针对控制端路径。 - 代码:
vim test.yml
---
- name: test
hosts: node1
vars:
a1: /test/file1
a2: /test/
a3: /test/softlinka
a4: /test/hardlinka
a5: /boot/
tasks:
- name: debug1(a1是文件)
debug: msg=this is file when: a1 is file
- name: debug2(a2是目录)
debug: msg="this is directory" when: a2 is directory
- name: debug3(a3是软链接)
debug: msg="this is softlink" when: a3 is link
- name: debug4(a4是硬链接)
debug: msg="this is hardlink" when: a4 is link
- name: debug5(a5是挂载点)
debug: msg="this is mount directory" when: a5 is mount
- name: debug6(a1存在)
debug: msg="this is exists" when: a1 is exists
(四)判断字符串的tests
- 功能:用
lower
(字母全小写)、upper
(字母全大写)判断字符串大小写(数字不影响)。 - 代码:
vim test.yml
---
- name: test
hosts: node1
vars:
a1: abc
a2: ABC
a3: a1b
tasks:
- name: debug1(a1全小写)
debug: msg=this string is all lower when: a1 is lower
- name: debug2(a2全大写)
debug: msg=this string is all upper when: a2 is upper
- name: debug3(a3字母全小写)
debug: msg=chenyu when: a3 is lower
(五)判断数据类型的tests
- 功能:用
string
(字符串)、number
(数字)判断数据类型。 - 代码:
vim test.yml
---
- name: test
hosts: node1
vars:
a1: 1
a2: "1"
a3: a
tasks:
- name: debug1(a1是数字)
debug: msg=this is number when: a1 is number
- name: debug2(a2是字符串)
debug: msg=this is string when: a2 is string
- name: debug3(a3是字符串)
debug: msg=this is string when: a3 is string
(六)block/rescue/always:限制性块(错误处理)
- 功能:
block
执行正常任务,失败则执行rescue
,无论成功/失败都执行always
。 - 应用场景:创建逻辑卷,失败则用备用大小,最后统一格式化;卷组不存在则提示。
- 前提:node1卷组
research
为2G,node2为1G(需先通过vg.yml
创建)。- 创建卷组的
vg.yml
代码:
- 创建卷组的
Vim vg.yml
---
- name: create vg for node1
hosts: node1
tasks:
- name: create partition
parted:
device: /dev/sdb
number: 1
part_type: primary
part_start: 10MiB
part_end: 2058MiB
state: present
- name: create vg research
lvg: vg=research pvs=/
Ansible逻辑卷配置与错误处理例题整理与详细说明
一、核心例题:逻辑卷(LV)配置与错误处理实战
(一)题目完整需求
创建名为/etc/ansible/lv.yml
的Playbook,在所有受管节点执行以下任务:
- 创建符合要求的逻辑卷:
- 位于
research
卷组中 - 名称为
data
- 大小为1500MiB
- 位于
- 使用ext4文件系统格式化该逻辑卷
- 错误处理:
- 若无法创建1500MiB大小的逻辑卷,显示错误消息
Could not create logical volume of that size
,并改为创建800MiB大小 - 若
research
卷组不存在,显示错误消息Volume group does not exist
- 若无法创建1500MiB大小的逻辑卷,显示错误消息
- 不要挂载该逻辑卷
(二)前提准备:卷组(VG)创建
1. 环境准备
- 在
node1
和node2
上各添加一块硬盘(假设为/dev/sdb
) node1
的research
卷组大小为2Gnode2
的research
卷组大小为1G(用于模拟"无法创建1500MiB逻辑卷"的场景)
2. 卷组创建Playbook(vg.yml
)
---
# 为node1创建2G的research卷组
- name: create vg for node1
hosts: node1
tasks:
# 步骤1:在/dev/sdb上创建主分区(2048MiB,约2G)
- name: create partition
parted:
device: /dev/sdb # 目标硬盘
number: 1 # 分区编号
part_type: primary # 主分区
part_start: 10MiB # 分区起始位置(跳过前10MiB)
part_end: 2058MiB # 分区结束位置(10+2048=2058MiB)
state: present # 确保分区存在
# 步骤2:基于/dev/sdb1创建卷组research
- name: create vg research
lvg:
vg: research # 卷组名称
pvs: /dev/sdb1 # 使用的物理卷
# 为node2创建1G的research卷组
- name: create vg for node2
hosts: node2
tasks:
# 步骤1:在/dev/sdb上创建主分区(1024MiB,约1G)
- name: create partition for node2
parted:
device: /dev/sdb
number: 1
part_type: primary
part_start: 10MiB
part_end: 1034MiB # 10+1024=1034MiB
state: present
# 步骤2:基于/dev/sdb1创建卷组research
- name: create vg research for node2
lvg:
vg: research
pvs: /dev/sdb1
3. 执行卷组创建
ansible-playbook vg.yml
(三)逻辑卷配置Playbook(lv.yml
)实现
1. 完整代码
---
- name: create lvm (逻辑卷配置与错误处理)
hosts: node1,node2 # 在所有受管节点执行
tasks:
# 任务1:当research卷组存在时,创建逻辑卷并处理错误
- name: create lv (卷组存在时执行)
block: # 正常执行的任务块
# 子任务1:尝试创建1500MiB的逻辑卷
- name: create lvm 1500M
lvol:
vg: research # 卷组名称
lv: data # 逻辑卷名称
size: 1500M # 逻辑卷大小
rescue: # block执行失败时触发(如空间不足)
# 子任务1:输出错误消息
- name: output fail message
debug:
msg: Could not create logical volume of that size # 错误提示
# 子任务2:创建800MiB的备用逻辑卷
- name: create lvm 800M
lvol:
vg: research
lv: data
size: 800M
always: # 无论block/rescue是否成功,都执行(格式化操作)
# 子任务1:用ext4格式化逻辑卷
- name: format lvm
filesystem:
fstype: ext4 # 文件系统类型
dev: /dev/research/data # 逻辑卷设备路径
# 条件:仅当research卷组存在时,执行上述block-rescue-always
when: "'research' in ansible_facts.lvm.vgs" # 或使用"ansible_lvm.vgs"
# 任务2:当research卷组不存在时,输出错误消息
- name: search not exists (卷组不存在时执行)
debug:
msg: Volume group does not exist # 错误提示
# 条件:仅当research卷组不存在时执行
when: "'research' not in ansible_facts.lvm.vgs" # 或使用"ansible_lvm.vgs"
2. 关键语法与逻辑说明
(1)block/rescue/always
结构
block
:包含正常情况下需要执行的任务(尝试创建1500MiB逻辑卷)rescue
:当block
中的任何任务失败时触发(如node2
的卷组只有1G,无法创建1500MiB逻辑卷),执行错误处理(输出消息+创建800MiB逻辑卷)always
:无论block
成功还是rescue
触发,都会执行的任务(格式化逻辑卷,确保创建后必格式化)
(2)卷组存在性判断
- 核心条件:
"'research' in ansible_facts.lvm.vgs"
ansible_facts.lvm.vgs
是Ansible收集的facts信息,包含所有卷组名称- 当
research
在卷组列表中时,执行block
部分;否则执行"卷组不存在"的错误提示
(3)模块说明
lvol
:用于管理逻辑卷(创建、修改、删除),需指定vg
(卷组)、lv
(逻辑卷名)、size
(大小)filesystem
:用于格式化存储设备,需指定fstype
(文件系统类型)和dev
(设备路径)debug
:用于输出自定义消息(错误提示)
3. 执行结果分析
在
node1
上:research
卷组大小为2G,足够创建1500MiB逻辑卷block
中的"创建1500MiB"任务成功,rescue
不触发always
执行,格式化逻辑卷- 最终结果:1500MiB的
/dev/research/data
被创建并格式化为ext4
在
node2
上:research
卷组大小为1G(1024MiB),无法创建1500MiB逻辑卷block
中的任务失败,触发rescue
- 输出错误消息
Could not create logical volume of that size
,并创建800MiB逻辑卷 always
执行,格式化逻辑卷- 最终结果:800MiB的
/dev/research/data
被创建并格式化为ext4
在无
research
卷组的节点上:- 跳过
block
部分,执行"卷组不存在"的错误提示 - 最终结果:输出
Volume group does not exist
- 跳过
二、扩展例题:错误处理高级用法
(一)fail
模块:条件满足时中断Playbook
1. 功能说明
fail
模块用于在满足特定条件时主动中断Playbook执行,并输出自定义错误消息,常与when
配合使用。
2. 完整代码(c.yml
)
---
- name: test fail module (测试fail模块中断功能)
hosts: node1
tasks:
# 任务1:执行shell命令,输出含"error"的字符串
- name: shell
shell:
cmd: echo 'this is a string for testing--error' # 输出包含"error"的内容
register: return_value # 注册命令结果到变量return_value
# 任务2:当命令输出含"error"时,中断Playbook
- name: fail
fail:
msg: Conditions established, Interrupt running playbook # 中断提示消息
when: "'error' in return_value.stdout" # 条件:命令输出中包含"error"
# 任务3:因Playbook被中断,此任务不会执行
- name: debug
debug:
msg: I never execute, because the playbook has stopped
3. 执行结果
- 任务1执行成功,输出含"error"的字符串
- 任务2的条件满足(
'error' in return_value.stdout
为true
),执行fail
模块,Playbook中断 - 任务3不会执行
(二)failed_when
:自定义任务失败条件
1. 功能说明
failed_when
用于自定义任务的"失败条件",即使任务实际执行成功(返回码0),若满足failed_when
条件,也会被标记为失败。
2. 完整代码
---
- name: test failed_when (测试自定义失败条件)
hosts: node1
tasks:
# 任务1:正常执行的debug
- name: debug
debug:
msg: I execute normally # 正常输出
# 任务2:当命令输出含"error"时,标记为失败
- name: shell
shell:
cmd: echo 'this is a string testing--error' # 输出包含"error"的内容
register: return_value # 注册结果
failed_when: "'error' in return_value.stdout" # 自定义失败条件
# 任务3:因任务2被标记为失败,默认情况下此任务不会执行
- name: debug2
debug:
msg: chenyu
3. 执行结果
- 任务1正常执行,输出消息
- 任务2的
shell
命令实际执行成功(返回码0),但因'error' in return_value.stdout
为true
,被failed_when
标记为失败 - 任务3默认不会执行(Playbook在任务失败后中断)
(三)ignore_errors: yes
:忽略错误继续执行
1. 功能说明
ignore_errors: yes
用于忽略当前任务的错误,即使任务失败,Playbook也会继续执行后续任务。
2. 完整代码
---
- name: test ignore_errors (测试忽略错误)
hosts: node1
tasks:
# 任务1:输出主机名(正常执行)
- name: debug1
debug:
msg: "{{ ansible_fqdn }}" # 输出节点的完全限定域名
# 任务2:引用不存在的变量(会失败,但被忽略)
- name: debug2
debug:
msg: "{{ ansible_ip }}" # ansible_ip是不存在的变量,会报错
ignore_errors: yes # 忽略当前任务的错误
# 任务3:创建文件(因任务2的错误被忽略,此任务会执行)
- name: create file
file:
path: /tmp/abc # 文件路径
state: touch # 确保文件存在(不存在则创建)
3. 执行结果
- 任务1正常执行,输出主机名
- 任务2因引用不存在的变量
ansible_ip
而失败,但ignore_errors: yes
使其被忽略 - 任务3继续执行,在
/tmp
下创建abc
文件
(四)changed_when
:自定义任务状态
1. 功能说明
changed_when
用于自定义任务的"变更状态"(changed
/ok
),即使任务实际修改了系统状态,也可通过此参数强制标记为ok
,反之亦然。
2. 完整代码(强制标记为changed
)
---
- name: test changed_when (测试自定义变更状态)
hosts: node1
tasks:
# 任务1:debug模块默认不会标记为changed,通过changed_when强制标记
- name: debug1
debug:
msg: "{{ ansible_fqdn }}"
changed_when: true # 强制标记为"changed"(即使实际未变更系统)
3. 完整代码(强制标记为ok
)
---
- name: test changed_when (测试自定义变更状态)
hosts: node1
tasks:
# 任务1:ls命令默认不会标记为changed,此处显式指定
- name: shell
shell:
cmd: ls /tmp # 仅查询,不修改系统状态
changed_when: false # 强制标记为"ok"(即使实际可能有隐含变更)
4. 执行结果
changed_when: true
:任务执行后状态为changed
(绿色输出)changed_when: false
:任务执行后状态为ok
(黄色输出)
三、总结:错误处理核心方法对比
方法 | 作用 | 适用场景 | 示例代码片段 |
---|---|---|---|
block/rescue/always |
批量任务错误捕获与处理 | 复杂流程(尝试→失败处理→最终操作) | block: ... rescue: ... always: ... |
fail 模块 |
条件满足时主动中断Playbook | 关键条件不满足时需终止执行 | fail: msg="中断" when: 条件 |
failed_when |
自定义任务失败条件 | 需基于命令输出判断任务是否失败 | failed_when: "'error' in return.stdout" |
ignore_errors: yes |
忽略任务错误,继续执行后续任务 | 非关键任务失败不影响整体流程 | ignore_errors: yes |
changed_when |
自定义任务变更状态(changed /ok ) |
需精确控制任务状态显示(如审计、报告) | changed_when: true 或 changed_when: false |
通过上述方法,可实现Ansible Playbook的精细化错误控制,确保在复杂场景下的稳定性与可维护性。
for循环和if的例题
1,从 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。
使用 group.all 变量生成主机清单的实现
如果需要使用 group.all
变量(包含所有主机)来生成主机清单,我们可以通过 Ansible 的内置变量和 Jinja2 模板来实现。这种方法更灵活,能自动包含清单中的所有主机。
一、修改后的模板文件 newhosts.j2
下载模板文件 curl -0 http://ansible.example.com/materials/newhosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for ycy in groups.all %}
{{ hostvars[ycy].ansible_default_ipv4.address }} {{ hostvars[ycy].ansible_fqdn }} {{ hostvars[ycy].ansible_hostname }}
{% endfor %}
模板说明
保留本地主机条目:
- 前两行保持不变,包含 IPv4 和 IPv6 的本地主机配置
使用 group.all 遍历所有主机:
groups.all
是 Ansible 的内置变量,包含清单中所有主机for host in groups.all
循环遍历所有主机
二、适配的生成主机清单剧本 newhosts.yml
---
- name: get fact
hosts: all
- name: template inventory
hosts: test01
tasks:
- name: test1
template:
src: /home/student/ansible/newhosts.j2
dest: /etc/newhosts
三、执行与验证
- 执行剧本:
ansible-playbook /home/student/ansible/newhosts.yml
- 验证结果:
登录test01主机组中的主机,检查生成的/etc/newhosts
文件:
cat /etc/newhosts
应该能看到包含本地主机和所有节点(node1到node5)的完整主机清单,格式与要求一致。
2,编写剧本修改远程文件内容
创建剧本 /home/student/ansible/newissue.yml,满足下列要求:
1)在所有清单主机上运行,替换/etc/issue 的内容
2)对于 test01 主机组中的主机,/etc/issue 文件内容为 test01
3)对于 test02 主机组中的主机,/etc/issue 文件内容为 test02
4)对于 web 主机组中的主机,/etc/issue 文件内容为 Webserver
(一)题目需求
- 创建剧本
/home/student/ansible/newissue.yml
,在所有清单主机上运行。 - 按主机组设置
/etc/issue
内容:test01
主机组:内容为test01
test02
主机组:内容为test02
web
主机组:内容为Webserver
(二)核心技术点
group_names
变量:Ansible内置变量,存储当前主机所属的所有主机组,用于条件判断。- Jinja2
if-elif
逻辑:在copy
模块的content
参数中嵌入条件,实现基于主机组的差异化内容配置。 copy
模块:直接通过content
参数设置文件内容,无需本地文件,简化配置流程。
(三)完整实现步骤
1. 编写newissue.yml
剧本
剧本通过group_names
判断主机所属组,结合if-elif
逻辑动态设置/etc/issue
内容,确保所有主机按组匹配正确配置。
# /home/student/ansible/newissue.yml
---
- name: Configure /etc/issue based on host group
hosts: all # 在所有清单主机上执行
tasks:
- name: Set /etc/issue content by host group
copy:
content: |
{% if 'test01' in group_names %}
test01
{% elif 'test02' in group_names %}
test02
{% elif 'web' in group_names %}
Webserver
{% endif %}
dest: /etc/issue # 目标文件路径
- 剧本逻辑说明:
hosts: all
:覆盖所有清单主机,无需分组执行,简化操作。group_names
:无需收集facts即可使用,存储当前主机的组列表(如test01
主机的group_names
为['test01']
)。if-elif
条件:- 优先判断是否属于
test01
组,是则内容为test01
; - 否则判断是否属于
test02
组,是则内容为test02
; - 否则判断是否属于
web
组,是则内容为Webserver
; - 若主机不属于上述任何组,
/etc/issue
将为空(无else
分支,不设置默认内容)。
- 优先判断是否属于
copy
模块的content
参数:直接嵌入多行文本与Jinja2逻辑,无需额外创建本地文件,高效便捷。
(四)执行与验证
1. 执行剧本
ansible-playbook /home/student/ansible/newissue.yml
2. 分主机组验证
test01
主机组:[student@master ansible]$ ansible node1 -m shell -a "cat /etc/issue" node1 | CHANGED | rc=0 >> test01
test02
主机组:[student@master ansible]$ ansible test02 -m shell -a "cat /etc/issue" node2 | CHANGED | rc=0 >> test02
web
主机组:[student@master ansible]$ ansible web -m shell -a "cat /etc/issue" node4 | CHANGED | rc=0 >> webserver node3 | CHANGED | rc=0 >> webserver
三、关键技术总结
技术点 | 作用 | 适用场景 | 示例代码片段 |
---|---|---|---|
groups.all |
遍历清单中所有主机 | 批量生成包含所有主机的配置(如主机清单) | {% for host in groups.all %}...{% endfor %} |
hostvars |
获取指定主机的facts信息(IP、主机名) | 动态获取主机属性用于配置生成 | hostvars[host]['ansible_fqdn'] |
group_names |
查看当前主机所属的所有组 | 基于主机组的差异化配置(如文件内容) | {% if 'test01' in group_names %} |
Jinja2for 循环 |
批量生成重复结构的配置 | 主机清单、批量用户创建等 | {% for host in groups.all %}...{% endfor %} |
Jinja2if-elif 逻辑 |
基于条件动态设置内容 | 按组/按主机属性差异化配置 | {% if 'test01' in group_names %}test01{% endif %} |
template 模块 |
渲染包含变量/逻辑的模板文件 | 复杂配置文件生成(如主机清单、Nginx配置) | src: newhosts.j2 dest: /etc/newhosts |
copy 模块content |
直接设置文件内容,无需本地文件 | 简单文本文件配置(如/etc/issue、标语文件) | content: "test01" dest: /etc/issue |
四、注意事项
groups.all
使用限制:若清单中包含非node1-node5
的主机,需在模板中添加条件过滤(如{% if 'node' in host %}...{% endif %}
),避免生成多余条目。gather_facts
开关:题目一中必须开启gather_facts: yes
,否则hostvars
无法获取主机名;题目二无需开启,减少执行时间。- 权限与归属:
/etc/newhosts
与/etc/issue
均为系统配置文件,需设置root
所有权与0644
权限,避免权限不足导致解析失败。 - 多组归属优先级:若主机同时属于多个组(如某主机既在
test01
也在web
),if-elif
会优先匹配第一个条件(即test01
),需确保主机组划分唯一。