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'