ansible循环+判断(with,loop,when,if,for)

发布于:2025-09-04 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、文档核心定位

本文档聚焦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
执行与验证
  1. 执行命令:ansible-playbook b.yml
  2. 验证方式:在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&1a&2a&3b&1b&2b&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 round3.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关键字实现条件执行,支持变量、任务结果、路径等多维度判断,核心运算符:==!=><>=<=andornotisin

(一)判断变量的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创建)。
    1. 创建卷组的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,在所有受管节点执行以下任务:

  1. 创建符合要求的逻辑卷:
    • 位于research卷组中
    • 名称为data
    • 大小为1500MiB
  2. 使用ext4文件系统格式化该逻辑卷
  3. 错误处理:
    • 若无法创建1500MiB大小的逻辑卷,显示错误消息Could not create logical volume of that size,并改为创建800MiB大小
    • research卷组不存在,显示错误消息Volume group does not exist
  4. 不要挂载该逻辑卷

(二)前提准备:卷组(VG)创建

1. 环境准备
  • node1node2上各添加一块硬盘(假设为/dev/sdb
  • node1research卷组大小为2G
  • node2research卷组大小为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.stdouttrue),执行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.stdouttrue,被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: truechanged_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 %}

模板说明

  1. 保留本地主机条目

    • 前两行保持不变,包含 IPv4 和 IPv6 的本地主机配置
  2. 使用 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

三、执行与验证

  1. 执行剧本:
ansible-playbook /home/student/ansible/newhosts.yml
  1. 验证结果:
    登录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

(一)题目需求

  1. 创建剧本/home/student/ansible/newissue.yml,在所有清单主机上运行。
  2. 按主机组设置/etc/issue内容:
    • test01主机组:内容为test01
    • test02主机组:内容为test02
    • web主机组:内容为Webserver

(二)核心技术点

  • group_names变量:Ansible内置变量,存储当前主机所属的所有主机组,用于条件判断。
  • Jinja2if-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条件:
      1. 优先判断是否属于test01组,是则内容为test01
      2. 否则判断是否属于test02组,是则内容为test02
      3. 否则判断是否属于web组,是则内容为Webserver
      4. 若主机不属于上述任何组,/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

四、注意事项

  1. groups.all使用限制:若清单中包含非node1-node5的主机,需在模板中添加条件过滤(如{% if 'node' in host %}...{% endif %}),避免生成多余条目。
  2. gather_facts开关:题目一中必须开启gather_facts: yes,否则hostvars无法获取主机名;题目二无需开启,减少执行时间。
  3. 权限与归属/etc/newhosts/etc/issue均为系统配置文件,需设置root所有权与0644权限,避免权限不足导致解析失败。
  4. 多组归属优先级:若主机同时属于多个组(如某主机既在test01也在web),if-elif会优先匹配第一个条件(即test01),需确保主机组划分唯一。