除条件判断外, 另一种分支控制结构是循环结构。Ansible提供了很多种循环结构,一般都命名为with_Xxx,例如with_items、with_list、with_file等,使用最多的是with_items。

 

循环:迭代,需要重复执行的任务


对迭代项的引用,固定变量名为"item”,使用with_item属性给定要迭代的元素; 这个是以任务为中心,围绕每个任务来跑主机,如果中间某个任务中断,那么所有主机以后的任务就无法安装。

元素:

  • 列表
  • 字符串
  • 字典

基于字符串列表给出元素示例:


-    hosts: websrvs
remote_user: root
tasks:
- name: install packages
yum: name={{ item }} state=latest
with_items:
- httpd
- php
- php-mysql
- php-mbstring
- php-gd


基于字典列表给元素示例:item.name ​​.​​后边的表示键


- hosts: all
remote_user: root
tasks:
- name: create groups
group: name={{ item }} state=present
with_items:
- groupx1
- groupx2
- groupx3
- name: create users
user: name={{ item.name }} group={{ item.group }} state=present
with_items:
- {name: 'userx1', group: 'groupx1'}
- {name: 'userx2', group: 'groupx2'}
- {name: 'userx3', group: 'groupx3'}


 

 loop循环


在这里仅介绍loop循环,它是在Ansible 2.5版本中新添加的循环结构,等价于with_list。大多数时候,with_xx的循环都可以通过一定的手段转换成loop循环,所以从Ansible 2.5版本之后,原来经常使用的with_items循环都可以尝试转换成loop。有了循环结构,生活就美妙多了。

例如,在locahost上创建两个文件/tmp/test/{1,2}.txt,不用循环结构的playbook写法∶ 

[root@localhost ~]# cat loop.yml 
---
- name: play1
hosts: all
remote_user: root
gather_facts: false
tasks:
- name: create file /tmp/test1.txt
file: path=/tmp/test1.txt state=touch
- name: create file /tmp/test2.txt
file: path=/tmp/test2.txt state=touch

[root@localhost ~]# ansible-playbook loop.yml

PLAY [play1] ***************************************************************************************************

TASK [create file /tmp/test1.txt] ******************************************************************************
changed: [192.168.179.99]

TASK [create file /tmp/test2.txt] ******************************************************************************
changed: [192.168.179.99]

PLAY RECAP *****************************************************************************************************
192.168.179.99 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

使用loop写法

[root@localhost ~]# cat loop.yml 
---
- name: play1
hosts: all
remote_user: root
gather_facts: false
tasks:
- name: create file /tmp/test1.txt
file: path={{item}} state=touch
loop:
- /tmp/test1.txt
- /tmp/test2.txt


[root@localhost ~]# ansible-playbook loop.yml

PLAY [play1] ***************************************************************************************************

TASK [create file /tmp/test1.txt] ******************************************************************************
changed: [192.168.179.99]

TASK [create file /tmp/test2.txt] ******************************************************************************
changed: [192.168.179.99]

PLAY RECAP *****************************************************************************************************
192.168.179.99 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

显然,循环可以将多个任务组织成单个任务并实现相同的功能。解释下上面的loop和{{item}}。

loop等价于with_list,从名字上可以知道它是遍历数组(列表)的,所以在loop指令中,每个元素都以列表的方式去定义。列表有多少个元素,就循环执行file模块多少次,每轮循环中,都会将本次迭代的列表元素保存在控制变量 item中。

或许,使用伪代码去解释这个loop结构会更容易理解,这里我使用shell伪代码来演示∶

for item in /tmp/test1.txt /tmp/test2.txt; do touch $(item) ;done

当有需要重复性执行的任务时,可以使用迭代机制。其使用格式为将需要迭代的内容定义为item变量引用,并通过loop语句指明迭代的元素列表即可。loop的值是python list数据结构,每个task会循环读取list的值,然后后key的名称是item,list里面也支持python字典。 

示例一:安装多个软件

