Ansible循环与判断实战指南

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

Ansible 循环与判断使用指南

循环

1. with_items 迭代列表

当需要在 Linux 中依次安装多个软件包时,可使用 with_items 进行迭代。

示例:安装 httpd、samba、samba-client 软件包

---
- name: install packages
  hosts: node1
  tasks:
    - name: yum_repo1
      yum_repository:
        file: server
        name: baseos
        description: rhel8
        baseurl: file:///mnt/BaseOS
        enabled: yes
        gpgcheck: no

    - name: yum_repo2
      yum_repository:
        file: server
        name: appstream
        description: appstream
        baseurl: file:///mnt/AppStream
        enabled: yes
        gpgcheck: no

    - name: mount cdrom
      mount:
        src: /dev/cdrom
        path: /mnt
        fstype: iso9660
        state: mounted

    - name: install pks
      yum:
        name: "{{ item }}"
        state: present
      with_items:
        - httpd
        - samba
        - samba-client

2. with_dict 迭代字典

item.key 对应字典的键,item.value 对应字典的值。

示例:迭代字典并输出键值对

---
- name: test
  hosts: node1
  tasks:
    - name: debug
      debug:
        msg: "{{ item.key }} & {{ item.value }}"
      with_dict:
        address: 1
        netmask: 2
        gateway: 3

3. with_fileglob 迭代文件

适用于拷贝多个文件到受控主机的场景。

示例:拷贝 /tmp/ 目录下的 .sh 和 .py 文件

