ansbile实战应用系列教程10:构造条件语句和循环
循环的任务迭代
Ansible支持使用循环在一组项目上迭代任务的几种不同方法。循环可以使用列表中的每个项、列表中每个文件的内容、生成的数字序列或使用更复杂的结构来重复任务。
使用循环使管理员不必编写使用同一模块的多个任务。例如,不需要编写5个任务来确保5个用户存在,而可以编写一个任务来遍历5个用户的列表,以确保它们都存在。
Simple Loops
简单的循环在项目列表上迭代任务。with_items键被添加到任务中,并将任务应该迭代的项列表作为值。循环变量项保存此迭代使用的当前值。
考虑以下代码片段,它使用service模块两次,以确保两个网络服务运行:
- name: postfix is running
service:
name: postfix
state: started
- name: dovecot is running
service:
name: dovecot
state: started
可以将这两个任务重写为使用简单循环,这样只需一个任务就可以确保两个服务都在运行。
- name: postfix and dovecot are running
service:
name: "{{ item }}"
state: started
with_items:
- postfix
- dovecot
with_items使用的列表可以由一个变量提供。在下面的示例中,变量mail_services包含需要运行的服务列表。
vars:
mail_services:
- postfix
- dovecot
tasks:
- name: postfix and dovecot are running
service:
name: "{{ item }}"
state: started
with_items: "{{ mail_services }}"
Simple Loops over Lists of Hash/Dictionaries
with_items列表不需要是简单值的列表。在下面的示例中,列表中的每个项实际上是一个散列/字典。示例中的每个哈希/字典都有两个键,name和groups,当前项循环变量中每个键的值可以使用item.name和item.groups变量。
示例:
---
- name: add several users
hosts: servera
tasks:
- name: Users exist and are in the correct groups
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
with_items:
- { name: 'jane', groups: 'wheel' }
- { name: 'joe', groups: 'root' }
确保jane是wheel组成员;joe是root组成员。
嵌套循环with_nested
with_nested键用于嵌套循环,循环在循环中运行。它接受一个包含两个或多个列表的列表。例如,给定一个包含两个列表的列表,该任务将迭代第一个列表中的每个项,同时迭代第二个列表中的每个项。
为了更好地说明这一点,请看下面的剧本片段。mysql_user模块使用嵌套循环,将第一个列表中的所有用户的所有MySQL特权授予第二个列表中命名的所有数据库的所有表。
示例:
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: All DB users have privileges on all databases
mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
password: redhat
with_nested:
- [ 'joe', 'jane' ]
- [ 'clientdb', 'employeedb', 'providerdb' ]
前面的示例迭代6次。它将用户joe的特权一次添加到三个数据库中的每个数据库,然后对用户jane执行相同的操作。
with_nested列表中的列表项可以在变量中定义。前面的例子可以重写使用变量来包含数据库用户和数据库的列表::
- name: test loop
hosts: all
gather_facts: no
vars:
db_users:
- joe
- jane
databases:
- clientdb
- employeedb
- providerdb
tasks:
- name: All DB users have privileges on all databases
mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
其他常见循环指令
http://www.ansible.com.cn/docs/playbooks_loops.html#id20
下表显示了Ansible支持的其他循环类型。
Loop keyword | Description |
---|---|
with_file | 获取控制节点文件名列表,在序列中items设置为每个文件的内容 |
with_fileglob | 接受一个文件名通配式模式,items将按顺序非递归地设置为与该模式匹配的控制节点上目录中的每个文件。 |
with_sequence | 以递增的数字顺序生成项目序列。可以接受具有十进制、八进制或十六进制整数值的start和end参数。 |
with_random_choice | 获取一个列表,随机设置一些值。 |
关键字 with_items
---
- name: add several users
hosts: servera
tasks:
- name: add user
user:
name: "{{ item }}"
state: present
groups: "wheel"
with_items:
- testuser1
- testuser2
改写方法一:
---
- name: add several users
hosts: servera
tasks:
- name: add user
user:
name: "{{ item }}"
state: present
groups: "wheel"
with_items: [ testuser1, testuser2 ]
改写方法二:
---
- name: add several users
hosts: servera
tasks:
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
with_items:
- [testuser1,testuser2]
改写方法三:
---
- name: add several users
hosts: servera
tasks:
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
vars:
users:
- testuser1
- testuser2
with_items: "{{ users }}"
对并行数据集使用循环with_together
假设你通过某种方式加载了以下变量数据:
—
alpha: [ ‘a’, ‘b’, ‘c’, ‘d’ ]
numbers: [ 1,2, 3, 4 ]
如果你想得到’(a, 1)’和’(b, 2)’之类的集合.可以使用’with_together’:
示例:
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_together
debug:
msg: "{{ item }}"
with_together:
- [ 1,2,3 ]
- [ a,b,c ]
- [ A,B,C ]
输出结果是:
ok: [servera.lab.example.com] => (item=[1, u'a', u'A']) => {
"item": [
1,
"a",
"A"
],
"msg": [
1,
"a",
"A"
]
}
ok: [servera.lab.example.com] => (item=[2, u'b', u'B']) => {
"item": [
2,
"b",
"B"
],
"msg": [
2,
"b",
"B"
]
}
ok: [servera.lab.example.com] => (item=[3, u'c', u'C']) => {
"item": [
3,
"c",
"C"
],
"msg": [
3,
"c",
"C"
]
}
with_list
示例:
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: debug write_list
debug:
msg: "{{ item }}"
with_list:
- [ 1,2,3 ]
- [ a,b ]
只执行两次,每个条目[ 1,2,3 ],[ a,b ]作为一个整体。
with_list 的执行结果:
ok: [servera] => (item=[u'a', u'b']) => {
"item": [
"a",
"b"
],
"msg": [
"a",
"b"
]
}
ok: [servera] => (item=[1, 2, 3]) => {
"item": [
1,
2,
3
],
"msg": [
1,
2,
3
]
}
with_list执行的结果 未展开,还是在一块。
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: debug write_list
debug:
msg: "{{ item }}"
with_items:
- [ 1,2,3 ]
- [ a,b ]
with_items的执行结果:
ok: [servera] => (item=b) => {
"item": "b",
"msg": "b"
}
ok: [servera] => (item=a) => {
"item": "a",
"msg": "a"
}
ok: [servera] => (item=3) => {
"item": 3,
"msg": 3
}
ok: [servera] => (item=2) => {
"item": 2,
"msg": 2
}
ok: [servera] => (item=1) => {
"item": 1,
"msg": 1
}
with_items 、 with_flattened执行的结果item 展开了。
使用索引循环列表with_indexed_items
如果你想循环一个列表,同时得到一个数字索引来标明你当前处于列表什么位置,
输出项目的时候,同时输出项目对应索引号。
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_indexed_items
debug:
msg: "{{ item }}"
with_indexed_items:
- [ a,b,c ]
输出结果:0,a 1,b 2,c
对整数序列使用循环with_sequence
with_sequence 可以以升序数字顺序生成一组序列.你可以指定起始值、终止值,以及一个可选的步长值.
指定参数时也可以使用key=value这种键值对的方式.如果采用这种方式,’format’是一个可打印的字符串.
数字值可以被指定为10进制,16进制(0x3f8)或者八进制(0600).负数则不受支持.
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_sequence
debug:
msg: "{{ item }}"
with_sequence: start=1 end=5 stride=1
以上效果等同
with_sequence:
start=1
end=5
stride=1
也等同:
with_sequence: count=5
其他写法参考:
1、with_sequence:start=2 end=10 stride=3
2、with_sequence:start=6 end=2 stride=2
[student@workstation loop]$ ansible-playbookwithsequence.yml
PLAY [test loop] *******************************************
TASK [test with_sequence] **************************************
fatal: [servera]: FAILED! => {"failed":true, "msg": "to count backwards make stride negative"}
to retry, use: --limit @/home/student/loop/withsequence.retry
PLAY RECAP*********************************************
servera : ok=0 changed=0 unreachable=0 failed=1
3、with_sequence:start=2 end=6 stride=2 format="number is %0.2f"
保留两位浮点小数。
随机选择 with_random_choice
random_choice’功能可以用来随机获取一些值.它并不是负载均衡器(已经有相关的模块了).它有时可以用作一个简化版的负载均衡器,比如作为条件判断:
提供的字符串中的其中一个会被随机选中.
还有一个基本的场景,该功能可用于在一个可预测的自动化环境中添加混乱和兴奋点.
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_random_choice
debug:
msg: "{{ item }}"
with_random_choice:
- 1
- 2
- 3
- 4
- 5
从列表中随机返回一个。
对哈希表使用循环with_dict
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_dict
debug:
msg: "{{ item }}"
with_dict: "{{ users }}"
vars:
users:
alice: female
bob: male
输出结果会自动将姓名放入key中,性别放入value中。
执行结果:
ok: [servera.lab.example.com] => (item={'key': u'bob', 'value': u'male'}) => {
"item": {
"key": "bob",
"value": "male"
},
"msg": {
"key": "bob",
"value": "male"
}
}
ok: [servera.lab.example.com] => (item={'key': u'alice', 'value': u'female'}) => {
"item": {
"key": "alice",
"value": "female"
},
"msg": {
"key": "alice",
"value": "female"
}
}
with_file
示例:打印文件列表中文件内容
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with file
debug:
msg: "{{ item }}"
with_file:
- /etc/fstab
- /etc/hosts
对文件列表使用循环with_fileglob
with_fileglob 可以以非递归的方式来模式匹配单个目录中的文件.
当在role中对 with_fileglob 使用相对路径时, Ansible会把路径映射到roles/<rolename>/files
目录.
示例:打印目录中匹配到的文件名(不包含目录下子目录中文件)
---
- name: test loop
hosts: all
gather_facts: no
tasks:
- name: test with_fileglob
debug:
msg: "{{ item }}"
with_fileglob:
- /etc/host*
执行结果:
ok: [servera.lab.example.com] => (item=/etc/hosts) => {
"item": "/etc/hosts",
"msg": "/etc/hosts"
}
ok: [servera.lab.example.com] => (item=/etc/host.conf) => {
"item": "/etc/host.conf",
"msg": "/etc/host.conf"
}
ok: [servera.lab.example.com] => (item=/etc/hosts.allow) => {
"item": "/etc/hosts.allow",
"msg": "/etc/hosts.allow"
}
ok: [servera.lab.example.com] => (item=/etc/hosts.deny) => {
"item": "/etc/hosts.deny",
"msg": "/etc/hosts.deny"
}
ok: [servera.lab.example.com] => (item=/etc/hostname) => {
"item": "/etc/hostname",
"msg": "/etc/hostname"
}
Do-Until Loops
- shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
满足util条件 退出循环。
上面的例子递归运行shell模块,直到模块结果中的stdout输出中包含”all systems go”字符串,或者该任务按照10秒的延迟重试超过5次.”retries”和”delay”的默认值分别是3和5.
该任务返回最后一个任务返回的结果.单次重试的结果可以使用-vv选项来查看.被注册的变量会有一个新的属性’attempts’,值为该任务重试的次数.
Running Tasks Conditionally
当满足某些条件时,Ansible可以使用条件语句来执行tasks或play。例如,条件可以用于在Ansible安装或配置服务之前确定托管主机上的可用内存。
条件允许管理员区分托管主机,并根据主机满足的条件为它们分配功能角色。playbook变量、registered变量和ansible facts都可以用条件语句进行测试。可以使用字符串比较、数学运算符和布尔值等操作符。
下面的例子说明了Ansible条件句的一些用法。
l 可以在变量(例如min_memory)中定义硬限制,并与托管主机上的可用内存进行比较。
l 命令的输出可以被Ansible捕获和评估,以确定任务是否完成,然后再采取进一步的行动。例如,如果一个程序失败,则需要跳过一批。
l Ansible facts可用于确定托管主机网络配置,并决定发送哪个模板文件(例如,网络绑定或集群)。
l 可以评估cpu的数量,以确定如何正确地调优web服务器。
l 可以将注册的变量与定义的变量进行比较,以检查服务更改。例如,这可以用于验证文件的MD5校验和。
when条件判断
when语句用于有条件地运行任务。它将要测试的条件作为值。如果条件满足,则运行任务。如果不满足条件,则跳过该任务。
可以测试的最简单的条件之一是布尔变量是真还是假。下面例子中的when语句只会在run_my_task为真时运行该任务:
---
- name: test
hosts: all
gather_facts: no
vars:
run_my_task: true
tasks:
- name: test with_fileglob
debug:
msg:
"Hello run my task"
when: run_my_task
只有run_my_task值为true、yes、1才执行。
---
- name: test
hosts: all
gather_facts: no
vars:
run_my_task: no
tasks:
- name: test with_fileglob
debug:
msg:
"Hello run my task"
when: run_my_task
只有run_my_task值为false、no、0不执行。要boolean值。
下一个示例稍微复杂一些,测试my_service变量是否有值。如果是这样,就使用my_service的值作为要安装的包的名称。如果没有定义my_ service变量,则跳过该任务而不会出现错误。
my_service变量定义的情况下,才会执行task。
---
- name: test
hosts: all
gather_facts: no
vars:
my_service: httpd
tasks:
- name: "{{ my_service }} package is installed"
yum:
name: "{{ my_service }}"
when: my_service is defined
下表显示了管理员在使用条件语句时可以使用的一些操作符:
Operator | Example |
---|---|
Equal(value is a string) | ansible_mechine == “x86_64” |
Equal(value is numeric) | max_memroy == 512 |
Less than | min_memroy < 128 |
Greater than | min_memroy > 128 |
Less than or equal to | min_memroy <= 128 |
Greater than or equal to | min_memroy >= 128 |
Not equal to | min_memroy != 128 |
Variable exists | min_memory is defined |
Variable does not exists | min_memory is not defined |
Variable is set to 1 True or yes | available_memroy |
Variable is set to 0 False or no | not available_memroy |
First variable’s value is preset as a value in second variable’s list | my_special_user in superusers |
上表中的最后一个条目一开始可能有点令人困惑。在该条目的示例中,my_special_user是一个具有一些值的变量。变量superusers是一个有一个值列表的变量。如果my_special_user的值在超级用户列表中,则条件传递并运行任务。
下面的示例将使用这种类型的条件。给定变量设置和示例中显示的条件,任务将运行:
---
- name: test
hosts: all
gather_facts: no
vars:
my_special_user: devops
superusers:
- root
- devops
- toor
tasks:
- name: Task run if my_special_user is in superusers
user:
name: "{{ my_special_user }}"
groups: wheel
append: yes
when: my_special_user in superusers
下面是另一个使用相同条件句的例子。它使用了Ansible自动设置的两个magic variables。只有在托管主机的inventory_hostname变量(包含来自inventory文件的托管主机的名称)的值作为主机组数据库的成员被列出时,该任务才会运行。
- name: Create the database admin
user:
name: dbadmin
when: inventory_hostname in groups["databases"]
说明:
inventory_hostname:代表被管理节点在inventory中主机名。
groups[“databases”]:代表inventory中databases主机组。
多条件判断
一个when语句可用于计算多个值。为此,条件语句可以结合and和or关键字,或者用括号分组。
下面的代码片段展示了一些如何表示多个条件的示例。
对于and操作,两个条件必须为真,才能满足整个条件语句。例如,如果安装的内核是指定的版本,并且inventory_hostname在staging组中,则满足以下条件:
ansible_kernel == 3.10.0-327.e17.x86_64 **and** inventory_hostname in groups['[staging]()']
如果一个条件语句在其中一个条件为真时应该被满足,那么就应该使用or语句。例如,如果机器运行Red Hat Enterprise Linux或Fedora,则满足以下条件:
ansible_distribution =="RedHat" or ansible_distribution == "Fedora"
更复杂的条件语句可以通过使用括号对条件进行分组来清楚地表达,以确保正确地解释它们。例如,如果机器运行RedHatEnterpriseLinux 7或Fedora 23,则满足以下条件语句:
(ansible_distribution == "RedHat" andansible_distribution_major_version == 7) or (ansible_distribution =="Fedora" and ansible_distribution_major_version == 23)
Combining Loops and Conditional Tasks
可以组合循环和条件。在下面的示例中,如果有一个文件系统挂载在/上且空闲空间超过300 MB,那么yum模块将安装mariadb-server包。ansible_mountes的facts是一个字典列表,每个字典表示一个已挂载文件系统的facts。循环遍历列表中的每个字典,条件语句不满足,除非发现一个字典表示一个挂载的文件系统,其中两个条件都为真。
Yaml代码:
---
- name: test
hosts: all
tasks:
- name: install mariadb-server if enough space on root
yum:
name: mariadb-server
state: latest
with_items: "{{ ansible_mounts }}"
when: item.mount == "/" and item.size_available > 300000000
1、查看facts主机变量
[student@workstation loop]$ ansible servera -m setup -a 'filter=ansible_mounts'
servera | SUCCESS => {
"ansible_facts": {
"ansible_mounts": [
{
"device": "/dev/vda1",
"fstype": "xfs",
"mount": "/",
"options": "rw,seclabel,relatime,attr2,inode64,noquota",
"size_available": 41486602240,
"size_total": 42936958976,
"uuid": "d85d7be1-03aa-433a-a847-030104cc3ce2"
}
]
},
"changed": false
}
下面是结合条件和注册变量的另一个示例。只有在postfix服务运行时,以下带注释的剧本才会重新启动httpd服务。示例2:
---
- name: test
hosts: servera
tasks:
- name: Postfix server status
command: /usr/bin/systemctl is-active postfix ①
ignore_errors: yes ②
register: result ③
- name: Restart Apache HTTPD if Postfix running
service:
name: httpd
state: restarted
when: result.rc == 0 ④
① postfix服务是否运行?
② 如果它没有运行并且命令“失败”,忽略报错,不要停止处理
③ 将模块结果的信息保存在一个名为result的变量中
④ 计算postfix任务的输出。如果systemctl命令的退出代码为0,则Postfix是active的,此任务将重新启动httpd服务。