Ansible 任务控制

Ansible的循环控制及条件任务

Ansible Playbook的循环控制、条件控制、处理程序以及错误处理机制。

loop循环控制

Ansible支持使用loop关键字来执行循环任务。

简单迭代任务循环

将loop关键字添加到任务中,并用列表表示要迭代的项目的值。并使用item临时变量来保存每次循环迭代过程中使用的值。

如下是一个启动两个服务的例子,在没有了解循环控制时应这样编写playbook,分别写两个独立的任务来执行:

1
2
3
4
5
6
7
8
9
- name: Postfix is running
service:
name: postfix
state: started

- name: httpd is running
service:
name: httpd
state: started

使用loop循环控制,则可以简写如下:

1
2
3
4
5
6
7
- name : Start Postfix and httpd services
service:
name: "{{ item }}"
state: started
loop:
- postfix
- httpd

也可以把loop所使用的列表存到play的变量中,他们三个执行的效果是相同的:

1
2
3
4
5
6
7
8
9
10
11
vars:
start_services:
- postfix
- httpd
tasks:
- name : Start Postfix and httpd services
service:
name: "{{ item }}"
state: started
loop: "{{ start_services }}"

循环散列或字典列表

loop也可以循环散列或者是字典,下面示例中每个字典或散列有两个键,分别是name和groups。当前循环中的每个键的值可以分别通过item.name和item.group来检索。

1
2
3
4
5
6
7
8
9
10
- name: User exist and are in the corrent Groups
user:
name: "{{ item.name }}"
group: "{{ item.group }}"
loop:
- name: user1
group: wheel
- name: user2
group: root

早期的循环语法

在Ansible 2.5之前,大多数的playbook使用不同的循环愈发。提供了多个循环关键字,都以前缀with_开头,后跟Ansible查找插件的名称连用。这种循环语法在目前依旧很常见,但在未来某个时刻将会被弃用。

循环关键字 描述
with_items 和loop类似,但当为with_items提供了列表的列表,他们会被扁平化处理为单级列表。item作为循环变量保存每次迭代过程中的值
with_file 此关键字需要控制节点文件名列表。循环变量item保存每次迭代过程中保存文件列表中相应文件的内容
wirh_sequence 此关键字不需要列表,而是需要参数生成数字序列列表。循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值

将Register变量与Loop一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- name: Loop Register Test
gather_facts: no
hosts: localhost
tasks:
- name: Loop Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results

- name: Show echo_results variable
debug:
var: echo_results

条件任务语句

使用条件语句when可以控制该任务是否执行,根据条件配置满足一定情况下执行任务。比如在执行任务前首先要判断一下磁盘剩余空间、内存大小是否满足需求。如果不满足则直接跳过该任务。

变量也可以作为when条件语句的判断条件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
---
- name: Test Boolean Task Demo
hosts: all
vars:
run_task: true
tasks:
- name: Install the web services
yum:
name: httpd
state: present
when: run_task

...

在当变量run_task为真的时候,则执行Install the web services的任务,否则跳过该任务。

下面是常用的判断条件列表。

判断条件 示例
等于(字符串) ansible_machine == “x86_64”
等于(数字) max_memory == 512
常见判断 <=、<、>、>=、!=
变量存在 max_memory is defined
变量不存在 max_memory is not defined
第一个变量在第二个变量的列表里 var1 in var_all

多个条件组合

在使用when条件语句时,如果要判断组合条件,可以使用andor关键字来进行组合,并与括号分组条件。

when: ansible_facts.distribution == "RedHat" or ansible_facts.distribution == "Debian"

如果多个条件之间是and关系,也可以使用列表的形式来表示:

1
2
3
when:
- ansible_facts.distribution == "RedHat"
- ansible_facts.distribution == "CentOS"

使用括号能编写更复杂的条件:

1
2
3
4
when: >
(ansible_facts.distribution == "RedHat" and ansible_facts.distribution_major_version == "7")
or
(ansible_facts.distribution == "Fedora" and ansible_facts.distribution_major_version == "28")

loop循环和条件判断组合使用

例子中yum模块将要安装mariadb-server软件包,但是要求根目录满足剩余空间300MB以上才会安装,所以可以遍历目标主机上所有挂在的磁盘然后找到根目录再判断剩余空间,如果满足则安装,不满足则跳过该任务。

当对某个任务结合使用when和loop时,将对每一项都使用when语句进行判断。

1
2
3
4
5
6
7
tasks:
- name: Make Sure root have enough space to install mariadb
yum:
name: mariadb-server
state: present
loop: "{{ ansible_facts.mounts }}"
when: item.mount == "/" and item.size_available > 300000000

处理程序

有时候我们需要更改完配置文件后重启服务,但只想对文件有更改的情况下才重启服务。得益于Ansible的模块设计的幂等性,我们可以通过判断是否进行了更改而选择执行任务。这种方式叫处理程序。

处理程序可看作非活动任务,只有在使用notify语句激活后才会被触发,只有配置文件更新了并激活了该处理任务时,在到处理程序定义的位置时才会执行该任务。

处理任务只有在被激活的情况下才会执行,要注意的是处理程序不会按照你的激活顺序执行,而是按照激活程序的编写顺序以此判断是否被激活和执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- name: Enable internet services
hosts: all
become: yes
tasks:
- name: Add Web content
get_url:
url: http://materials.example.com/labs/playbook-review/index.php
dest: /var/www/html/index.php
mode: 0644
notify: restart apache
handlers:
- name: restart apache
systemd:
name: httpd
state: restarted
...

