流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。
条件判断
ansible提供的条件判断只有when指令,因为可以写Jinja2条件判断表达式,所以判断方式比较灵活。
同时满足多个条件
常见的编程语言在多条件判断时要么使用逻辑与(and
或&&
),要么使用逻辑或(or
或||
)。ansible同样支持这种写法。
1
| when: age > 18 and age < 30
|
也可以将这些条件以列表的方式提供。例如:
1 2 3 4 5 6 7 8 9
| - hosts: localhost gather_facts: false tasks: - debug: var: item when: - item > 3 - item < 5 loop: [1,2,3,4,5,6]
|
按条件导入文件
1 2 3 4 5 6 7 8 9
| --- - hosts: localhost gather_facts: yes tasks: - include_tasks: RedHat.yml when: ansible_os_family == "RedHat" - include_tasks: Centos.yml when: ansible_os_family == "Centos"
|
更加精炼的写法可以参考
1 2 3 4 5
| --- - hosts: localhost gather_facts: yes tasks: - include_tasks: "{{ansible_os_family}}.yml"
|
when和循环
当when指令和循环指令一起使用时,when的判断操作在每轮循环内执行。详细内容下文中描述。
循环迭代
ansible 2.5之前的循环迭代都使用with_xxx
来完成,比如with_items
,后面加入了loop
指令,loop
指令与with_list
指令等价。
with_xxx
语法都使用对应的lookup插件来实现(比如with_list
使用的是lookup的list插件),如果存在某lookup插件xxx,就可以使用with_xxx
来迭代。
with_list
直接上例子:
1 2 3 4 5 6 7 8 9
| - hosts: localhost gather_facts: false tasks: - file: name: "{{item}}" state: touch with_list: - aaa - bbb
|
与上面with_list
等价的loop语法:
1 2 3 4 5 6
| - file: name: "{{item}}" state: touch loop: - aaa - bbb
|
with_items和with_flattened
with_list用于迭代简单列表,有时候列表中会嵌套列表。
1 2 3 4 5 6 7 8
| - hosts: localhost gather_facts: false vars: name: [a,[b1,b2],c] tasks: - debug: var: "{{item}}" with_items: "{{name}}"
|
注意,with_items
只压平嵌套列表的第一层,不会递归压平第二层、第三层…
与with_items等价的loop指令的写法为:
1
| loop: "{{ nested_list | flatten(levels=1) }}"
|
筛选器函数flatten()默认会递归压平所有嵌套列表,如果只是压平第一层,需指定参数levels=1。
此外,还存在lookup插件:items、flattened,前者只压第一层,后者递归压平所有嵌套层次。例如:
1 2 3 4 5 6 7 8
| - hosts: localhost gather_facts: false vars: name: [a,[b1,b2],c,[d1,d2,[e1,e2,e3]]] tasks: - debug: var: "{{item}}" with_flattened: "{{name}}"
|
with_dict
with_dict
用于迭代一个字典结构,迭代时可以使用item.key
表示每个字典元素的key,item.value
表示每个字典元素的value。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - hosts: localhost gather_facts: false vars: users: slions: name: slions age: 29 zhangsan: name: zhangsan age: 18 tasks: - debug: msg: "who: {{item.key}} name: {{item.value.name}} age: {{item.value.age}}" with_dict: "{{users}}"
|
与with_dict
等价的loop指令有两种写法:
1 2
| loop: "{{lookup('dict', users)}}" loop: "{{users | dict2items}}"
|
另外,在Ansible 2.8中可以自定义dict2items
筛选器函数得到的key和value的名称。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - hosts: localhost gather_facts: false vars: users: slions: name: slions age: 29 zhangsan: name: zhangsan age: 18 tasks: - debug: msg: "who: {{item.k}} name: {{item.v.name}} age: {{item.v.age}}" loop: "{{users|dict2items(key_name='k',value_name='v')}}"
|
with_sequence
Ansible的lookup插件sequence也可以用来生成连续数(Jinja2的range也可以生成连续数)。其中:
- start参数指定序列的起始数,不指定该参数时默认从1开始
- end参数指定序列的终止数
- stride参数指定序列的步进值。不指定该参数时,步进为1
- format参数指定序列的输出格式,遵循printf风格
- count参数指定生成序列数的个数,不能和end参数共存
此外,sequence插件的各个参数可以简写为如下格式:
1
| [start-]end[/stride][:format]
|
例如
1 2 3 4 5 6 7
| --- - hosts: localhost gather_facts: false tasks: - debug: msg: "_{{item}}_" with_sequence: start=0 end=5 stride=2 format=a%02d
|
执行结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| PLAY [localhost] *********************************************************************************************
TASK [debug] ************************************************************************************************* ok: [localhost] => (item=a00) => { "msg": "_a00_" } ok: [localhost] => (item=a02) => { "msg": "_a02_" } ok: [localhost] => (item=a04) => { "msg": "_a04_" }
PLAY RECAP *************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
也可简写为:
1
| with_sequence: 0-5/2:a%02d
|
因为生成的每个序列数都会经过字符串格式化,所以得到的每个序列元素都是字符串。如果想要转换成数值,需使用Jinja2的Filter。例如:
1 2 3 4 5 6 7
| --- - hosts: localhost gather_facts: false tasks: - debug: msg: "{{1 + item|int}}" with_sequence: start=0 end=3
|
与with_sequence等价的loop写法为:
1 2 3
| - debug: msg: "{{ 'a%02d' | format(item) }}" loop: "{{ range(0, 5, 2)|list }}"
|
Jinja2的range()也可以生成序列数。语法:
注意range()不包含结尾数end。
with_fileglob
with_fileglob
用于迭代通配到的每个文件名。
例如:
1 2 3 4 5 6 7 8 9 10
| --- - hosts: localhost gather_facts: no tasks: - copy: src: "{{item}}" dest: /tmp/ with_fileglob: - /etc/m*.conf - /etc/*.cnf
|
执行结果为:
1 2 3 4 5 6 7 8 9
| PLAY [localhost] *********************************************************************************************
TASK [copy] ************************************************************************************************** changed: [localhost] => (item=/etc/man_db.conf) changed: [localhost] => (item=/etc/mke2fs.conf) changed: [localhost] => (item=/etc/my.cnf)
PLAY RECAP *************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
with_lines
with_lines
用于迭代命令输出结果的每一行。
这功能也是非常实用的,如下示例:find找出一堆文件,然后进行操作(比如copy)。
1 2 3 4 5 6 7 8 9
| --- - hosts: localhost gather_facts: false tasks: - copy: src: "{{item}}" dest: /home/myscript/ with_lines: - find ~ -maxdepth 1 -type f -name "*.sh"
|
循环和when
当with_xxx
或loop
指令和when
指令一起使用时,when
将在循环的内部进行条件判断。也就是说,when决定每轮迭代时是否执行一个任务,而不是决定整个循环是否进行。
循环和register
1 2 3 4 5 6 7 8 9 10 11 12
| --- - hosts: localhost gather_facts: false vars: mylist: [11, 22] tasks: - debug: var: item loop: "{{ mylist }}" register: res - debug: var: res
|
其中第二个debug任务的执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ok: [localhost] => { "res": { "changed": false, "msg": "All items completed", "results": [ { "ansible_loop_var": "item", "changed": false, "failed": false, "item": 11 }, { "ansible_loop_var": "item", "changed": false, "failed": false, "item": 22 } ] } }
|
再来一个shell模块的示例:
1 2 3 4 5 6 7 8 9 10 11
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - shell: echo {{item}} loop: "{{ mylist }}" register: res - debug: var: res
|
其中第二个任务的执行结果为:
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
| ok: [localhost] => { "res": { "changed": true, "msg": "All items completed", "results": [ { "ansible_loop_var": "item", "changed": true, "cmd": "echo 11", "delta": "0:00:00.001643", "end": "2021-12-02 11:40:05.012729", "failed": false, "invocation": { "module_args": { ...... } }, "item": 11, "rc": 0, "start": "2021-12-02 11:40:05.011086", "stderr": "", "stderr_lines": [], "stdout": "11", "stdout_lines": [ "11" ] }, { "ansible_loop_var": "item", ...... "stdout": "22", "stdout_lines": [ "22" ] } ] } }
|
可见,当register和循环指令结合时,会将每轮迭代的模块执行结果以一个字典的方式追加在一个名为results的列表中。即:
1 2 3 4 5 6 7 8 9 10 11 12
| "res": { "changed": true, "msg": "All items completed", "results": [ { ...第一轮迭代模块返回值... }, { ...第二轮迭代模块返回值... } ] }
|
所以,可使用res.results
来访问每轮的迭代结果。例如,再次迭代遍历这些结果:
1 2 3 4 5 6 7 8 9 10 11 12
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - shell: echo {{item}} loop: "{{ mylist }}" register: res - debug: var: item.stdout loop: "{{res.results}}"
|
循环控制
循环控制功能需要在使用循环的时候使用loop_control
指令,该指令有一些参数,每种参数都是一个控制开关。
label参数
循环迭代时,每轮迭代过程中都会将当前的item输出(要么输出到屏幕,要么输出到日志)。如果所迭代的每项的内容(即每个item)很短,这倒无所谓,但如果item的内容很长,则可读性比较差。
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - shell: echo {{item}} loop: "{{ mylist }}" register: res - debug: var: item.stdout loop: "{{res.results}}"
|
输出如下:
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
| TASK [debug] ***************************************************************************************************** ok: [localhost] => (item={u'stderr_lines': [], u'ansible_loop_var': u'item', u'end': u'2021-12-02 11:57:33.161219', u'stderr': u'', u'stdout': u'11', u'changed': True, u'failed': False, u'delta': u'0:00:00.040706', u'cmd': u'echo 11', u'item': 11, u'rc': 0, u'invocation': {u'module_args': {u'warn': False, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'echo 11', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'stdout_lines': [u'11'], u'start': u'2021-12-02 11:57:33.120513'}) => { "ansible_loop_var": "item", "item": { "ansible_loop_var": "item", "changed": true, "cmd": "echo 11", "delta": "0:00:00.040706", "end": "2021-12-02 11:57:33.161219", "failed": false, "invocation": { "module_args": { "_raw_params": "echo 11", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": false } }, "item": 11, "rc": 0, "start": "2021-12-02 11:57:33.120513", "stderr": "", "stderr_lines": [], "stdout": "11", "stdout_lines": [ "11" ] }, "item.stdout": "11"
|
使用label参数可以自定义迭代时显示的内容来替代默认显示的item。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - shell: echo {{item}} loop: "{{ mylist }}" register: res - debug: var: item.stdout loop: "{{res.results}}" loop_control: label: "this is test play"
|
输出为:
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
| TASK [debug] ***************************************************************************************************** ok: [localhost] => (item=this is test play) => { "ansible_loop_var": "item", "item": { "ansible_loop_var": "item", "changed": true, "cmd": "echo 11", "delta": "0:00:00.034184", "end": "2021-12-02 12:21:47.385021", "failed": false, "invocation": { "module_args": { "_raw_params": "echo 11", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": false } }, "item": 11, "rc": 0, "start": "2021-12-02 12:21:47.350837", "stderr": "", "stderr_lines": [], "stdout": "11", "stdout_lines": [ "11" ] }, "item.stdout": "11"
|
pause参数
loop_control
的pause
参数可以控制每轮迭代之间的时间间隔。
以下示例表示第一轮迭代后,等待一秒,再进入第二轮迭代。
1 2 3 4 5 6 7 8 9 10 11
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - debug: var: item loop: "{{mylist}}" loop_control: pause: 1
|
index_var参数
index_var
参数可以指定一个变量,这个变量可以记录每轮循环迭代过程中的索引位,也即表示当前是第几轮迭代。
1 2 3 4 5 6 7 8 9 10 11
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - debug: msg: "index: {{idx}}, value: {{item}}" loop: "{{mylist}}" loop_control: index_var: idx
|
输出结果:
1 2 3 4 5 6
| ok: [localhost] => (item=11) => { "msg": "index: 0, value: 11" } ok: [localhost] => (item=22) => { "msg": "index: 1, value: 22" }
|
通过index_var
,可以进行一些条件判断。比如只在第一轮循环时执行某任务:
1 2 3 4 5 6 7 8 9 10 11 12
| --- - hosts: localhost gather_facts: false vars: mylist: [11,22] tasks: - debug: msg: "index: {{idx}}, value: {{item}}" when: idx == 0 loop: "{{mylist}}" loop_control: index_var: idx
|
其他流程控制
pause模块
Ansible中,可以使用pause
模块或wait_for
模块来实现睡眠等待的功能,先简单演示pause模块的用法。
pause可以等待几分钟、几秒钟、等待交互式输入确定。
例如,先睡眠10秒,再执行debug任务:
1 2 3 4 5 6 7 8
| --- - hosts: localhost gather_facts: false tasks: - pause: seconds: 10 - debug: msg: "hello world"
|
睡眠1分钟:
交互式输入Enter键确认:
1 2 3 4
| tasks: - pause: - debug: msg: "hello world"
|
带提醒的交互式输入:
1 2
| - pause: prompt: "输入你的用户名!"
|
隐藏用户的输入:
1 2 3
| - pause: prompt: "输入你的用户名" echo: no
|
将用户交互式输入内容注册成变量:
1 2 3 4 5 6 7 8 9 10
| --- - hosts: localhost gather_facts: false tasks: - pause: prompt: "输入用户密码" echo: no register: passwd - debug: msg: "{{passwd.user_input}}"
|
wait_for模块
wait_for
模块可以等待多种事件的发生,常用的功能有:
- 等待端口打开和端口关闭
- 等待没有活动连接(在等待移除某个负载均衡节点时可能会有用)\
- 等待文件被创建或移除
- 等待或睡眠指定秒数
- 等待系统重启(即等待SSH连接重新建立)
- 等待文件中出现某个字符串
- 等待进程退出
常见用法:
睡眠几秒后,任务继续。
1 2 3
| - wait_for: timeout: 5
|
等待文件存在后,任务继续。
1 2 3 4 5
| - wait_for: path: /tmp/a.log deley: 3 sleep: 1 timeout: 20
|
等待文件不存在后,任务继续。
1 2 3
| - wait_for: path: /tmp/a.log state: absent
|
等待进程不存在后,任务继续。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| --- - hosts: localhost gather_facts: no tasks: - pids: name: "sleep" register: sleep_pids
- wait_for: path: "/proc/{{item}}" state: absent loop: "{{sleep_pids.pids}}"
- debug: msg: 'hello world'
|
pids模块可以根据进程名获取进程PID列表(可能是空列表、单元素列表、多元素列表)。
注意该模块要求先安装python的psutil模块,所以如果要使用pids,可执行:
$ yum install python3-devel
$ pip3 install psutil
等待文件中出现某字符串后,任务继续。
1 2 3
| - wait_for: path: /tmp/a.log search_regex: completed|finished
|
等待某端口打开,然后任务继续。
1 2 3 4 5 6 7 8 9 10 11
| - wait_for: port: "{{ item }}" state: started delay: 3 timeout: 100 loop: - 10251 - 10252 - 2379 - 6443 - 8443
|