tasks:
- name: "Install Packages"
yum: name={{ item }} state=latest
loop:
- httpd
- mysql-server
- php

示例二:批量创建多个用户。(with_items)

tasks:
- name: "add user"
user: name={{ item.name }} state=present groups={{ item.groups }}
loop:
- {name: "test5", groups: "tom"}
- {name: "test6", groups: "tom"}

其中引用变量时前缀item变量是固定的,而item后跟的键名就是在loop中定义的字典键名。

 

when条件判断


Ansible作为一个编排、协调任务的配置管理工具,它必不可少的功能是提供流程控制功能,比如条件判断、循环、退出等。

在Ansible中,提供的唯——个通用的条件判断是when指令,当when指令的值为true 时,则该任务执行,否则不执行该任务。例如∶

[root@localhost ~]# cat when.yml 
---
- name: play1
hosts: all
remote_user: root
gather_facts: false
vars:
myname: test
tasks:
- name: task will skip
debug: msg={{myname}}
when: myname == "test1"

- name: task will execute
debug: msg={{myname}}
when: myname == "test"


[root@localhost ~]# ansible-playbook when.yml

PLAY [play1] ************************************************************************************************************************

TASK [task will skip] ***************************************************************************************************************
skipping: [192.168.179.99]

TASK [task will execute] ************************************************************************************************************
ok: [192.168.179.99] => {
"msg": "test"
}

PLAY RECAP **************************************************************************************************************************
192.168.179.99 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

需要注意的是,when指令因为已经明确是做条件判断,,所以它的值必定是一个表达式,它会自动隐式地帮我们包围一层{{}},所以在写when指令的条件判断时,不要再手动加上{{}}。正如上面示例中的写法when∶myname =="test",如果改写成when∶{{myname ="junma"}},这表示表达式的嵌套,通常来说不会出错,但在某些场景下是错误的,而且Ansible会给我们警告。

一个比较常见的应用场景是实现跳过某个主机不执行任务或者只有满足条件的主机执行任务。例如∶

when: inventory_hostname =="web1"

这表示只有inventory中设置的主机web1才执行任务,其它主机都不执行。

示例一:只有当vsftpd服务运行时,才能重启Apache

---
- name: Restart httpd if vsftpd is running
hosts: all
tasks:
- name: Get vsftpd status
command: /usr/bin/systemctl is-active vsftpd #判断状态
ignore_errors: yes #如果vsftpd没运行或失败,则忽略,继续执行下面动作
register: result #定义变量保存结果
- name: Restart httpd
service:
name: httpd
state: restarted
when: result.rc == 0 #退出码为0,则重启httpd
...

示例二:安装并启动数据库 

编写playbook:

############基础写法################
- name: Mariadb is running
hosts: web1
vars:
mariadb_pkgs:
- mariadb-server
- python3-PyMySQL
tasks:
- name: Mariadb is installed
yum:
name: "{{ item }}"
state: present
loop: "{{ mariadb_pkgs }}"
- name: Start Mariadb
service:
name: mariadb
state: started
enabled: true
#修改 playbook,条件变为受管主机使用 rehdat 操作系统时才执行########
---
- name: Mariadb is running
hosts: web1
vars:
mariadb_pkgs:
- mariadb-server
- python3-PyMySQL
tasks:
- name: Mariadb is installed
yum:
name: "{{ item }}"
state: present
loop: "{{ mariadb_pkgs }}"
when: ansible_distribution == "RedHat" #更改条件
...

最后,虽然when指令的逻辑很简单∶值为true则执行任务,否则不执行任务。但是,它的用法并不简单,when指令的值可以是Jinja2的表达式,很多内置在Jinja2中的Python的语法都可以用在when指令中,而这需要掌握Python的基本语法。如果不具备这些知识,那么想要实现某种判断功能可能会感觉到较大的局限性,而且别人写的脚本可能看不懂。但是我的建议是,别强求,掌握常用的条件判断方式,以后有需求再去网上搜索对应用法。