---
- name: test
  hosts: node1
  tasks:
    - name: cp file
      copy:
        src: "{{ item }}"
        dest: /tmp/
      with_fileglob:
        - /tmp/*.sh
        - /tmp/*.py

4. with_lines 迭代行

with_lines 可将命令的输出结果按行迭代。

示例:查找 /etc/ansible 目录下的所有 .yml 文件并拷贝

---
- name: test
  hosts: node1
  tasks:
    - name: cp file
      copy:
        src: "{{ item }}"
        dest: /tmp/
      with_lines:
        - find /etc/ansible -name "*.yml"

5. with_nested 嵌套迭代

用于多重循环场景,例如组合多个列表中的元素。

示例:嵌套迭代输出组合值

---
- name: test
  hosts: node1
  tasks:
    - name: debug
      debug:
        msg: "{{ item[0] }} & {{ item[1] }}"
      with_nested:
        - [a, b]
        - [1, 2, 3]

6. with_sequence 序列迭代

生成数字序列,支持指定起始值、结束值和步长。

示例:生成从 1 到 5 的序列

---
- name: test
  hosts: node1
  tasks:
    - name: debug
      debug:
        msg: "{{ item }}"
      with_sequence:
        start=1
        end=5
        stride=1

注意:在较新版本的 Ansible 中,建议使用 loop 配合 range 过滤器替代 with_sequence

7. with_random_choice 随机选择

从列表中随机选择一个值。

示例:随机输出列表中的一个值

---
- name: test
  hosts: node1
  tasks:
    - name: debug
      debug:
        msg: "{{ item }}"
      with_random_choice:
        - 1
        - 2
        - a
        - b
        - c

Loop 循环(现代写法)

现在更推荐使用 loop 替代传统的 with_*,通常结合过滤器使用。


过滤器(Filters)

字符串过滤器

---
- name: test
  hosts: node1
  vars:
    testvar: "abc123ABC 666"
    testvar1: " abc "
  tasks:
    - name: debug1
      debug:
        msg: "{{ testvar | upper }}"  # 转换为大写

    - name: debug2
      debug:
        msg: "{{ testvar | lower }}"  # 转换为小写

    - name: debug3
      debug:
        msg: "{{ testvar1 | trim }}"  # 去除首尾空格

    - name: debug4
      debug:
        msg: "{{ testvar | length }}" # 计算字符串长度

创建用户并使用哈希密码

使用加密算法对字符串进行hash加密

创建一个用户ydh,并且设置密码为redhat,密码采用SHA512哈希格式

---
- name: create user
  hosts: node1
  tasks:
    - name: create ydh
      user:
        name: ydh
        password: "{{ 'redhat' | password_hash('sha512') }}"

补充字符串过滤器示例

---
- name: 过滤器
  hosts: servera
  vars:
    testvar: "abc123ABC 666"
    testvar1: "  abc  "
    testvar2: "123456789"
    testvar3: "1a2b,@#$%^&"
  tasks:
    - name: 将字符串转换成纯大写
      debug:
        #将字符串转换成纯大写
        msg: "{{ testvar | upper }}"
        
    - name: 将字符串转换成纯小写
      debug:
        #将字符串转换成纯小写
        msg: "{{ testvar | lower }}"
        
    - name: 将字符串首字母大写,之后的所有字母纯小写
      debug:
        #将字符串首字母大写,之后的所有字母纯小写
        msg: "{{ testvar | capitalize }}" 
        
    - name: 返回字符串的第一个字符
      debug:
        #返回字符串的第一个字符
        msg: "{{ testvar | first }}" 
        
    - name: 返回字符串的最后一个字符   
      debug:
        #返回字符串的最后一个字符
        msg: "{{ testvar | last }}"
        
    - name: 将字符串开头和结尾的空格去除
      debug:
        #将字符串开头和结尾的空格去除
        msg: "{{ testvar1 | trim }}"
        
    - name: 将字符串放在中间,并且设置字符串的长度为30,字符串两边用空格补齐30位长
      debug:
        #将字符串放在中间,并且设置字符串的长度为30,字符串两边用空格补齐30位长
        msg: "{{ testvar1 | center(width=30) }}"
        
    - name: 返回字符串长度,length与count等效,可以写为count
      debug:
        #返回字符串长度,length与count等效,可以写为count
        msg: "{{ testvar2 | length }}"
        
    - name: 将字符串转换成列表,每个字符作为一个元素
      debug:
        #将字符串转换成列表,每个字符作为一个元素
        msg: "{{ testvar3 | list }}"
        
    - name: 将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序
      debug:
        #将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序
        #shuffle的字面意思为洗牌
        msg: "{{ testvar3 | shuffle }}"

数字操作过滤器

# 将字符串转换为整数
msg: "{{ '8' | int }}"
 msg: "{{ 8+('8' | int) }}"  
 #将对应的值转换成int类型
 #ansible中,字符串和整形不能直接计算,比如{{ 8+'8' }}会报错
 #所以,我们可以把一个值为数字的字符串转换成整形后再做计算

# 转换失败时返回默认值
msg: "{{ 'a' | int(default=6) }}"  
#将对应的值转换成int类型,如果无法转换,默认返回0
#使用int(default=6)或者int(6)时,如果无法转换则返回指定值6

# 转换为浮点数
msg: "{{ '8' | float }}" #将对应的值转换成浮点型,如果无法转换,默认返回'0.0'
 msg: "{{ 'a' | float(8.88) }}" #当对应的值无法被转换成浮点型时,则返回指定值’8.88‘
 
# 获取绝对值
msg: "{{ testvar4 | abs }}" #获取对应数值的绝对值

# 四舍五入
msg: "{{ 12.5 | round }}" # 四舍五入
msg: "{{ 3.1415926 | round(5) }}" #取小数点后五位  

# 生成随机数
msg: "{{ 100 | random }}" #从0到100中随机返回一个随机数
msg: "{{ 10 | random(start=5) }}" #从5到10中随机返回一个随机数
msg: "{{ 15 | random(start=5, step=3) }}"  
#从5到15中随机返回一个随机数,步长为3
#步长为3的意思是返回的随机数只有可能是5、8、11、14中的一个
msg: "{{ 15 | random(step=5) }}" #从0到15中随机返回一个随机数,这个随机数是5的倍数

文件与目录类过滤器

# 使用不同算法计算哈希值
msg: "{{ '123456' | hash('sha1') }}" #使用sha1算法对字符串进行哈希
msg: "{{ '123456' | hash('md5') }}" #使用md5算法对字符串进行哈希
msg: "{{ '123456' | checksum }}"  #获取到字符串的校验和,与md5哈希值一致
msg: "{{ '123456' | password_hash('sha256') }}" #使用sha256算法对字符串进行哈希,哈希过程中会生成随机"盐",以便无法直接对比出原值
msg: "{{ '123456' | password_hash('sha256', 'mysalt') }}" #使用sha256算法对字符串进行哈希,并使用指定的字符串作为"盐"
msg: "{{ '123123' | password_hash('sha512') }}" #使使用sha512算法对字符串进行哈希,哈希过程中会生成随机"盐",以便无法直接对比出原值
msg: "{{ '123123' | password_hash('sha512','ebzL.U5cjaHe55KK') }}" #使用sha512算法对字符串进行哈希,并使用指定的字符串作为"盐"

Ansible 条件判断(When)

基本判断运算符


`==`, `!=`, `>`, `<`, `>=`, `<=`, `and`, `or`, `not`, `is`, `in`

每次执行完一个任务,不管成功与失败,都会将执行的结果进行注册,可以使用这个注册的变量来when

变量判断 Tests

  • defined:变量已定义返回真
  • undefined:变量未定义返回真
  • none:变量值为空返回真

示例:

---
- name: test
  hosts: node1
  vars:
    aa: 11
    cc:
  tasks:
    - name: create debug1
      debug:
        msg: a
      when: aa is defined

    - name: create debug2
      debug:
        msg: ab
      when: bb is undefined

    - name: create debug3
      debug:
        msg: abc
      when: cc is none

任务执行结果判断 Tests

  • success/successed:通过任务的返回信息判断执行状态,任务执行成功返回真
  • failure/failed:通过任务的返回信息判断执行状态,任务执行失败返回真
  • change/changed:通过任务的返回信息判断执行状态,任务状态为 changed 返回真
  • skip/skipped:通过任务的返回信息判断执行状态,任务被跳过返回真

路径判断 Tests

注意:这些判断针对的是 Ansible 控制机上的路径,不是目标主机。

  • file:判断路径是否是一个文件
  • directory:判断路径是否是一个目录
  • link:判断路径是否是一个软连接
  • mount:判断路径是否是一个挂载点
  • exists:判断路径是否存在

字符串判断 Tests

  • lower:判断包含字母的字符串中的字母是否纯小写
  • upper:判断包含字母的字符串中的字母是否纯大写

其他 Tests

  • string:判断对象是否是一个字符串
  • number:判断对象是否一个数字

错误处理与流程控制

block/rescue/always

用于异常处理:先执行 block,失败则执行 rescue,最后执行 always

无论是block还是rescue执行失败还是成功,在最后都执行always

例题:

创建一个名为/etc/ansible/lv.yml 的playbook,它将在所有受管节点上运行以执行下列任务:

​ 创建符合以下要求的逻辑卷:

​ 逻辑卷创建在research卷组中

​ 逻辑卷名称为data

​ 逻辑卷大小为1500MiB

​ 使用ext4文件系统格式化逻辑卷

​ 如果无法创建请求的逻辑卷大小,应显示错误消息

​ Could not create logical volume of that size,并且应改为使用大小 800MiB。

​ 如果卷组research 不存在 ,应显示错误消息

​ Volume group does not exist。

​ 不要以任何方式挂载逻辑卷

前提:在node1、node2上添加一块硬盘,然后新建卷组

Node1的卷组大小为2G 卷组名为research

Node2的卷组大小为1G 卷组名为research

示例:创建逻辑卷

---
- name: create vg for node1
  hosts: node1
  tasks:
    - name: create partition
      parted:
        device: /dev/vdb
        number: 1
        part_type: primary
        part_start: 10MiB
        part_end: 2058MiB
        state: present
 
    - name: create vg research
      lvg:
        vg: research
        pvs: /dev/vdb1
 
- name: create vg for node2
  hosts: node2
  tasks:
    - name: create partition for node2
      parted:
        device: /dev/vdb
        number: 1
        part_type: primary
        part_start: 10MiB
        part_end: 1034MiB
        state: present
 
    - name: create vg research for node2
      lvg:
        vg: research
        pvs: /dev/vdb1

新建lv.yml,满足题目需求

---
- name: create lvm
  hosts: node1,node2
  tasks:
    - name: create lv
      block:
        - name: create lvm 1500M
          lvol:
            vg: research
            lv: data
            size: 1500M
 
      rescue:
        - name: output fail message
          debug:
            msg: Could not create logical volume of that size
 
        - name: create lvm 800M
          lvol:
            vg: research
            lv: data
            size: 800M
      always:
        - name: format lvm
          filesystem:
            fstype: ext4
            dev: /dev/research/data
      when: "'research' in ansible_facts.lvm.vgs"
      #也可以用when: "'research' in ansible_lvm.vgs"
 
    - name: serach not exists
      debug:
        msg: Volume group does not exist
      when: "'research' not in ansible_facts.lvm.vgs"
      #也可以用when: "'research'  not in ansible_lvm.vgs"

fail 模块

用于在满足条件时中断 playbook 执行。

但我们一般是不会无故中断,除非在满足条件的情况下可以中断,经常和when一起用

示例:

---
- name: test
  hosts: node1
  tasks:
    - name: shell
      shell:
        cmd: echo 'this is a string for testing--error'
      register: return_value

    - name: fail
      fail:
        msg: Conditions established, Interrupt running playbook
      when: "'error' in return_value.stdout"

    - name: debug
      debug:
        msg: I never execute, because the playbook has stopped

failed_when

---
- name: test
  hosts: node1
  tasks:
    - name: debug
      debug:
        msg: I execute normally
 
    - name: shell
      shell:
        cmd: echo 'this is a string testing--error'
      register: return_value
      failed_when: "'error' in return_value.stdout"
 
    - name: debug2
      debug:
        msg: chenyu

直接指定任务失败的条件。

ignore_errors

---
- name: test
  hosts: node1
  tasks:
    - name: debug1
      debug:
        msg: "{{ansible_fqdn}}"
 
    - name: debug2
      debug:
        msg: "{{ansible_ip}}"
      ignore_errors: yes
 
    - name: create file
      file:
        path: /tmp/abc
        state: touch

忽略任务错误继续执行。

changed_when

---
- name: test
  hosts: node1
  tasks:
    - name: debug1
      debug:
        msg: "{{ansible_fqdn}}"
      changed_when: true
 
 
#或者可以让任务执行状态显示失败
---
- name: test
  hosts: node1
  tasks:
    - name: shell
      shell:
        cmd: ls /tmp
      changed_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,它将使用上述模板在 test01 主机组的主机上
生成文件/etc/newhosts。

#下载http://ansible.example.com/materials/newhosts.j2
curl -O http://ansible.example.com/materials/newhosts.j2

vim newhosts.yml
---
- name: get fact
  hosts: all
- name: test1
  hosts: node1
  tasks: 
  	- name: cp file1
  	  template:
  	    src: /home/student/ansible/newhosts.j2
  	    dest: /etc/newhosts

vim /home/student/ansible/newhosts.j2
{% for ydh in groups.all %}
{{ hostvars[ydh].ansible_default_ipv4.address }} 
{{ hostvars[ydh].ansible_fqdn }}
{{ hostvars[ydh].ansible_hostname }}
{% endfor %}

编写剧本修改远程文件内容
创建剧本 /home/student/ansible/newissue.yml,满足下列要求:
1)在所有清单主机上运行,替换/etc/issue 的内容
2)对于 test01 主机组中的主机,/etc/issue 文件内容为 test01
3)对于 test02 主机组中的主机,/etc/issue 文件内容为 test02
4)对于 web 主机组中的主机,/etc/issue 文件内容为 Webserver

vim newissue.yml

---
- name: replace file
  hosts: all
  tasks: 
    - name: test11
      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'

网站公告

今日签到

点亮在社区的每一天
去签到