在第一次添加新页面后,激活restart apache处理程序,当开始执行handlers处理程序时,发现restart apache处理程序被激活,则执行该任务。

如果第二次执行该playbook你会发现处理程序并没有执行,因为任务Add Web content返回的状态是OK,由于没有进行任何更改所以不会执行重启httpd服务的操作。

如何处理任务失败

Ansible在执行任务的过程中,有任何一个任务执行失败,则不论接下来是否还有任务都不会执行。

但是如果你有特定的要求,即便是某一个任务执行失败,也继续往下执行其他任务的话,你需要在对应的任务上添加ignore_errors关键字。

1
2
3
4
5
- name: Latest version of notapkg is installed
yum:
name: notapkg
state: latest
ignore_errors: yes

任务失败后强制执行处理程序

哪怕是Play在执行任务的过程中失败了,也会强制执行已经被激活的处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
- name: Enable internet services
force_handlers: yes
hosts: all
become: yes
tasks:
- name: Latest version of notapkg is installed
yum:
name: httpd
state: latest
notify:
- restart apache

- name: Always is False
command: /bin/false

handlers:
- name: restart apache
systemd:
name: httpd
state: restarted
...

指定任务失败的条件

使用failed_when关键字可以指定任务已失败的条件,条件语句和when的条件语句相同。

下面的例子是当有任何一个磁盘挂载点剩余的空间小于300MB则将任务结果返回为失败,即便是已经成功安装。

1
2
3
4
5
6
7
tasks:
- name: Make Sure root have enough space to install mariadb
yum:
name: mariadb-server
state: present
loop: "{{ ansible_facts.mounts }}"
failed_when: item.size_available < 300000000

如果failed_when关键字为true那么不管最后的执行结果如何,都会返回任务处理结果为失败。

指定是否报告任务的Changed结果

当任务对托管主机进行了更改后,如果有处理任务,则会激活处理任务。但在有些特殊模块如command模块,有时并不能按照预期来返回ok结果,所以只能返回Changed作为任务执行结果,这将会激活处理任务。

使用changed_when关键字可以控制何时返回Changed执行结果。他和failed_when关键字一样,和when的条件语句相同。你可以对某一变量是否在另一个变量中出现过进行判断。

1
2
3
4
5
6
7
8
9
10
tasks:
- shell:
cmd: /usr/local/bin/upgrade-database
register: command_result
changed_when: "'Success' in command_result.stdout"
handelers:
- name: restart database
service:
- name: mariadb
state: restarted

Ansible 块和错误自动处理

在Playbook中,块用来对任务进行逻辑分组,可用于控制任务的执行方式,例如任务块可以和when关键字连用,可以将某一条件用于多个任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---
- name: Enable internet services
hosts: all
become: yes
tasks:
- name: Installed Service
block:
- name: Latest version of notapkg is installed
yum:
name: httpd
state: latest

- name: Always is True
command: /bin/true
changed_when: false
when: ansible_facts.distribution == "RedHat"

- name: Always is True
command: /bin/true
changed_when: false
...

通过块,也可以与rescue和always语句连用来处理错误。如果块中包裹的任务有任何一个执行失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue运行结束后,最后运行always语句中包含的任务。

  • block:定义要运行的主要任务
  • rescue:定义在block块中有任务执行失败后要运行的任务
  • always:始终都要执行的任务,不论block和rescue子句中定义的任务执行成功还是失败都会运行always中定义的任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
- name: Test Block
hosts: all
become: yes
tasks:
- name: Installed Service
block:
- name: Latest version of notapkg is installed
yum:
name: httpd
state: latest

- name: Always is True
command: /bin/true
changed_when: false
when: ansible_facts.distribution == "RedHat"
rescue:
- name: rescue block tasks
debug:
msg: "rescued block tasks"
always:
- name: Always has been exec
debug:
msg: "Always has been exec"
...

任务控制总结


vars变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

services:
- "{{ web_service }}"
- "{{ fw_service }}"

packages:
- "{{ web_package }}"
- "{{ ssl_package }}"
- "{{ fw_package }}"

ssl_cert_dir: /etc/httpd/conf.d/ssl

web_config_files:
- src: server.key
dest: "{{ ssl_cert_dir }}"
- src: server.crt
dest: "{{ ssl_cert_dir }}"
- src: ssl.conf
dest: /etc/httpd/conf.d
- src: index.html
dest: /var/www/html

PlayBook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
- name: Playbook Control Lab
hosts: webservers
vars_files: vars.yml
tasks:
#Fail Fast Message
- name: check ram size
fail:
msg: "Cant install under free ram 256M"
when: ansible_facts.memtotal_mb < min_ram_mb and ansible_facts.distribution != "RedHat"

#Install all Packages

- name: Installed "{{ packages }}" Packages
yum:
name: "{{ packages }}"
state: present

#Enable and start services

- name: Enable and start "{{ services }}" services
service:
name: "{{ item }}"
state: started
enabled: true
loop: "{{ services }}"

#Block of config tasks

- name: Config Document Tasks
block:
- name: existed ssl_cret_dir
file:
path: "{{ ssl_cert_dir }}"
state: directory

- name: Config File existed
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
loop: "{{ web_config_files }}"
notify:
- restart web service
rescue:
- debug:
msg: >
One or more of the configuration changes faild, but the web service is still active.
#Configure the firewall
- name: Configed Firewalld Allowed
firewalld:
service: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop:
- http
- https

#Add handlers
handlers:
- name: restart web service
service:
name: "{{ web_service }}"
state: restarted