【运维进阶】实施任务控制
实施任务控制
在 Ansible 中,“实施任务控制” 通常指的是对任务执行流程的控制,比如:
条件执行(when)
循环执行(with_items / loop)
错误处理(block / rescue / ignore_errors)
任务依赖(tags / handlers)
异步执行(async / poll)
任务注册与判断(register)
实验环境
[lth@controller ~ 20:55:49]$ mkdir web && cd web[lth@controller web 20:56:02]$ cat > ansible.cfg <<'EOF'
> [defaults]
> remote_user = lth
> inventory = ./inventory
>
> [privilege_escalation]
> become = True
> become_user = root
> become_method = sudo
> become_ask_pass = False
> EOF
[lth@controller web 20:56:47]$ cat > inventory <<'EOF'
> controller
> node1
> node2
> node3
> node4
> EOF
编写循环任务
利用循环,管理员无需编写多个使用同一模块的任务。例如,确保存在五个用户,不需要编写五个任务,而是只需编写一个任务来对含有五个用户的列表迭代。
Ansible支持使用 loop 关键字对一组项目迭代任务。您可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
本节介绍迭代项目列表的简单循环。
简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中, 将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
示例:
---
- name: add several usershosts: node1gather_facts: notasks:- name: add user janeuser:name: "jane"groups: "wheel"state: present- name: add user joeuser:name: "joe"state: presentgroups: "wheel"
使用loop循环改写:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item }}"groups: "wheel"state: presentloop:- jane- joe
或者
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item }}"groups: "wheel"state: presentloop: "{{ users }}"vars:users:- jane- joe
复杂循环
[lth@controller web 21:46:47]$ cat playbook.yml # yaml格式起始行,一般不省略---# Playbook中第一个play# play具有属性:name,hosts,become,tasks,缩进一致# name属性,用于简要描述play- name: Enable intranet services# hosts属性,用于定义要在哪个受管理节点执行hosts: node1# tasks属性,用于描述play中任务,属性是列表格式tasks:# 第一个任务# 任务具有属性:涵name和模块名等。# name属性,用于简要描述任务- name: latest version of httpd and firewalld installed# 指明模块名,也就是要执行的任务yum:# 执行要操作的rpm包名称name:# rpm包名称是-开头的列表格式,或者逗号分隔的列表格式- httpd- firewalld# 定义软件包的状态,lastet代表升级为最新版本state: latest# 第二个任务- name: test html page is installed# copy模块,用于将content属性值写入到目标文件copy:content: "Welcome Lth WebSite!\n"dest: /var/www/html/index.html# 第三个任务- name: firewalld enabled and running# service模块,用于启用并启动firewalld服务service:name: "{{item}}"enabled: truestate: startedloop:- httpd- firewalld# 第四个任务# - name: firewalld permits access to httpd service# # firewalld,用于放行http服务# firewalld:# service: http# permanent: true# state: enabled# immediate: yes# 第五个任务- name: httpd enabled and running# service模块,用于启用并启动httpd服务service:name: httpdenabled: truestate: started# Playbook中第二个play,-开头表示列表- name: Test intranet web serverhosts: localhostbecome: notasks:- name: connect to intranet web server# uri模块,用于测试网站是否可以访问uri:url: http://node1return_content: yesstatus_code: 200# yaml格式结束行,一般省略...# 验证
[lth@controller web 21:46:58]$ ansible-playbook playbook.yml PLAY [Enable intranet services] *********************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node1]TASK [latest version of httpd and firewalld installed] **********************************************ok: [node1]TASK [test html page is installed] ******************************************************************changed: [node1]TASK [firewalld enabled and running] ****************************************************************ok: [node1] => (item=httpd)ok: [node1] => (item=firewalld)TASK [httpd enabled and running] ********************************************************************ok: [node1]PLAY [Test intranet web server] *********************************************************************TASK [Gathering Facts] ******************************************************************************ok: [localhost]TASK [connect to intranet web server] ***************************************************************ok: [localhost]PLAY RECAP ******************************************************************************************localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node1 : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环散列或字典列表
在 Ansible 中,循环处理字典列表或散列可通过loop
实现:
- 对字典列表(如
[{key1: val1}, {key2: val2}]
),直接用loop: "{{ 列表变量 }}"
,通过item.key
访问各字段。 - 对嵌套字典(如
{key: {subkey: subval}}
),先用dict2items
过滤器转为[{key: ..., value: ...}]
形式,再循环,通过item.key
和item.value.subkey
访问。
可结合when
条件过滤,或用map
提取特定键值,实现批量操作。
在以下示例中,列表中的每个项实际上是散列或字典。
示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
示例:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item.name }}"groups: "{{ item.groups }}"state: presentloop: - name: janegroups: wheel- name: joegroups: root
改写为:
---
- name: add several usershosts: node1gather_facts: novars:users:- name: janegroups: wheel- name: joegroups: roottasks:- name: add users user:name: "{{ item.name }}"state: presentgroups: "{{ item.groups }}" loop: "{{ users }}"
Do-Until Loops
Do-Until 循环 是一种 后测试循环结构,意思是:
- 先执行一次循环体
- 然后再判断条件
- 直到条件为真时,循环才会终止
示例:测试 node2,什么时候可以ping通。
[lth@controller web 21:51:20]$ vim playbook.yml
[lth@controller web 21:51:25]$ cat playbook.yml
---
- name: test loophosts: node1gather_facts: notasks:- shell: ping -c1 -w 2 node2register: resultuntil: result.rc == 0retries: 20delay: 1# delay 代表两次检测任务是否完成的间隔时间,默认值5秒。
...# 关闭node2测试
[lth@controller web 21:51:36]$ ansible-playbook playbook.yml PLAY [test loop] ************************************************************************************TASK [shell] ****************************************************************************************FAILED - RETRYING: command (20 retries left).FAILED - RETRYING: command (19 retries left).FAILED - RETRYING: command (18 retries left).FAILED - RETRYING: command (17 retries left).# 测试过程中打开node2后
[lth@controller web 21:51:47]$ ansible-playbook playbook.yml PLAY [test loop] ************************************************************************************TASK [shell] ****************************************************************************************FAILED - RETRYING: command (20 retries left).FAILED - RETRYING: command (19 retries left).FAILED - RETRYING: command (18 retries left).FAILED - RETRYING: command (17 retries left).FAILED - RETRYING: command (16 retries left).FAILED - RETRYING: command (15 retries left).FAILED - RETRYING: command (14 retries left).FAILED - RETRYING: command (13 retries left).FAILED - RETRYING: command (12 retries left).FAILED - RETRYING: command (11 retries left).FAILED - RETRYING: command (10 retries left).FAILED - RETRYING: command (9 retries left).FAILED - RETRYING: command (8 retries left).FAILED - RETRYING: command (7 retries left).FAILED - RETRYING: command (6 retries left).FAILED - RETRYING: command (5 retries left).changed: [node1]PLAY RECAP ******************************************************************************************node1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
编写条件任务
Ansible可使用conditionals在符合特定条件时执行任务或play。
例如,管理员可利用条件来区分不同的受管节点,并根据它们所符合的条件来分配功能角色。 Playbook变量、注册的变量和ANSIBLE FACTS都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
用例:
- 定义变量min_memory,判断被管理节点可用内存是否满足该值。
- 捕获命令输出,判定task是否执行完成,以便决定是否进行下一步操作。
- 被管理节点上收集到的网络facts,判定是否适合哪种绑定(bonding或者trunking)。
- 根据CPU的数量决定如何调优web服务器。
- Registered变量与预定义的变量对比,判断是否有变化。例如文件的MD5值。
when 语句
ansible playbook 中使用 when 来运行条件任务。
when 用于有条件地运行任务,取要测试的条件作为值。如果条件满足,则运行任务。若条件不满足,则跳过任务。
注意:通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面。原因是任务是YAML散列/字典,when 语句只是任务中的一个键,就如任务的名称以及它所使用的模块一样。
常见判断
操作 | 示例 |
---|---|
等于(值为字符串) | ansible_machine == “x86_64” |
等于(值为数字) | max_memory == 512 |
小于 | min_memory < 128 |
大于 | min_memory > 256 |
小于等于 | min_memory <= 256 |
大于等于 | min_memory >= 512 |
不等于 | min_memory != 512 |
变量存在 | min_memory is defined |
变量不存在 | min_memory is not defined |
布尔变量值是1、True或yes的求值为真。 | memory_available |
布尔变量值是0、False或no的求值为假。 | memory_available |
memory_available变量值为真,最终结果为假。 | not memory_available |
第一个变量的值存在,作为第二个变量的列表中的值 | ansible_distribution in supported_distros |
布尔值变量判断
示例:
---
- name: test hosts: node1gather_facts: novars:run_my_task: truetasks:- name: test whendebug:msg: "Hello run my task"when: run_my_task
示例中的when语句导致:任务仅在run_my_task为 true时运行。
变量是否定义判断
变量判断:
- defined == not undefined 变量定义返回真
- undefined == not defined 变量未定义返回真
- none 变量定义了,但是值是空值,返回真
示例1:
[lth@controller web 21:53:17]$ cat playbook.yml
---
- hosts: node1gather_facts: novars:username: yuxbtasks:- debug:msg: "var: username is defined"when: username is defined
...# 执行
[lth@controller web 21:53:24]$ ansible-playbook playbook.yml PLAY [node1] ****************************************************************************************TASK [debug] ****************************************************************************************
ok: [node1] => {"msg": "var: username is defined"
}PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例2:判断受管主机是否具有相应设备。
# 看有没有sdb硬盘
[lth@controller web 22:00:06]$ cat playbook.yml
---
- hosts: node1gather_facts: yesvars:username: yuxbtasks:- debug:msg: "sdb is exist"when: ansible_devices.sdb is defined
...# 执行
[lth@controller web 22:00:10]$ ansible-playbook playbook.yml PLAY [node1] ****************************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [debug] ****************************************************************************************
skipping: [node1]PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0# 看有没有sr0盘
[lth@controller web 22:00:20]$ vim playbook.yml
[lth@controller web 22:00:23]$ cat playbook.yml
---
- hosts: node1gather_facts: yesvars:username: yuxbtasks:- debug:msg: "sr0 is exist"when: ansible_devices.sr0 is defined
...# 执行
[lth@controller web 22:00:41]$ ansible-playbook playbook.yml PLAY [node1] ****************************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [debug] ****************************************************************************************
ok: [node1] => {"msg": "sr0 is exist"
}PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
文件属性判断
- file:如果路径是一个普通文件,返回真
- directory:如果路径是一个目录,返回真
- link:如果路径是一个软连接,返回真
- mount:如果路径是一个挂载点,返回真
- exist:如果路径是存在,返回真
示例:
---
- hosts: node1gather_facts: novars:file_name: /etc/hoststasks:- debug:msg: "{{ file_name }} is regular file"when: file_name is file
任务执行结果判断
- succeeded,通过任务的返回信息判断,任务执行成功返回真。
- failed,通过任务的返回信息判断,任务执行失败返回真。
- changed,通过任务的返回信息判断,任务执行状态为changed返回真。
- skipped,通过任务的返回信息判断,任务没有满足条件跳过执行,返回真。
示例:
---
- hosts: node1gather_facts: novars:doshell: "yes"tasks:- shell: cat /etc/hostsregister: resultignore_errors: truewhen: doshell == "yes"- name: successdebug:msg: successwhen: result is succeeded- name: faileddebug:msg: failedwhen: result is failed- name: changeddebug:msg: changedwhen: result is changed- name: skippeddebug:msg: skipwhen: result is skipped
in 和 not in 判断
用于判断某个值是否存在于一个 字符串、列表 或 字典键中。
示例1:给用户添加组
[lth@controller web 22:03:18]$ cat playbook.yml
---
- name: test hosts: node1gather_facts: novars:username: devopssupergroup: wheeltasks:- name: gather user informationshell: id {{ username }}register: result- name: Task run if user is in supergroupsuser:name: "{{ username }}"groups: "{{ supergroup }}"append: yeswhen: supergroup not in result.stdout
...[root@node1 ~ 22:03:23]# useradd devops
[root@node1 ~ 22:03:25]# id devops
uid=1091(devops) gid=1091(devops) 组=1091(devops)# 执行
[lth@controller web 22:04:22]$ ansible-playbook playbook.yml PLAY [test] *****************************************************************************************TASK [gather user information] **********************************************************************
changed: [node1]TASK [Task run if user is in supergroups] ***********************************************************
changed: [node1]PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
**示例二:**根据主机所属组控制输出
[lth@controller web 22:06:18]$ cat inventory
controller[nodes]
node1
node2
node3
node4[webs]
node1
node2[dbs]
node3
node4[lth@controller web 22:06:24]$ cat playbook.yml
---
- name: test hosts: node1 node3gather_facts: notasks:- name: install httpdyum:name: httpdstate: presentwhen: inventory_hostname in groups.webs- name: install mariadbyum:name: mariadb state: present when: inventory_hostname in groups.dbs
...# 执行
[lth@controller web 22:06:33]$ ansible-playbook playbook.yml PLAY [test] *****************************************************************************************TASK [install httpd] ********************************************************************************
skipping: [node3]
ok: [node1]TASK [install mariadb] ******************************************************************************
skipping: [node1]
changed: [node3]PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
node3 : ok=1 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
loop 和 when 联合
-
loop:用于对一组元素逐个执行任务
-
when:用于对每一个循环项(item)进行条件判断
当 loop 和 when 一起使用时,when 表达式会对 每个 item 单独判断
示例:当 / 文件系统可用空间大于300000000 安装 mariadb-server
**解决方法:**通过 ansible_facts 获取 / 文件系统可用空间
[lth@controller web 22:10:41]$ cat playbook.yml
---
- name: Combining Loops and Conditional Play hosts: node1tasks:- name: install mariadb-server if enough space on rootyum:name: mariadb-serverstate: latestloop: "{{ ansible_mounts }}"when:- item.mount == "/"- item.size_available > 300000000
...# 执行
[lth@controller web 22:10:41]$ ansible-playbook playbook.yml PLAY [Combining Loops and Conditional Play] *********************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [install mariadb-server if enough space on root] ***********************************************
skipping: [node1] => (item={u'block_used': 35554, u'uuid': u'b5c926d1-b582-436b-95a9-38664b23e619', u'size_total': 1063256064, u'block_total': 259584, u'mount': u'/boot', u'block_available': 224030, u'size_available': 917626880, u'fstype': u'xfs', u'inode_total': 524288, u'options': u'rw,relatime,attr2,inode64,noquota', u'device': u'/dev/sda1', u'inode_used': 326, u'block_size': 4096, u'inode_available': 523962})
ok: [node1] => (item={u'block_used': 524536, u'uuid': u'db4466fc-68b8-4fd7-82b1-0b578187dafa', u'size_total': 53660876800, u'block_total': 13100800, u'mount': u'/', u'block_available': 12576264, u'size_available': 51512377344, u'fstype': u'xfs', u'inode_total': 26214400, u'options': u'rw,relatime,attr2,inode64,noquota', u'device': u'/dev/mapper/centos-root', u'inode_used': 34139, u'block_size': 4096, u'inode_available': 26180261})PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ansible Handlers
Ansible Handlers 功能
功能说明
-
Handlers 是为 “有变更时才执行的操作” 而设计的
-
常用于在某些操作完成后执行额外的步骤,例如:
- 修改配置文件后重启服务
- 更新程序后重载守护进程
- 改变权限后重新应用策略
工作原理
-
任务执行后发生变更(changed)
-
该任务中使用 notify 通知某个 handler
-
Handler 在 playbook 的最后统一执行一次(除非用 meta: flush_handlers 强制提前执行)。
示例 :在远程主机 node1
上部署 Web 服务器
[lth@controller web 22:15:35]$ cat playbook.yml
---
- name: deploy web serverhosts: node1tasks:- name: install packagesyum:name: httpdstate: presentnotify:- enable and restart apache- name: install httpd-manualyum:name: httpd-manualstate: presentnotify:- enable and restart apache- debug: msg: last task in taskshandlers:- name: enable and restart apacheservice:name: httpdstate: restartedenabled: yes
...# 执行
[lth@controller web 22:15:49]$ ansible-playbook playbook.yml PLAY [deploy web server] ****************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [install packages] *****************************************************************************
changed: [node1]TASK [install httpd-manual] *************************************************************************
changed: [node1]TASK [debug] ****************************************************************************************
ok: [node1] => {"msg": "last task in tasks"
}RUNNING HANDLER [enable and restart apache] *********************************************************
changed: [node1]PLAY RECAP ******************************************************************************************
node1 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
处理 Errors
Errors 介绍
在 Ansible 中,错误(Errors)指执行 Playbook 或任务时出现的异常情况,可能由多种原因导致,会中断正常执行流程。了解错误的类型、触发原因及处理方式,对确保自动化任务稳定稳运行至关重要。
示例:
# 环境
[root@node1 ~ 22:19:08]# cp /etc/hosts /etc/myhost
[root@node2 ~ 22:19:11]# rm -f /etc/myhost[lth@controller web 22:19:21]$ vim playbook.yml
[lth@controller web 22:19:36]$ cat playbook.yml
---
- name: testhosts: node1,node2tasks:- name: show /etc/myhostsshell: cat /etc/myhosts- name: echo enddebug:msg: echo end# 执行
[lth@controller web 22:19:38]$ ansible-playbook playbook.yml PLAY [test] *****************************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node2]
ok: [node1]TASK [show /etc/myhosts] ****************************************************************************
fatal: [node2]: FAILED! => {"changed": true, "cmd": "cat /etc/myhosts", "delta": "0:00:00.003767", "end": "2025-08-17 22:19:38.212666", "msg": "non-zero return code", "rc": 1, "start": "2025-08-17 22:19:38.208899", "stderr": "cat: /etc/myhosts: 没有那个文件或目录", "stderr_lines": ["cat: /etc/myhosts: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
fatal: [node1]: FAILED! => {"changed": true, "cmd": "cat /etc/myhosts", "delta": "0:00:00.003663", "end": "2025-08-17 22:19:45.235356", "msg": "non-zero return code", "rc": 1, "start": "2025-08-17 22:19:45.231693", "stderr": "cat: /etc/myhosts: 没有那个文件或目录", "stderr_lines": ["cat: /etc/myhosts: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
node2 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
ignore_errors
您可能希望即使在任务失败时也继续执行play。例如,您或许预期特定任务有可能会失败,并且希望通过有条件地运行某项其他任务来恢复。
ignore_errors可以定义在以下位置:
- 定义在 play 中,则play中所有任务忽略错误。
- 定义在 task 中,则特定task忽略错误。
示例:
[lth@controller web 22:21:51]$ vim playbook.yml
[lth@controller web 22:22:01]$ cat playbook.yml
---
- name: testhosts: node1tasks:- name: install a not exist packageyum:name: notexitpackagestate: presentignore_errors: yesregister: result- name: debug install resultdebug:msg: notexitpackage is not exitwhen: result is failed# 执行
[lth@controller web 22:22:12]$ ansible-playbook playbook.yml PLAY [test] *****************************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [install a not exist package] ******************************************************************
fatal: [node1]: FAILED! => {"changed": false, "msg": "No package matching 'notexitpackage' found available, installed or updated", "rc": 126, "results": ["No package matching 'notexitpackage' found available, installed or updated"]}
...ignoringTASK [debug install result] *************************************************************************
ok: [node1] => {"msg": "notexitpackage is not exit"
}PLAY RECAP ******************************************************************************************
node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
force_handlers
play中使用关键字 force_handlers,控制当play中某个任务执行失败后是否继续执行之前定义的notify,可用值yes或no(也可以是true或false),默认no(false)。
示例:
[lth@controller web 22:23:38]$ vim playbook.yml
[lth@controller web 22:23:43]$ cat playbook.yml
---
- name: testhosts: node1force_handlers: yestasks:- name: a task which always notifies its handlercommand: /bin/truenotify: restart the sshd- name: fails because the package doesn't existyum:name: notexistpkgstate: latesthandlers:- name: restart the sshdservice:name: sshdstate: restarted[lth@controller web 22:23:59]$ ansible-playbook playbook.yml PLAY [test] *****************************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [a task which always notifies its handler] *****************************************************
changed: [node1]TASK [fails because the package doesn't exist] ******************************************************
fatal: [node1]: FAILED! => {"changed": false, "msg": "No package matching 'notexistpkg' found available, installed or updated", "rc": 126, "results": ["No package matching 'notexistpkg' found available, installed or updated"]}RUNNING HANDLER [restart the sshd] ******************************************************************
changed: [node1]PLAY RECAP ******************************************************************************************
node1 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
fail 模块
fail 模块,执行该任务,任务必定 failed。
示例:
[lth@controller web 22:25:19]$ vim playbook.yml
[lth@controller web 22:25:26]$ cat playbook.yml
---
- name: test fail modulehosts: node1gather_facts: notasks:- debug:msg: task1- fail:- debug:msg: task3[lth@controller web 22:25:37]$ ansible-playbook playbook.yml PLAY [test fail module] *****************************************************************************TASK [debug] ****************************************************************************************
ok: [node1] => {"msg": "task1"
}TASK [fail] *****************************************************************************************
fatal: [node1]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
提示:fail模块本身也可以配置when判断,实现说明情况下任务是失败的。
failed_when
指明什么条件下,判定任务执行失败。
示例:
[lth@controller web 22:27:07]$ vim playbook.yml
[lth@controller web 22:27:10]$ cat playbook.yml
- name: test failed_whenhosts: node1tasks:- shell: /root/adduserregister: command_resultfailed_when: "'failed' in command_result.stdout"
环境准备:
[root@node1 ~ 22:27:16]# cat /root/adduser
#!/bin/bash
useradd devops &> /dev/null
if [ $? -eq 0 ];thenecho add user devops success
elseecho add user devops failed
fi
[root@node1 ~ 22:27:24]# chmod +x /root/adduser
以上示例:
- 当devops用户不存在时,shell模块跳过执行。
- 当devops用户存在时,shell模块执行失败。
以上示例可改写为fail模块和when语句联合使用:
- name: test fail modulehosts: node1tasks:- shell: /root/adduserregister: command_result- fail:msg: "add user devops failed"when: "'failed' in command_result.stdout"
验证:
[lth@controller web 22:29:04]$ ansible-playbook playbook.yml PLAY [test failed_when] *****************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [shell] ****************************************************************************************
fatal: [node1]: FAILED! => {"changed": true, "cmd": "/root/adduser", "delta": "0:00:00.005987", "end": "2025-08-17 22:29:04.903010", "failed_when_result": true, "rc": 0, "start": "2025-08-17 22:29:07.897023", "stderr": "", "stderr_lines": [], "stdout": "add user devops failed", "stdout_lines": ["add user devops failed"]}PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
changed_when
指明什么条件下,判定任务执行结果为changed。
示例1:
[lth@controller web 22:35:15]$ vim playbook.yml
[lth@controller web 22:35:23]$ cat playbook.yml
---
- name: changed_whenhosts: node1tasks:- name: upgrade-databaseshell: /usr/local/bin/upgrade-databaseregister: resultchanged_when: "'Success' in result.stdout"notify:- restart_databasehandlers:- name: restart_databaseservice:name: mariadbstate: restarted
环境准备:
[root@node1 ~ 22:35:29]# yum install -y mariadb-server
[root@node1 ~ 22:35:34]# systemctl enable mariadb --now
[root@node1 ~ 22:35:48]# vim /usr/local/bin/upgrade-database
#!/bin/bash
mysql -e 'create user lth@"%" identified by "redhat";' && mysql -e 'GRANT ALL PRIVILEGES on *.* TO lth@"%";' && echo update database Success
[root@node1 ~ 22:35:55]# chmod +x /usr/local/bin/upgrade-database
对于command模块和shell模块,只要命令正常执行,结果状态通常都是changed。可以通过返回码和输出结果来判定它们是否做出更改。
关键字 changed_when: false ,让任务结果状态不为changed,只能报告为ok或failed。
示例2:
[lth@controller web 22:38:33]$ vim playbook.yml
[lth@controller web 22:38:26]$ cat playbook.yml
---
- name: Test Whenhosts: node1gather_facts: notasks:- name: test changed_whenshell: cat /etc/redhat-releasechanged_when: false[lth@controller web 22:38:47]$ ansible-playbook playbook.yml PLAY [Test When] ************************************************************************************TASK [test changed_when] ****************************************************************************
ok: [node1]PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ansible block
多个任务作为block子条目,block作为多个任务整体。
Ansible 的 block
是一种语法结构,可以让你将多个任务组合在一起,并对这些任务统一地使用:
when
条件判断become
权限提升rescue
错误处理always
最终操作(无论成功或失败都执行)
示例:
- name: block examplehosts: node1tasks:- block:- debug:msg: task1- debug:msg: task2when: ansible_distribution == "RedHat"
这里的when判断结果影响block中所有任务,关键字when与block对齐。
对齐语法二:
- name: block examplehosts: node1tasks:- block:- debug:msg: task1- debug:msg: task2when: ansible_distribution == "RedHat"
blocks 还可以与 rescue和always一起使用:
- block:定义主要任务。
- rescue:block中任务执行失败后,就会执行rescue中定义的任务。
- always:无论block和rescue中任务执行是否成功和失败,都会执行的任务。
block配置when指令,同样适用rescue和always。
**示例1:**更新数据库,更新失败要回滚,最后要重启数据库。
- name: upgrade dbblock:- name: upgrade the databaseshell:cmd: /usr/local/lib/upgrade-databaserescue:# 数据库更新失败就回滚- name: revert the database upgradeshell:cmd: /usr/local/lib/revert-databasealways:# 重启数据库- name: always restart the databaseservice:name: mariadbstate: restartedwhen: ansible_distribution == "RedHat" # 此处的when语句将作用于block、rescue和always子句
**示例2:**在所有受管节点上创建符合以下要求的逻辑卷:
-
在research卷组中创建逻辑卷:
- 逻辑卷名称为data
- 逻辑卷大小为4000MiB
- 使用ext4文件系统格式化逻辑卷
- 将逻辑卷挂载到/data目录
- 如果无法创建请求的逻辑卷大小,应显示错误信息:Could not create logical volume of that size 并且应改为使用大小800MiB。
-
如果卷组research不存在,应显示错误信息:Volume does not exist
环境准备:
# node1:添加一块 20G sata 硬盘
[root@node1 ~ 22:44:43]# vgcreate research /dev/sdbPhysical volume "/dev/sdb" successfully created.Volume group "research" successfully created# node2:添加一块 20G sata 硬盘
[root@node2 ~ 22:44:54]# parted /dev/sdb unit MiB mklabel msdos
信息: You may need to update /etc/fstab.[root@node2 ~ 22:45:13]# parted /dev/sdb unit MiB mkpart primary 1 1025
信息: You may need to update /etc/fstab.[root@node2 ~ 22:45:24]# vgcreate research /dev/sdb1 Physical volume "/dev/sdb1" successfully created.Volume group "research" successfully created
playbook.yaml内容如下:
---
- name: create and use lvhosts: alltasks:- block:- name: Create a logical volume of 4000mlvol:vg: researchlv: datasize: 4000rescue:- debug:msg: Could not create logical volume of that size- name: Create a logical volume of 800mlvol:vg: researchlv: datasize: 800always:- name: Create a ext4filesystem:fstype: ext4dev: /dev/research/data- name: create directory /datafile:path: /datastate: directory- name: Mount /dev/research/datamount:path: /datasrc: /dev/research/datafstype: ext4state: mountedwhen: ansible_lvm.vgs.research is defined- name: Volume group does not existdebug:msg: Volume group does not existwhen: ansible_lvm.vgs.research is not defined
验证:
[lth@controller web 22:49:37]$ ansible-playbook playbook.yml PLAY [create logical volume] ************************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node2]
ok: [controller]
ok: [node3]
ok: [node1]
ok: [node4]TASK [Create a logical volume of 4000m] *************************************************************
skipping: [controller]
skipping: [node3]
skipping: [node4]
[WARNING]: The value 4000 (type int) in a string field was converted to u'4000' (type string). If
this does not look like what you expect, quote the entire value to ensure it does not change.
fatal: [node2]: FAILED! => {"changed": false, "err": " Volume group \"research\" has insufficient free space (255 extents): 1000 required.\n", "msg": "Creating logical volume 'data' failed", "rc": 5}
changed: [node1]TASK [Could not create logical volume of that size] *************************************************
ok: [node2] => {"msg": "Could not create logical volume of that size"
}TASK [Create a logical volume of 800m] **************************************************************
[WARNING]: The value 800 (type int) in a string field was converted to u'800' (type string). If this
does not look like what you expect, quote the entire value to ensure it does not change.
changed: [node2]TASK [Create a ext4 filesystem] *********************************************************************
skipping: [controller]
skipping: [node3]
skipping: [node4]
changed: [node2]
changed: [node1]TASK [Create a directory] ***************************************************************************
skipping: [controller]
skipping: [node3]
skipping: [node4]
changed: [node2]
changed: [node1]TASK [Mount up device] ******************************************************************************
skipping: [controller]
skipping: [node3]
skipping: [node4]
changed: [node2]
changed: [node1]TASK [Volume does not exist] ************************************************************************
ok: [controller] => {"msg": "Volume does not exist"
}
skipping: [node1]
skipping: [node2]
ok: [node3] => {"msg": "Volume does not exist"
}
ok: [node4] => {"msg": "Volume does not exist"
}PLAY RECAP ******************************************************************************************
controller : ok=2 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
node1 : ok=5 changed=4 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
node2 : ok=6 changed=4 unreachable=0 failed=0 skipped=1 rescued=1 ignored=0
node3 : ok=2 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
node4 : ok=2 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0# 验证挂载点
[root@node1 ~ 22:51:20]# df /data/
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/mapper/research-data 3966144 15992 3728968 1% /data
[root@node1 ~ 22:51:47]# df /data -h
文件系统 容量 已用 可用 已用% 挂载点
/dev/mapper/research-data 3.8G 16M 3.6G 1% /data