RHCA - DO374 | Day06:管理任务执行
一、控制提权
1. 提权策略
Ansible剧本可以通过指令或连接变量来实现不同层次的提权,这些指令主要包括become、become_user、become_method和become_flags。
1)配置默认提权
如果在Ansible配置文件的privilege_escalation部分,将become布尔值设置为true(或yes),则默认情况下,你的剧本中的所有play都会使用权限提升。当在受管主机上运行时,这些剧本会使用become_method指定的方法更改为become_user指定的用户。
使用命令行选项来运行剧本时,也可以覆盖配置文件并指定提权设置,而且命令行选项具有更高的优先级。下面比较了配置指令和命令行选项(如表所示)。
| 配置指令 | 命令行选项 |
| become | --become或-b |
| become_method | --become-method=BECOME_METHOD |
| become_user | --become-user=BECOME_USER |
| become_password | --ask-become-pass 或-K |
2)为剧本中的play配置提权
编写剧本时,有可能需要对个别的play进行提权,这种情况下只需要对特定的play设置提权就行。如果没有这种需求,请使用配置文件或命令行中的默认设置。
以下剧本中包含了三个play。
-
第一个play使用become: true来明确启用提权(无论配置文件或命令行选项指定了什么)。
-
第二个play使用become: false来禁止提权(无论配置文件或命令行选项指定了什么)。
-
第三个play没有become指令,则根据Ansible配置文件或命令行中的默认设置决定是否要提权。
ansible_user_id变量用来显示出运行当前play的受管主机上的用户名称。
---
- name: Become the user "manager"hosts: webserversbecome: true # 第一个play使用 become: truetasks:- name: Show the user used by this playansible.builtin.debug:var: ansible_user_id- name: Do not use privilege escalationhosts: webserversbecome: false # 第二个play使用become: falsetasks:- name: Show the user used by this playansible.builtin.debug:var: ansible_user_id- name: Use privilege escalation based on defaultshosts: webserverstasks:- name: Show the user used by this playansible.builtin.debug:var: ansible_user_id
为确保你的剧本正确使用提权,最好在剧本中作明确指定。取决于具体的task或受管主机,可能还需要在配置设置或清单变量中设置好提权方法及提权的目标用户。
3)为剧本中的task配置提权
允许为剧本中的特定task启用或禁用权限提升,只需将become相关指令添加到task区块。
---
- name: Play with two tasks, one uses privilege escalationhosts: allbecome: false # Play禁用提权tasks:- name: This task needs privilegesansible.builtin.yum:name: perlstate: installedbecome: true # 特定task启用权限- name: This task does not need privilegesansible.builtin.shell: perl -vregister: perlcheckfailed_when: perlcheck.rc != 0
前面这个示例中,默认情况下,剧本已禁止提权,但第一个task启用了权限提升。
4)使用block将需要提权的task分组
如果某个play中有一个子任务需要(或不需要)提权,可以基于block块来设置become指令。block块中的所有task共用一套相同的提权设置,这个设置会覆盖play级别的设置。
例如:
-
为play中的一个子任务启用提权
-
为play中的一个子任务禁用提权
-
为子任务添加become_user指令,来提权为其他普通用户(如数据库用户,而不是root)
要使用这种方法,请将必要的指令添加到block块中。
在下面的示例中,剧本中包含一个默认关闭提权的play。这个play由一个block块(包含两个任务)和一个单独的任务组成。block块中的任务会启用提权,即使默认情况下剧本的提权是关闭的。最后一个任务不使用提权,因为它在block块之外。
---
- name: Deploy web serviceshosts: webserversbecome: falsetasks: - name: Tasks that need privilege escalationblock:- name: Ensure httpd is installedansible.builtin.yum:name: httpdstate: installed- name: Start and enable webserveransible.builtin.service:name: httpdstate: startedenabled: truebecome: true # block块中的任务会启用提权- name: Test website from itself, do not becomeansible.builtin.uri:url: http://{{ ansible_host }}return_content: trueregister: webpagefailed_when: webpage.status != 200
5)在角色中应用提权
可以使用以下任一方法来执行提权:
-
角色本身在其内部或任务上设置提权相关的变量,角色通过文档来指定是否必须设置其他变量(如become_method)才能使用这个角色
-
在Ansible配置文件或剧本文件中自己指定提权设置
或者,也可以在调用角色的play上设置提权,就像在各个task或block上一样。
- name: Example play with one rolehosts: localhostroles:- role: role-namebecome: true
6)利用连接变量配置提权
还可以为清单组或主机配置适当的连接变量,从而来配置提权。下表将配置指令与连接变量名称进行了比较(如表所示)。
| 配置或剧本指令 | 连接变量 |
| become | ansible_become |
| become_method | ansible_become_method |
| become_user | ansible_become_user |
| become_password | ansible_become_pass |
连接变量 具有最高优先级,针对主机、组指定的连接变量会覆盖在Ansible配置或剧本中的配置(比如become_method、become_user、become_pass),一样会覆盖原本在play、task、block以及role中的配置。
下面这个例子展示了如何在YAML清单中为一个组(webservers)指定提权连接变量:
webservers:hosts:servera.lab.example.com:serverb.lab.example.com:vars: # 针对主机组提权ansible_become: true
你也可以只针对某个主机(而不是主机组)来设置提权,这样作有点无聊,除非是一个较大的组中有一个特殊的主机需要特殊的连接设置。
下面这个例子展示了如何只为一个主机(servera.lab.example.com)指定提权连接变量:
webservers:hosts:servera.lab.example.com:ansible_become: true # 针对主机提权serverb.lab.example.com:
同样地,也可以在剧本中设置连接变量,比如为一个play。剧本中的连接变量设置优先级高,会覆盖清单中的变量设置。
---
- name: Example play using connection variableshosts: webserversvars:ansible_become: true # 针对剧本提权tasks:- name: Play will use privilege escalation even if inventory says noansible.builtin.yum:name: perlstate: installed
2. 选择提权方法
Ansible提供了多种配置提权的途径,在选择如何控制提权时,主要应考虑以下两点:
-
尽可能以最低权限运行任务,避免因剧本错误而对受管主机造成无意的损害
-
保持剧本简洁,这样会更容易理解剧本,以及它是否以提权的方式运行
有些用户喜欢总是提权为root的方式运行所有的剧本,这种方法虽然简单,但是会导致一些本来不需要root权限的任务也会以高权限运行,增加了安全风险。
还有一些用户喜欢直接以root用户连接到受管机,这样可以避免提权操作。但这不是一个好方法,因为任何要运行剧本的人都必须在受管主机上拥有root权限,这会很难确定到底是哪一个管理员执行了哪一个剧本。
较好的做法是能够有选择地控制哪些play或task需要提权。例如,如果apache用户能够启动httpd服务,那就没有必要非得以root用户来运行task。如果任务需要,应该以尽可能简单的方式配置提权。
你可以使用become: true为一整个play启用提权,然后使用become: false为不需要提权的任务选择性地禁用提权。
或者,可以将需要提权的任务放到一个play中,将不需要提权的任务放到另一个play中(如果与工作流兼容)。如果整个剧本文件都需要权限提升,则可以在配置文件中设置提权。最大的挑战是,使用同一个剧本的不同主机需要一些其他的提权方法才能正常工作。在这种情况下,当使用剧本中的become提权时,可以为这些例外主机或组设置清单变量(如ansible_become_method)。
3. 课堂练习:控制提权
开始练习(部署环境):
以用户student登入workstation虚拟机,使用lab命令来构建案例环境。
[student@workstation ~]$ lab start task-escalation
步骤说明:
1)从Git仓库https://git.lab.example.com/student/task-escalation.git克隆项目到/home/student/git-repos目录下,并创建exercise分支
创建/home/student/git-repos目录,并进入此目录:
[student@workstation ~]$ mkdir -p ~/git-repos/
[student@workstation ~]$ cd ~/git-repos/
从https://git.lab.example.com/student/task-escalation.git克隆项目,并进入项目目录:
[student@workstation git-repos]$ git clone https://git.lab.example.com/student/task-escalation.git
[student@workstation git-repos]$ cd task-escalation
检出exercise分支:
[student@workstation task-escalation]$ git checkout -b exercise
2)观察项目代码中ansible.cfg文件的全局提权设置,注意become已设置为false,因此任何任务都不会提权运行
[student@workstation task-escalation]$ cat ansible.cfg[privilege_escalation]
become=false
become_method=sudo
become_user=root
become_ask_pass=false
3)分析intranet.yml剧本,确定哪些task需要提权运行
[student@workstation task-escalation]$ cat intranet.yml
---
- name: Enable intranet serviceshosts: servera.lab.example.comtasks:- name: latest version of httpd and firewalld installedansible.builtin.yum:name:- httpd- firewalldstate: latest- name: test html page is installedansible.builtin.copy:content: "Welcome to the example.com intranet!\n"dest: /var/www/html/index.html- name: firewalld enabled and runningansible.builtin.service:name: firewalldenabled: truestate: started- name: firewalld permits http serviceansible.posix.firewalld:service: httppermanent: truestate: enabledimmediate: true- name: httpd enabled and runningansible.builtin.service:name: httpdenabled: truestate: started- name: Test intranet web serverhosts: localhosttasks:- name: connect to intranet web serveransible.builtin.uri:url: http://servera.lab.example.comreturn_content: truestatus_code: 200
第一个play的任务需要提权才能运行,但第二个play的这一个任务就不需要。下面这两种方法任选其一,都可以满足这些要求:
-
将become: true添加到第一个play的顶部。除非另有指定,否则这个设置会使这个play中的每个任务都以提权的方式运行
-
用block块设置把需要提权运行的任务按顺序部署好,然后为整个block设置提权
4)修改intranet.yml剧本,在第一个play中通过block块为多个task作整体提权
[student@workstation task-escalation]$ vim intranet.yml
---
- name: Enable intranet serviceshosts: servera.lab.example.comtasks:- name: Tasks that require privilege escalationbecome: true # 为block块设置提权block:- name: latest version of httpd and firewalld installedansible.builtin.yum:name:- httpd- firewalldstate: latest- name: test html page is installedansible.builtin.copy:content: "Welcome to the example.com intranet!\n"dest: /var/www/html/index.html- name: firewalld enabled and runningansible.builtin.service:name: firewalldenabled: truestate: started- name: firewalld permits http serviceansible.posix.firewalld:service: httppermanent: truestate: enabledimmediate: true- name: httpd enabled and runningansible.builtin.service:name: httpdenabled: truestate: started- name: Test intranet web serverhosts: localhosttasks:- name: connect to intranet web serveransible.builtin.uri:url: http://servera.lab.example.comreturn_content: truestatus_code: 200
5)使用ansible-navigator命令来运行剧本

按Esc键退出ansible-navigator命令。
6)测试剧本执行结果,确保能正常访问到网页
[student@workstation task-escalation]$ curl servera.lab.example.com
Welcome to the example.com intranet!
7)从第一个play中移除block块语句,添加全局become: true指令,以便为整个play启用提权
建议使用git命令恢复原始文件,以避免手动修改intranet.yml文件的麻烦:
[student@workstation task-escalation]$ git restore intranet.yml
为第一个play添加become: true设置,而第二个play不需要提权,直接沿用ansible.cfg文件中的become: false设置:
[student@workstation task-escalation]$ vim intranet.yml
---
- name: Enable intranet serviceshosts: servera.lab.example.combecome: true # 为第一个play提权设置tasks:- name: latest version of httpd and firewalld installedansible.builtin.yum:name:- httpd- firewalldstate: latest- name: test html page is installedansible.builtin.copy:content: "Welcome to the example.com intranet!\n"dest: /var/www/html/index.html- name: firewalld enabled and runningansible.builtin.service:name: firewalldenabled: truestate: started- name: firewalld permits http serviceansible.posix.firewalld:service: httppermanent: truestate: enabledimmediate: true- name: httpd enabled and runningansible.builtin.service:name: httpdenabled: truestate: started- name: Test intranet web serverhosts: localhosttasks:- name: connect to intranet web serveransible.builtin.uri:url: http://servera.lab.example.comreturn_content: truestatus_code: 200
8)使用ansible-navigator命令来运行剧本

按Esc键退出ansible-navigator命令。
9)测试剧本执行结果,确保能正常访问到网页
[student@workstation task-escalation]$ curl servera.lab.example.com
Welcome to the example.com intranet!
结束练习(清理环境):
在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。
[student@workstation ~]$ lab finish task-escalation
二、 控制任务执行
1. 控制任务的执行顺序
在一个play中,Ansible总是先运行roles语句调用的角色中的任务,然后再运行tasks语句指定的一条条任务。下面的剧本包含了一个roles部分和一个tasks部分。
---
- name: Ensure Apache is deployedhosts: www1.example.comgather_facts: falsetasks:- name: Open the firewallansible.posix.firewalld:service: httppermanent: truestate: enabledroles:role: apache
当你运行这个剧本时,Ansible会先运行apache角色里的任务,然后才会运行Open the firewall这个任务,尽管Open the firewall这个任务比apache这个角色在剧本中先定义。
[user@host ~]$ ansible-navigator run deploy_apache.yml -m stdoutPLAY [Ensure Apache is deployed] *****************************************
TASK [apache : Ensure httpd packages are installed] ********************** changed: [www1.example.com]
TASK [apache : Ensure httpd service is started and enabled] ************** changed: [www1.example.com]
TASK [Open the firewall] ************************************************* changed: [www1.example.com]
PLAY RECAP *************************************************************** www1.example.com : ok=3 changed=3 unreachable=0 ...
为了提高可读性,建议在剧本中先写roles语句后写tasks语句,这样就和实际执行顺序一致了。
1)将角色作为task导入或包含进剧本
最新版本的Ansible支持将角色作为任务包含或导入到剧本里,而不是直接使用roles语句来调用角色。这种方法的优点是,可以轻松地运行一组任务、导入或包含角色、然后运行更多任务。潜在的缺点是,在不仔细检查剧本的情况下,想搞清楚你的剧本使用了哪些角色可能会比较费劲。
使用 include_role模块 能够动态包含一个角色,而使用 import_role 模块可以静态地导入一个角色。
下面这个剧本展示了使用include_role模块来包含一个角色:
---
- name: Executing a role as a taskhosts: remote.example.comtasks:- name: A normal taskansible.builtin.debug:msg: 'first task'- name: A task to include role2 hereansible.builtin.include_role:name: role2- name: Another normal taskansible.builtin.debug:msg: 'second task'
-
使用 import_role 导入角色时,ansible-navigator run命令在执行之前先解析并插入角色内容,检测并报告语法错误,如果检测到错误,则不会开始执行剧本(根本不执行)。
-
使用 include_role 包含角色时,Ansible会先执行任务,当遇到include_role时才会解析并插入角色内容,如果此时检测到语法错误,则剧本的执行将中止(只执行了一部分)。
如果将 when指令与include_role模块一起使用,则只有满足when指令中的条件才会将角色的内容包含进来。
2)定义前置任务(pre-tasks)、后置任务(post-tasks)
你可能希望你的剧本在执行正常的角色任务之前先运行一些任务;你也可能希望你的剧本在正常的tasks任务之后再运行某些任务。下面这两个指令就可以起作用了。
-
pre_tasks:用来定义在roles角色部分之前运行的任务
-
post_tasks:用来定义在tasks及通知的handlers之后运行的任务
下面的剧本提供了一个示例,其中包括pre_tasks、roles、tasks、post_tasks和handlers部分。
当然,正常生产环境很少有剧本会同时把这些部分都包含在内。
---
- name: Deploying New Application Versionhosts: webserverspre_tasks:# Stop monitoring the web server to avoid sending false alarms# while the service is updating.- name: Disabling Nagios for this hostcommunity.general.nagios:action: disable_alertshost: "{{ inventory_hostname }}"services: httpdelegate_to: nagios-srvroles:role: deploy-contenttasks:- name: Restarting memcachedansible.builtin.service:name: memcachedstatus: restartednotify: Notifying the support team# Confirm that the application is fully operational# after the update.- name: Validating the new deploymentansible.builtin.uri:url: "http://{{ inventory_hostname }}/healthz"return_content: trueregister: resultfailed_when: "'OK' not in result.content"post_tasks:- name: Enabling Nagios for this hostcommunity.general.nagios:action: enable_alertshost: "{{ inventory_hostname }}"services: httpdelegate_to: nagios-srvhandlers:# Send a message to the support team through Slack# every time the memcached service is restarted.- name: Notifying the support teamcommunity.general.slack:token: G922VJP25/D923DW937/3Ffe373sfhRE6y52Fg3rvf5GNKmsg: 'Memcached on {{ inventory_hostname }} restarted'delegate_to: localhostbecome: false
3)执行顺序回顾
总体来看,Ansible按照以下顺序运行剧本任务:
-
pre_tasks
-
pre_tasks部分通知的handlers
-
roles
-
tasks
-
roles或tasks通知的handlers
-
post_tasks
-
post_tasks部分通知的handlers
如上所述,在一个play中上面这些部分的编写顺序不同,也不会改变它们的执行顺序。例如,如果你把tasks任务部分写在roles角色部分之前,Ansible仍然会先运行roles部分的角色。然而,为了提高剧本的可读性,最好是按照执行顺序来组织剧本内容:pre_tasks、roles、tasks、post_tasks,在play的最末尾定义handlers。
Ansible在以下几个点会运行并刷新通知的handlers:pre_tasks之后、roles和tasks之后、post_tasks之后。因此,如果在多个部分都通知了handlers,则某个handlers处理程序可能会在不同的时间点上多次运行。
要立即运行play中特定任务通知的任何处理程序,请添加一个使用带有flush_handlers参数的元模块的任务。
在下面的示例中:
-
如果这个play 得到 notify 通知,会在部署新配置文件之后,使用应用程序之前,运行Restart api server这个handler处理程序。
-
如果没有对元模块代用flush_handlers参数进行调用,play只会在运行完所有任务后才调用handler处理程序。
-
如果handler处理程序在使用API的最后一个任务之前尚未运行,则该任务可能会失败,因为配置文件已更新,但应用程序尚未重新读取新配置。
---
- name: Updating the application configuration and cache # 更新应用配置和缓存hosts: app_servers # 针对 app_servers 主机组tasks:- name: Deploying the configuration file # task 1:部署配置文件ansible.builtin.template: #使用模板cfg.j2生成配置文件并部署到目标/etc/api-server.cfgsrc: api-server.cfg.j2dest: /etc/api-server.cfgnotify: Restart api server #只是标记Handler为待执行,不会立即执行- name: Running all notified handlers # task 2:立即执行 Handleransible.builtin.meta: flush_handlers # 强制立即执行所有已被通知(notify)但尚未执行的 Handler(此处是 Restart api server)- name: Asking the API server to rebuild its internal cache # task 3:重建 API 服务器缓存ansible.builtin.uri:url: "https://{{ inventory_hostname }}/rest/api/2/cache/"method: POST # 向 API 服务器的 /rest/api/2/cache/ 端点发送 POST 请求,触发缓存重建force_basic_auth: trueuser: adminpassword: redhatbody_format: jsonbody:type: datadelay: 0status_code: 201 # 预期返回 HTTP 状态码 201(表示成功创建)# 补充:此操作依赖前一步 task 2 的服务重启(通过 flush_handlers 确保顺序)handlers:- name: Restart api server # 重启 API 服务ansible.builtin.service: # 当被通知时,重启 api-server 服务并确保开机自启(enabled: true)name: api-serverstate: restartedenabled: true
部署配置文件
如果 使用模板cfg.j2生成配置文件并部署 → 标记 Restart api server 的 Handler 为待执行。
立即执行 Handler
因上一步有变更,带有flush_handlers 则立即执行 Handler,重启 api-server 服务。
重建缓存
向 API 发送请求,确保新配置生效后的缓存一致性。
请记住,在一个play中handlers处理程序具有全局作用域。play可以通知在角色中定义的handler处理程序;一个角色可以通知由另一个角色或这个play自己定义的handler处理程序。
Ansible始终按照handler处理程序的列出顺序来运行已通知的handler,而不是按照通知的顺序。
-
Handler 的执行顺序:始终按照它们在 Playbook 或 Role 中 handlers 部分的 定义顺序 执行,而不是按照任务(Task)中 notify 触发的顺序。
-
通知(Notify)的作用:notify 只是将 Handler 标记为待执行,但不会立即触发它,也不会影响其最终的执行顺序。
Playbook 示例:
tasks:- name: Task Acommand: echo "Task A"notify: handler_2 # 先通知 handler_2- name: Task Bcommand: echo "Task B"notify: handler_1 # 后通知 handler_1handlers:- name: handler_1 # 定义顺序:第1个command: echo "Handler 1"- name: handler_2 # 定义顺序:第2个command: echo "Handler 2"
实际执行顺序:
1.任务运行顺序:Task A → Task B(handler_2 先被通知,handler_1 后被通知)
2.Handler 执行顺序:handler_1 → handler_2(因为它们在 handlers 部分的定义顺序是 handler_1 在前,handler_2 在后)
为什么这样设计?
-
确定性:Ansible 强调幂等性和可预测性。固定 Handler 的执行顺序可以避免因任务通知顺序不同导致的结果差异。
-
依赖管理:如果 Handler 之间有依赖关系(例如 handler_2 需要 handler_1 先运行),通过定义顺序控制比隐式依赖更可靠。
关键总结:
-
通知顺序:由任务中 notify 的先后触发,但仅标记 Handler 为待执行。
-
执行顺序:由 handlers 部分的定义顺序决定,与 notify 顺序无关。
-
执行时机:默认在所有任务结束后执行,或用 meta: flush_handlers 中途强制执行。
⚠️注意:如果多个任务通知同一个 Handler,它会只执行一次。Handler 是幂等的,无论被通知多少次,只会在最后执行一次。 除非使用到Listern。
2. 配置handler监听
除了通过任务直接触发(notify)之外,Handler 处理程序还可以通过订阅特定通知的方式被触发,这使得单个通知能够同时启动多个 Handler。默认情况下,当任务中 notify 指定的名称与某个 Handler 的名称完全匹配时,该 Handler 就会被执行。但由于每个 Handler 必须拥有唯一名称,若想实现"一个通知触发多个 Handler",则需要让这些 Handler 通过 listen 指令监听相同的通知名称。
以下示例演示这种机制:当任务检测到变更时,它会触发名为 "My handlers" 的通知。这个通知不仅会运行直接命名为 "My handlers" 的 Handler,还会触发所有通过 listen 指令监听了 "My handlers" 的其他 Handler。
---
- name: 测试 listen 指令hosts: localhostgather_facts: falsebecome: falsetasks:- name: 触发 handlersansible.builtin.debug:msg: "触发 handlers"notify: My handlers # 通知 My handlers 处理程序changed_when: true # 强制标记任务为"changed"以触发 notifyhandlers:# 监听 "My handlers" 事件- name: 监听通知的 handleransible.builtin.debug:msg: "第一个 handler 被触发"listen: My handlers # 监听 My handlers 通知# 此 handler 也会被触发,因为其名称与通知匹配,# 但注意:不能有两个同名的 handler- name: My handlers # 直接定义名为 My handlers 的 handleransible.builtin.debug:msg: "第二个 handler 被触发"
执行顺序:
-
由于 changed_when: true,该任务强制标记为 changed,触发 notify: My handlers。
-
因为名称完全匹配通知,优先执行。name: My handlers(第二个 Handler)
-
因为监听了相同通知,随后执行。listen: My handlers(第一个 Handler)
RUNNING HANDLER [My handlers] ****************************************
ok: [localhost] => {"msg": "第二个 handler 被触发"
}RUNNING HANDLER [Listening to a notification] *************************
ok: [localhost] => {"msg": "第一个 handler 被触发"
}
在前面的示例 Playbook 中,如果两个 Handler 均使用唯一的名称,并同时通过 listen: My handlers 监听同一个通知事件,将更易于理解 Playbook 的逻辑结构。
listen 指令在与 角色(Role) 结合使用时尤为有用。角色可以通过通知机制触发其内部的 Handler,并明确记录它会在特定事件(如配置变更)发生时发送某个通知(例如 notify: service_restart)。此时,其他角色或 Playbook 也可以监听同一通知,从而扩展响应行为。
典型应用场景示例:当某个服务需要重启时,角色可能仅需触发 notify: service_restart 通知。而在 Playbook 中,你可以定义一个 Handler 来监听此通知,除了重启服务外,还可执行其他关联操作,例如:
-
向监控系统发送服务重启告警
-
重启依赖该服务的其他组件
-
记录审计日志
这种设计实现了松耦合:角色无需知道谁会响应通知,只需声明事件;Playbook 则通过 listen 灵活扩展响应逻辑。
# roles/app_configure/tasks/main.yml
- name: Update service configtemplate:src: config.j2dest: /etc/service.confnotify: service_restart # 角色仅声明需要"服务重启",不定义具体操作handlers:- name: Restart the service # 基础操作:重启服务service:name: my_servicestate: restartedlisten: service_restart # 监听角色发出的通知- name: Notify monitoring system # 扩展操作:告警uri:url: "http://monitor/alert"method: POSTlisten: service_restart # 同一通知触发多动作
通知handler处理程序
总之,一个任务可以至少通过两种方式来通知多个handler处理程序:
-
通知多个不同名称的 Handler:在任务中显式列出多个 Handler 名称(例如 notify: handler_A, handler_B),分别触发独立的 Handler。
-
通过单一名称触发多个 Handler:使用 notify: common_event 通知一个通用名称,所有通过 listen: common_event 监听的 Handler 均会被触发。
若多个任务需复用同一组 Handler,第二种方法(listen)更简洁。优势包括:
-
维护方便:只需修改 Handler 的 listen 配置,无需调整每个任务的 notify 列表。
-
顺序可控:通过调整 handlers 部分的定义顺序即可管理执行顺序。
对比第一种方法:
-
需手动编辑每个任务的 notify 列表添加 Handler 名称。
-
同样需确保 Handler 的排列顺序正确。
注意:若任务触发的通知名称(如 notify: unknown_name)未匹配任何 Handler(包括 name 或 listen),Ansible 会直接报错终止执行。
3. 控制主机的执行顺序
Ansible根据hosts指令来确定受管主机,默认情况下就按照这些主机在清单中的顺序来运行剧本中的任务。通过使用order指令,可以更改主机的执行顺序。
以下剧本按字母顺序对web_servers组中的主机进行排序,然后再执行任务:
---
- name: Testing host orderhosts: web_serversorder: sortedtasks:- name: Creating a test file in /tmpansible.builtin.copy:content: 'This is a sample file'dest: /tmp/test.out
order指令可以接受以下值:
-
inventory:清单顺序,这是默认值
-
reverse_inventory:按清单顺序反序
-
sorted:按字母顺序对主机进行排序,数字在字母之前排序
-
reverse_sorted:按字母顺序反序
-
shuffle:每次运行play时,先将主机列表随机化
使用ansible-navigator命令时,输出结果可能无法反映预期的顺序。实际上Ansible通常是在多个主机上并发运行每个任务,ansible-navigator的输出显示的是任务的完成顺序,而不是执行的顺序。
例如,假设清单已配置为按字母顺序列出主机,并且play按清单顺序受管主机上运行(默认行为),你仍然可以看到这样的输出:
...output omitted...
TASK [Creating a test file in /tmp] *********************************
changed: [www2.example.com]
changed: [www3.example.com]
changed: [www1.example.com]
...output omitted...
注意这里主机www1.example.com上的任务最后才完成的。
4. 课堂练习:控制任务执行
开始练习(部署环境):
以用户student登入workstation虚拟机,使用lab命令来构建案例环境。
[student@workstation ~]$ lab start task-execution
步骤说明:
1)进入/home/student/task-execution/目录
[student@workstation ~]$ cd ~/task-execution
观察 deploy.yml 剧本
[student@workstation task-execution]$ cat deploy.yml
---
- name: ImpNementing Handlershosts: web_serverspre_tasks:- name: Configuring Apacheansible.builtin.include_tasks: apache.ymlroles:- role: firewall
剧本deploy.yml中包含一个play,这个play使用pre_tasks来部署了配置Apache HTTPD服务的任务,使用roles来运行firewall角色以便允许通过防火墙。
2)修改deploy.yml剧本,在pre_task前置任务,添加一些通知handlers的任务
使用 ansible.builtin.set_fact 模块来为 web_port变量 赋值80/tcp、为web_zone变量赋值public, 结合changed_when: true来强制标记任务为"changed"以触发 notify: display variables 的handler处理程序,就像下面这样:
ansible.builtin.set_fact 是 Ansible 中用于动态设置变量的核心模块,它允许在任务执行期间创建或修改变量。
pre_tasks:- name: Configuring Apacheansible.builtin.include_tasks: apache.yml## 添加部分:- name: Setting web port and zone for firewallansible.builtin.set_fact: # 变量赋值web_port: 80/tcpweb_zone: publicchanged_when: truenotify: display variables # 通知 display variables 处理程序
在roles角色部分之后,添加post_tasks部分
其中包含将index.html文件复制到/var/www/html目录的任务。使用ansible.builtin.copy模块复制文件。如果任务更改了受管主机,请通知验证连接的handler处理程序。post_tasks部分的内容应如下所示:
roles:- role: firewall## 添加部分: post_tasks:- name: Ensure the web content is copiedansible.builtin.copy:src: index.htmldest: /var/www/html/notify: verify connectivity
3)为play添加handlers处理程序
-
添加一个名为 display variables 的handler处理程序,同一通知必须同时触发两个处理程序,以便在定义web_port和web_zone变量时显示它们的值。
-
添加一个名为 verify connectivity 的handler处理程序,用来验证如果任务更改了网页内容,当第一个play结束时仍然可以访问http://{{ ansible_facts['fqdn'] }},这说明这个歌play是可用的。
在play的最末尾新建handlers部分,并使用ansible.builtin.debug模块输出web_port和web_zone变量的值,给它们一个任意字符串作为名称,并使用listen指令监听display variables通知,就像下面这样:
post_tasks:- name: Ensure the web content is copiedansible.builtin.copy:src: index.htmldest: /var/www/html/notify: verify connectivity## 添加部分: handlers:- name: Showing the web port configured as pre_taskansible.builtin.debug:var: web_portlisten: display variables- name: Showing the web zone configured as pre_taskansible.builtin.debug:var: web_zonelisten: display variables
为 verify connectivity 通知添加handler处理程序,使用ansible.builtin.uri模块来确认web内容可用。
handlers:- name: Showing the web port configured as pre_taskansible.builtin.debug:var: web_portlisten: display variables- name: Showing the web zone configured as pre_taskansible.builtin.debug:var: web_zonelisten: display variables## 添加部分: - name: verify connectivityansible.builtin.uri:url: http://{{ ansible_facts['fqdn'] }}status_code: 200become: false
完整 deploy.yml 示例:
---
- name: ImpNementing Handlershosts: web_serverspre_tasks:- name: Configuring Apacheansible.builtin.include_tasks: apache.yml- name: Setting web port and zone for firewallansible.builtin.set_fact: # 变量赋值web_port: 80/tcpweb_zone: publicchanged_when: truenotify: display variablesroles:- role: firewallpost_tasks:- name: Ensure the web content is copiedansible.builtin.copy:src: index.htmldest: /var/www/html/notify: verify connectivityhandlers:- name: Showing the web port configured as pre_taskansible.builtin.debug:var: web_portlisten: display variables- name: Showing the web zone configured as pre_taskansible.builtin.debug:var: web_zonelisten: display variables- name: verify connectivityansible.builtin.uri:url: http://{{ ansible_facts['fqdn'] }}status_code: 200become: false
4)运行deploy.yml剧本,并确认Ansible触发了你的handler处理程序(注意处理程序的执行顺序)
[student@workstation task-execution]$ ansible-navigator run -m stdout deploy.yml
PLAY [ImpNementing Handlers] ***************************************************TASK [Gathering Facts] *********************************************************
ok: [serverf.lab.example.com]TASK [Configuring Apache] ******************************************************
included: /home/student/task-execution/apache.yml for serverf.lab.example.comTASK [Installing apache] *******************************************************
changed: [serverf.lab.example.com]TASK [Starting apache] *********************************************************
changed: [serverf.lab.example.com]TASK [Setting web port and zone for firewall] **********************************
changed: [serverf.lab.example.com]RUNNING HANDLER [Showing the web port configured as pre_task] ******************
ok: [serverf.lab.example.com] => {
"web_port": "80/tcp"
}RUNNING HANDLER [Showing the web zone configured as pre_task] ******************
ok: [serverf.lab.example.com] => {
"web_zone": "public"
}TASK [firewall : Ensure FirewaLL Port Configuration] ***************************
changed: [serverf.lab.example.com] => (item={'port': '80/tcp', 'zone': 'public'})RUNNING HANDLER [firewall : reload firewalld] **********************************
changed: [serverf.lab.example.com]TASK [Ensure the web content is copied] ****************************************
changed: [serverf.lab.example.com]RUNNING HANDLER [verify connectivity]
************************************************* ok: [serverf.lab.example.com]PLAY RECAP ********************************************************************* serverf.lab.example.com : ok=11 changed=6 unreachable=0 failed=0 ...
使用curl命令检查,确认剧本已成功更新了受管主机:
[student@workstation task-execution]$ curl serverf.lab.example.com
This is the index file
5)修改 revert.yml 剧本中的2个task来通知handlers,并添加一个新的handler
-
将 notify: package check 语句添加到卸载apache的任务。
-
将 notify: post-handler 语句添加到删除index.html文件的任务。
修改revert.yml文件后,剧本的内容应如下所示:
...output omitted...tasks:- name: Uninstall apacheansible.builtin.yum:name: httpdstate: absentautoremove: truenotify: package check # 通知 package check 应用程序post_tasks:- name: Removing index.html fileansible.builtin.file:path: /var/www/html/index.htmlstate: absentnotify: post handler # 通知 post handler 应用程序
...output omitted...
在 revert.yml 剧本中,创建一个名为post-handler的处理程序,使用ansible.builtin.debug模块显示The index.html file was deleted。
完整的 revert.yml 文件应包括以下内容:
---
- name: Cleaning Firewall ruleshosts: web_serversroles:- role: firewallvars:firewall_rules:- port: 80/tcpzone: publicstate: disabledtasks:- name: Uninstall apacheansible.builtin.yum:name: httpdstate: absentautoremove: truenotify: package check # 通知 package check 应用程序post_tasks:- name: Removing index.html fileansible.builtin.file:path: /var/www/html/index.htmlstate: absentnotify: post handler # 通知 post handler 应用程序handlers:- name: Gather package factsansible.builtin.package_facts:manager: autolisten: package check # 监听 package check 应用程序- name: Check for the httpd packageansible.builtin.debug:msg: "httpd is not installed!"when: "'httpd' not in ansible_facts['packages']"listen: package check # 监听 package check 应用程序- name: post handler # post handler 应用程序ansible.builtin.debug:msg: The index.html file was deleted
6)运行revert.yml剧本,确认Ansible触发了handlers,注意handler的执行顺序,运行revert.yml剧本:
[student@workstation task-execution]$ ansible-navigator run -m stdout revert.yml
PLAY [Cleaning Firewall rules] *************************************************TASK [Gathering Facts] *********************************************************
ok: [serverf.lab.example.com]TASK [firewall : Ensure Firewall Port Configuration] ***************************
changed: [serverf.lab.example.com] => (item={'port': '80/tcp', 'zone': 'public',
'state': 'disabled'})TASK [Uninstall apache] ********************************************************
changed: [serverf.lab.example.com]RUNNING HANDLER [firewall : reload firewalld] **********************************
changed: [serverf.lab.example.com]RUNNING HANDLER [Gather package facts] *****************************************
ok: [serverf.lab.example.com]RUNNING HANDLER [Check for the httpd package] **********************************
ok: [serverf.lab.example.com] => {
"msg": "httpd is not installed!"
}TASK [Removing index.html file] ************************************************
changed: [serverf.lab.example.com]RUNNING HANDLER [post handler] *************************************************
ok: [serverf.lab.example.com] => {
"msg": "The index.html file was deleted"
}PLAY RECAP *********************************************************************
serverf.lab.example.com : ok=9 changed=4 unreachable=0 failed=0 ...
使用curl命令检查,确认剧本已成功更新了受管主机:
[student@workstation task-execution]$ curl serverf.lab.example.com
curl: (7) Failed to connect to serverf.lab.example.com port 80: No route to host
结束练习(清理环境):
在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。
[student@workstation ~]$ lab finish task-execution
三、 运行选定任务
1. 为Ansible资源打标签(tag)
在使用大型或复杂的剧本时,有可能会考虑只运行这个剧本或任务的一个子集(而不是全部)。我们可以为要跳过或运行的特定资源做标记。标签tag指的是为Ansible资源(如play或task)设置的文本标记。使用tags关键字,跟上要应用的标记列表,可以实现打标签的操作。
使用ansible-navigator运行剧本时,结合--tags选项来过滤剧本,可以实现只运行带有对应标签的play或task。
1)标记一个play
---
- name: Setup web services hosts: webserverstags: # 针对一个play- setup
...output omitted...
2)标记一个task
- name: Ensure that packages are installedansible.builtin.yum:name: "{{ packages }}"state: installedtags: # 针对一个task- install
3)标记import_tasks导入的任务(下面这个例子设置了2个标签:webproxy和webserver)
- name: Import common web services tasksimport_tasks: common.ymltags: # 针对 import_tasks 导入的任务- webproxy- webserver
4)标记一个role(下面这个例子设置了2个标签:production和staging)
roles: # 针对 role- { role: databases, tags: ['production', 'staging'] }
5)标记一个block块(下面这个例子将httpd相关的任务组合到一起,标签为webserver)
- name: Install packages and start httpd serviceblock:- name: Ensure httpd packages are installedansible.builtin.yum:name:- httpd- php- git- php-mysqlndstate: present- name: Ensure SELinux allows httpd connections to a remote databaseansible.posix.seboolean:name: httpd_can_network_connect_dbstate: truepersistent: true- name: Ensure httpd service is started and enabledansible.builtin.service:name: httpdstate: startedenabled: truetags: # 针对 block- webserver
2. 管理已标记的资源
使用 ansible-navigator命令运行带有特定标记的任务(--tags),或者跳过带有特定标记(--skip-tags)的任务。
1)运行带有特定标记的task
以下剧本包含两项任务。第一个task的标签为webserver,第二个task没有任何标签。
---
- name: Example play using tagginghosts:- servera.lab.example.com- serverb.lab.example.comtasks:- name: httpd is installedansible.builtin.yum:name: httpdstate: latesttags: webserver- name: postfix is installedansible.builtin.yum:name: postfixstate: latest
要运行其中的第1个task,可以使用 --tags 选项:
[user@host ~]$ ansible-navigator run -m stdout main.yml -i inventory --tags webserverPLAY [Example play using tagging] *********************************************TASK [Gathering Facts] ********************************************************
ok: [serverb.lab.example.com]
ok: [servera.lab.example.com]TASK [httpd is installed] *****************************************************
ok: [servera.lab.example.com]
ok: [serverb.lab.example.com]PLAY RECAP ********************************************************************
由于使用了 --tags 选项,剧本只有运行剧本中带有webserver标签的task。
2)跳过带有特定标记的task
如果使用--skip-tags选项,可以跳过带有指定标签的task,而只运行不带有这个标签的task。
[user@host ~]$ ansible-navigator run -m stdout main.yml -i inventory --skip-tags webserverPLAY [Example play using tagging] *********************************************TASK [Gathering Facts] ********************************************************
ok: [serverb.lab.example.com]
ok: [servera.lab.example.com]TASK [postfix is installed] ***************************************************
ok: [serverb.lab.example.com]
ok: [servera.lab.example.com]PLAY RECAP ********************************************************************
3)选定多个带标签的task
使用--tags、--skip-tags选项时,可以提供逗号分隔的标签列表,来实现多个task的指定。
[user@host ~]$ ansible-navigator run -m stdout main.yml -i inventory --tags install,setup
4)列出剧本中的标签
要列出剧本中定义了哪些标签,可以在使用ansible-navigator命令时添加--list-tags选项。
[user@host examples]$ ansible-navigator run -m stdout playbook.yml -i inventory --list-tagsplaybook: playbook.ymlplay #1 (webservers): Setup web services TAGS: [setup]
TASK TAGS: [setup]play #2 (webservers): Teardown web services TAGS: [teardown]
TASK TAGS: [teardown]
3. 指定特殊标签
在剧本中还可以设置特殊标签:always。标记为always的资源每次都会被运行,即使它不在--tags的标签列表中,除非使用 --skip-tags always 来明确跳过这个标签。
而使用 never 标签的任务不会被运行,除非使用 --tags 来明确指定这个标签。
除此以外,还有三个额外的特殊标签:
-
tagged标签:包括任何带有明确标签的资源
-
untagged标签:包括任何没有明确标签的资源
-
all标签:包括剧本中的所有任务,无论它们是否有标签(这是Ansible的默认行为)
Ansible文档《Tags》:https://docs.ansible.com/ansible/6/user_guide/playbooks_tags.html
4. 课堂练习:运行选定的任务
开始练习(部署环境):
以用户student登入workstation虚拟机,使用lab命令来构建案例环境。
[student@workstation ~]$ lab start task-tagging
步骤说明:
1)从仓库https://git.lab.example.com/student/task-tagging.git克隆项目到/home/student/git-repos目录下,并创建名为exercise的分支
创建好目录/home/student/git-repos并进入此目录:
[student@workstation ~]$ mkdir -p ~/git-repos/
[student@workstation ~]$ cd ~/git-repos/
克隆项目,并进入项目目录:
[student@workstation git-repos]$ git clone https://git.lab.example.com/student/task-tagging.git
Cloning into 'task-tagging'...
...output omitted...
[student@workstation git-repos]$ cd task-tagging
检出exercise分支:
[student@workstation task-tagging]$ git checkout -b exercise
Switched to a new branch 'exercise'
2)新建一个剧本,用来测试web应用是否成功部署
新建 test_webapp.yml 剧本文件,添加一个任务,使用uri模块来检索内容:
[student@workstation task-tagging]$ vim test_webapp.yml
---
- name: Web application smoke testhosts: web_serversgather_facts: falsetasks:- name: Verify content of http://localhost # 测试本地URL内容ansible.builtin.uri:url: http://localhost return_content: trueregister: test_urlfailed_when: "'Hello from' not in test_url['content']" # 检测判断hello from是否在URL内容tags: # 为 task 添加标签- tests
修改site.yml主剧本,将上述test_webapp.yml剧本导入到末尾:
[student@workstation task-tagging]$ vim site.yml
- name: Deploy HAProxyansible.builtin.import_playbook: deploy_haproxy.yml- name: Deploy Web Serveransible.builtin.import_playbook: deploy_apache.yml- name: Deploy Web Appansible.builtin.import_playbook: deploy_webapp.yml- name: Test deployed Web App # 使用 import_playbook 导入新playbookansible.builtin.import_playbook: test_webapp.yml
3)使用site.yml剧本来仅测试Web应用的部署,测试失败后,运行剧本来配置环境,跳过所有测试
在设置之前,使用 --tags 调用tests标签的任务来测试web应用:
[student@workstation task-tagging]$ ansible-navigator run -m stdout site.yml --tags tests
此命令仅运行带有tests标记的任务;由于服务器还没有安装,因此测试会失败。
运行size.yml剧本,这一次使用- -skip-tags 选项跳过tests测试标签的任务:
[student@workstation task-tagging]$ ansible-navigator run -m stdout site.yml --skip-tags tests
...output omitted...
PLAY RECAP *****************************************************************...
servera.lab.example.com : ok=6 changed=6 unreachable=0 failed=0 ...
serverb.lab.example.com : ok=6 changed=6 unreachable=0 failed=0 ...
serverc.lab.example.com : ok=6 changed=6 unreachable=0 failed=0 ...
再次调用tests标签的任务来测试web应用:
[student@workstation task-tagging]$ ansible-navigator run -m stdout site.yml --tags tests
...output omitted...
TASK [Verify content of http://localhost] **********************************...
ok: [serverc.lab.example.com]
ok: [serverb.lab.example.com]PLAY RECAP *****************************************************************...
现在因为web应用已经部署了,所以测试也成功了。
结束练习(清理环境):
在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。
[student@workstation ~]$ lab finish task-tagging
四、 优化任务执行速度
1. 优化剧本执行
可以通过多种方式来优化Ansible剧本。
1)优化基础架构
自动化执行环境的每个版本都做了一些增强和改进,运行最新版本的AAP平台也有助于提高你的剧本的速度,因为Ansible的核心组件,尤其是随附的模块也在不断优化。
在架构优化方面,主要从网络的角度尽量使控制机靠近受管机。Ansible在很大程度上依赖于网络通信和数据传输。控制节点与受管主机之间的高延迟连接或低带宽会降低剧本的执行时间。
2)关闭fact指标收集
每个play都有一个优先运行的隐含,就是使用ansible.buildin.setup模块从每个受管主机收集各种系统指标(facts),方便通过ansible_facts变量使用。但是,在每个远程主机上收集facts需要一定的时间,如果你的剧本不需要使用这些指标,那最好将gather_facts设置为false来关闭指标收集。
以下play就关闭了指标收集:
---
- name: Demonstrate disableing the facts gatheringhosts: web_serversgather_facts: false # 关闭指标收集tasks:- ansible.builtin.debug:msg: "gather_facts is set to False"
以下示例使用 time 命令来比较启用gather_facts和禁用gather_facts时上一个剧本的执行时间。
[user@host ~]$ time ansible-navigator run -m stdout speed_facts.yml -i inventoryPLAY [Demonstrate activating the facts gathering] ************************TASK [Gathering Facts] ***************************************************
ok: [www1.example.com]
ok: [www2.example.com]
ok: [www3.example.com]TASK [debug] *************************************************************
ok: [www1.example.com] => {
"msg": "gather_facts is set to True"
}
ok: [www2.example.com] => {
"msg": "gather_facts is set to True"
}
ok: [www3.example.com] => {
"msg": "gather_facts is set to True"
}PLAY RECAP ***************************************************************
www1.example.com : ok=1 changed=0 unreachable=0 ...
www2.example.com : ok=1 changed=0 unreachable=0 ...
www3.example.com : ok=1 changed=0 unreachable=0 ...real 0m6.171s
user 0m2.146s
sys 0m1.118s
禁用 gather_facts 指标:
[user@host ~]$ time ansible-navigator run -m stdout speed_nofacts.yml -i inventoryPLAY [Demonstrate disabling the facts gathering] *************************TASK [debug] *************************************************************
ok: [www1.example.com] => {
"msg": "gather_facts is set to False"
}
ok: [www2.example.com] => {
"msg": "gather_facts is set to False"
}
ok: [www3.example.com] => {
"msg": "gather_facts is set to False"
}PLAY RECAP ***************************************************************
www1.example.com : ok=1 changed=0 changed=0 changed=0 unreachable=0 ...
www2.example.com : ok=1 changed=0 changed=0 changed=0 unreachable=0 ...
www3.example.com : ok=1 changed=0 changed=0 changed=0 unreachable=0 ...real 0m1.336s
user 0m1.116s
sys 0m0.246s
可以看到对于关闭指标收集的剧本,运行耗时大大缩短了。
剧本中可以使用 ansible_facts['hostname']、ansible_hostname、ansible_facts['nodename']或ansible_nodename 变量来指代当前正在处理的主机。这些变量来自 gather_facts 收集任务,但通常也可以用inventory_hostname 和 inventory_hostname_short 魔法变量替换它们。
即使关闭了指标收集,也可以将 ansible.builtin.setup 模块作为一条任务运行,选择在play中的任何时候手动收集系统指标,然后收集的这些指标可用于后续的play中。
3)使用fact指标缓存(或称事实缓存)来重用收集的facts
Ansible使用 cache缓存插件 来存储由play收集的系统指标或清单数据。通过利用指标缓存,可以重用先前收集的指标,从而限制收集指标的次数。
ansible-navigator 默认情况下会启用memory缓存插件,这个插件会在内存中缓存当前Ansible运行时收集的系统指标。利用这一点可以提高包含多个play的剧本的性能。只要在第1个play中提前收集好系统指标,随后的play就可以关闭指标收集,而直接使用缓存中的系统指标。
下面的剧本包含两个简单的剧本,并说明了它是如何工作的(第一个play中即便没有设置任何task,剧本运行时setup仍然会运行,以便收集系统指标)。
- name: Gather facts for everyonehosts: allgather_facts: true # 第一个Play开启# any tasks we might want for the first play# if you do not have tasks, "setup" will still run- name: The next play, does not gather factshosts: allgather_facts: false # 第二个Play关闭tasks:- name: Show that we still know the factsansible.builtin.debug:var: ansible_facts
另有一种使用指标缓存的方法是启用smart智能收集,只需要为ansible.cfg配置作如下设置:
[defaults]
gathering=smart
smart智能收集只会收集每个新主机的系统指标,如果同一个主机在多个play中用到,则不会重复收集这个主机的系统指标。
默认情况下,指标缓存也适用于自动化控制器。此外,在自动化控制器中编辑作业模板时,你可以选中Enable Fact Storage复选框。这会将指标缓存插件改为保存由该模板启动的作业收集的系统指标的插件,以便在多个剧本运行之间重用这些指标。(如果使用此功能,则需要定期运行收集系统指标的作业以更新自动化控制器的指标存储。)
如果未选中Enable Fact Storage复选框,自动化控制器将使用默认的memory指标缓存插件,这个插件仅在特定作业运行期间缓存系统指标。
4)限制指标收集
如果关闭gather_facts,还可以明确使用ansible.buildin.setup的gather_subset选项来选择性地搜集一部分主机的指标,这通常比收集所有可用的系统指标更快。
-
可能子集包括all、min、hardware、network、virtual、ohai和facter。如果排除所有子集,则仍然得到最小子集。
例如,如果只想检索network子集中的系统指标,可以将其包括在内,但排除all和min:
- name: A play that gathers some factshosts: allgather_facts: falsetasks:- name: Collect only network-related factsansible.builtin.setup:gather_subset: # 针对搜集部分主机指标- '!all'- '!min'- network
5)提高并行数
当Ansible运行play时,它会在这一批次的每个主机上运行第一个任务,然后在这一批次的每个主机中运行第二个任务,以此类推,直到play完成。forks参数控制Ansible可以同时具有活动连接的数量。默认情况下,这个值设置为5,这意味着即使当前任务中有100台主机要处理,Ansible也只能以5台为一组来进行通信。在与所有100个主机通信后,Ansible将转到下一个任务。
例如,play中有5个任务,配置在100台主机上运行,则:
在第1-5台主机上,执行第1个任务;
在第6-10台主机上,执行第1个任务;
在第11-15台主机上,执行第1个任务;
.. ..
在第1-5台主机上,执行第2个任务;
在第6-10台主机上,执行第2个任务;
在第11-15台主机上,执行第2个任务;
.. ..
通过增加forks值,Ansible可以在更多的主机上同时运行每个任务,剧本通常在更短时间内完成。例如,如果将fork设置为100,则Ansible可以尝试同时打开与前一示例中所有100台主机的连接。这给控制节点带来了更多的负载,控制节点仍然需要足够的时间与每个主机进行通信。
在第1-100台主机上,执行第1个任务;
在第1-100台主机上,执行第2个任务;
.. ..
你可以指定要在Ansible配置文件中使用的forks数量,也可以使用 -f选项 与ansible-navigator命令一起使用。
下面是ansible.cfg配置文件[defaults]部分下forks参数的配置示例:
[defaults]
forks=100
因为forks值指定了Ansible启动的工作进程的数量,所以过高的数字可能会降低控制节点和网络的速度。一般建议先设置保守一点的值,如20或50,然后再逐步增加这个数字,每次执行时注意监视系统资源。
6)使用包管理器模块时避免循环
有些模块(比如yum)可以处理一份项目列表,并且不需要使用循环。正确、合理地利用这些模块,可以提高效率,因为模块只需要被调用一次,而不是通过循环来反复调用。
下面这个例子使用ansible.builtin.yum模块来安装多个包,这是安装软件包的最有效方法:
---
- name: Install the packages on the web servershosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure the packages are installedansible.builtin.yum:name:- httpd- mod_ssl- httpd-tools- mariadb-server- mariadb- php- php-mysqlndstate: present
前面这个例子相当于在命令行运行如下命令:
[root@host ~]# yum install httpd mod_ssl httpd-tools mariadb-server mariadb php php-mysqlnd
但如果像下面这样(使用loop来装包),效率就很低了:
---
- name: Install the packages on the web servershosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure the packages are installedansible.builtin.yum:name: "{{ item }}"state: presentloop:- httpd- mod_ssl- httpd-tools- mariadb-server- mariadb- php- php-mysqlnd
因为这样的话,就好比下面这样来多次运行yum命令:
[root@host ~]# yum install httpd
[root@host ~]# yum install mod_ssl
[root@host ~]# yum install httpd-tools
[root@host ~]# yum install mariadb-server
[root@host ~]# yum install mariadb
[root@host ~]# yum install php
[root@host ~]# yum install php-mysqlnd
前面使用loop循环的例子速度会较慢,效率低,因为Ansible运行ansible.builtin.yum模块七次,启动该模块的进程七次,并进行依赖项解析七次。当然,不是所有的Ansible模块都接受像name参数这样的列表。例如,ansible.buildin.service模块只接受单个服务的名称,并且需要一个循环才能对多个服务项进行操作:
- name: Starting the services on the web servershosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure the services are startedansible.builtin.service:name: "{{ item }}"state: startedenabled: trueloop:- httpd- mariadb
要获取不同模块的具体参数可以接受哪些类型的值,可以使用 ansible-navigator doc 命令来查看模块文档(如图-3所示)。

7)向受管主机高效地复制文件
ansible.buitin.copy 模块会递归地将文件和目录复制到受管主机,当目录很大、文件很多时,复制可能需要很长时间。重复运行剧本时,后续的复制所需的时间会变短,因为copy模块只会复制不同的文件。
但是,对于大量文件的复制,建议改用 ansible.posix.synchronize 模块会更高效。这个模块在后台使用rsync,通常比ansible.builtin.copy模块更快。通过将delete选项设置为true,模块还可以删除目标上存在的多余文件(源上不再存在的)。
下面这个例子使用ansible.posix.synchronize模块来将web_content目录递归复制到web_servers主机组的受管机:
---
- name: Deploy the web content on the web servershosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure web content is updatedansible.posix.synchronize:src: web_content/dest: /var/www/html
8)使用模板
ansible.buildin.lineinfile 模块可以用来在文件中插入或删除行,例如配置文件中的配置指令。下面的剧本通过替换几行内容来更新Apache HTTP Server配置文件:
---
- name: Configure the Apache HTTP Serverhosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure proper configuration of the Apache HTTP Serveransible.builtin.lineinfile:dest: /etc/httpd/conf/httpd.confregexp: "{{ item.regexp }}"line: "{{ item.line }}"state: presentloop:- regexp: '^listen 80$'line: 'listen 8181'- regexp: '^ServerAdmin root@localhost'line: 'ServerAdmin support@example.com'- regexp: '^DocumentRoot "/var/www/html"'line: 'DocumentRoot "/var/www/web"'- regexp: '^<Directory "/var/www/html">'line: '<Directory "/var/www/web">'
当与循环一起使用时,ansible.buildin.lineinfile模块效率低下(并且可能容易出错)。在这种情况下,请改用ansible.builtin.template或ansible-builtin.copy模块(预先准备好修改好的httpd.conf.j2网站配置模板文件)。
---
- name: Configure the Apache HTTP Serverhosts: web_serversbecome: truegather_facts: falsetasks:- name: Ensure proper configuration of the Apache HTTP Serveransible.builtin.template:src: httpd.conf.j2dest: /etc/httpd/conf/httpd.conf
9)启用pipelining流水线
为了在远程的受管主机上运行任务,Ansible执行几个SSH操作,将模块及其所有数据复制到远程节点再运行模块。可以激活pipelining流水线功能,Ansible建立更少的SSH连接,提高剧本性能。
若要激活pipelining功能,请在ansible-navigator.yml配置文件的执行环境部分中将ANSIBLE_PIPELINING环境变量的值设置为true。
---
ansible-navigator:ansible:config: ./ansible.cfgexecution-environment:image: ee-supported-rhel8:latestpull-policy: missingenvironment-variables:set:ANSIBLE_PIPELINING: true # 激活pipelining流水线功能
Ansible默认不使用流水线,因为这个功能要求禁用所有远程节点上的requiretty sudo选项。在Red Hat Enterprise Linux 8上,该sudo选项默认情况下就是禁用的,但在其他系统上可能是活动的。要禁用该选项,请使用visudo命令编辑受管节点上的/etc/sudoers文件,并禁用requirety选项:
[root@host ~]# visudo
Defaults !requiretty
2. 使用回调插件分析剧本执行
回调插件通过调整Ansible对各种事件的响应方式来扩展Ansible的功能。其中一些插件会变更ansible-navigator等命令行工具的输出结果,以提供额外的附加信息。例如,启用timer插件会在ansible-navigator命令的输出结果中显示剧本的执行时间。
注意:自动化控制器会记录一些关于作业(运行剧本)的信息,这些信息是根据ansible-navigator的输出结果提取的。因为某些回调插件会修改这个输出结果,所以应该谨慎使用回调插件,或者避免完全使用它们,尤其是当你通过自动化控制器运行剧本的时候。
Ansible自动化平台附带了一组回调插件,在ansible.cfg文件中可以使用callbacks_enabled指令启用这些插件。
[defaults]
callbacks_enabled=timer, profile_tasks, cgroup_perf_recap
使用ansible-navigator doc -t callback -l -m stdout命令可以列出可用的回调插件(如图-4所示)。

使用ansible-navigator doc -t callback plug-in-name -m stdout命令可以访问指定插件的文档(如图-5所示)。

为任务和角色计时
你可以使用timer、profile_tasks和profile_roles回调插件来帮助识别慢速任务和角色。
-
timer插件:显示剧本执行的持续时间
-
profile_tasks插件:在剧本执行结束时按降序显示每个任务的开始时间和在每个任务上花费的时间
-
profile_roles插件:在输出的末尾显示每个角色所花费的时间,按降序排列
要激活这些插件,请在ansible.cfg配置中添加或更新callbacks_enabled指令。不需要每次都启用全部的这3个插件,只需要启用你要用到的插件就好。
[defaults]
callbacks_enabled=timer, profile_tasks, profile_roles
启用这3个插件之后,ansible-navigator命令运行剧本的输出结果会发生变化(如图-6所示)。

3. 课堂练习:优化剧本执行速度
开始练习(部署环境):
以用户student登入workstation虚拟机,使用lab命令来构建案例环境。
[student@workstation ~]$ lab start task-speed
步骤说明:
1)进入/home/student/task-speed/目录,分析deploy_webservers.yml剧本
进入/home/student/task-speed/目录:
[student@workstation ~]$ cd ~/task-speed
查看 deploy_webservers.yml 剧本文件的内容:
[student@workstation task-speed]$ cat deploy_webservers.yml
---
- name: Deploy the web servershosts: web_serversbecome: truetasks:- name: Ensure required packages are installedansible.builtin.yum:name: "{{ item }}"state: presentloop: # 需要优化 yum 模块的loop循环- httpd- mod_ssl- httpd-tools- mariadb-server- mariadb- php- php-mysqlnd- name: Ensure the services are enabledansible.builtin.service:name: "{{ item }}"state: startedenabled: trueloop:- httpd- mariadb- name: Ensure the web content is installedansible.builtin.copy: # 需要优化 copy 模块修改为同步模块src: web_content/dest: /var/www/html
这个剧本用来装包、起服务,以及将本地目录复制到受管主机。
2)激活timer和profile_tasks回调插件,再运行deploy_webservers.yml剧本
修改ansible.cfg文件,添加callbacks_enabled指令和需要激活的两个回调插件:
[defaults]
inventory=inventory.yml
remote_user=devops
callbacks_enabled=timer, profile_tasks
运行deploy_webservers.yml剧本,并记下总运行时间:
[student@workstation task-speed]$ ansible-navigator run -m stdout deploy_webservers.yml
...output omitted...
PLAY RECAP **************************************************************
serverb.lab.example.com : ok=4 changed=3 unreachable=0 ...
serverc.lab.example.com : ok=4 changed=3 unreachable=0 ...Playbook run took 0 days, 0 hours, 1 minutes, 22 seconds # 记录运行时间
Wednesday 09 November 2022 20:27:38 +0000 (0:00:34.966) 0:01:22.396 ****
=========================================================================
Ensure required packages are installed --------------------------- 38.29s
Ensure the web content is installed ------------------------------ 34.97s
Ensure the services are enabled ----------------------------------- 7.14s Gathering Facts 1.96s
你的系统中实际的执行时间可能不同。但是,web内容部署和包安装应该是最耗时的任务。
3)优化deploy_webservers.yml剧本。请关闭系统指标收集,因为剧本不需要使用系统指标;还要删除包安装任务中的循环,并用ansible.posix.synchronize模块替换ansible.buitin.copy模块
提示:如果把文件改错了,可以从 deploy_webservers.yml.backup 文件恢复原始文件。
修改deploy_webservers.yml剧本,添加gather_facts指令并将其设置为false:
[student@workstation task-speed]$ vim deploy_webservers.yml
---
- name: Deploy the web servershosts: web_serversbecome: truegather_facts: false # 关闭系统指标收集...output omitted...
从软件包安装任务中删除循环:
...output omitted...tasks:- name: Ensure required packages are installedansible.builtin.yum: # 删除包安装任务中的循环,使用列表方式name:- httpd- mod_ssL- httpd-tools- mariadb-server- mariadb- php- php-mysqlndstate: present
...output omitted...
下一个任务使用ansible.buildin.service模块启动httpd和mariadb服务。此任务还使用循环。但是,与ansible.builtin.yum模块不同的是,ansible.builtin.service模块在其name参数中不接受服务列表,因此服务这项任务请保持原样。
在最后一个任务中,将ansible.builtin.copy模块替换为ansible.posix.synchronize模块,这在部署大目录时更高效。
...output omitted...- name: Ensure the web content is installedansible.posix.synchronize: # 替换ansible.posix.synchronize模块src: web_content/dest: /var/www/html
保存更改并关闭编辑器。
4)运行clean.yml剧本来清理受管主机,等清理完毕,再运行优化过的deploy_webservers.yml剧本,并将执行时间与其第一次执行时间进行比较
运行clean.yml剧本:
[student@workstation task-speed]$ ansible-navigator run -m stdout clean.yml
运行deploy_webservers.yml剧本:
[student@workstation task-speed]$ ansible-navigator run -m stdout deploy_webservers.yml
...output omitted...
PLAY RECAP ************************************************************** serverb.lab.example.com : ok=3 changed=3 unreachable=0 ... serverc.lab.example.com : ok=3 changed=3 unreachable=0 ...Playbook run took 0 days, 0 hours, 0 minutes, 22 seconds
Wednesday 09 November 2022 20:40:41 +0000 (0:00:01.524) 0:00:22.276 ****
=========================================================================
Ensure required packages are installed --------------------------- 17.16s
Ensure the services are enabled ----------------------------------- 3.56s
Ensure the web content is installed ------------------------------- 1.52s
请注意,收集系统指标的任务不存在了。剧本和任务的执行时间也应该减少。
5)禁用timer和profile_tasks回调插件
删除ansible.cfg配置文件中的callbacks_enabled行:
[student@workstation task-speed]$ cat ansible.cfg
[defaults]
inventory=inventory.yml
remote_user=devops
结束练习(清理环境):
在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。
[student@workstation ~]$ lab finish task-speed
五、 综合实验:管理任务执行
[student@workstation task-review]$ vim deploy_apache.yml
---
- name: Ensure Apache is deployedhosts: web_serversforce_handlers: truegather_facts: falsebecome: true # 设置提权roles:# The "apache" role has a dependency on the "firewall" role.# The "firewall" role requires the "firewall_rules" variable.- role: apache
开始实验(部署环境):
以用户student登入workstation虚拟机,使用lab命令来构建案例环境。
[student@workstation ~]$ lab start task-review
解决方案:
1. 从https://git.lab.example.com/student/task-review.gitrepository仓库克隆项目到/home/student/git-repos目录,并创建exercise分支
1)克隆项目到/home/student/git-repos目录
[student@workstation git-repos]$ git clone https://git.lab.example.com/student/task-review.git
[student@workstation git-repos]$ cd task-review/
2)检出exercise分支
[student@workstation task-review]$ git checkout -b exercise
2. 修改ansible.cfg文件,设置默认禁用提权,因firewall、haproxy、apache和webapp角色中的任务需要提权,可修改deploy_apache.yml、deploy_haproxy.yml和deploy_webapp.yml剧本在play级别作提权设置
1)修改ansible.cfg文件,将提权部分的become=true改成become=false,或者删除整个[privilege_escalation]配置块
[student@workstation task-review]$ vim ansible.cfg
[defaults]
inventory=inventory.yml
remote_user=devops
2)修改deploy_apache.yml剧本,为play设置become: true提权
[student@workstation task-review]$ vim deploy_apache.yml
---
- name: Ensure Apache is deployedhosts: web_serversforce_handlers: truegather_facts: falsebecome: true # 设置提权roles:# The "apache" role has a dependency on the "firewall" role.# The "firewall" role requires the "firewall_rules" variable.- role: apache
3)修改deploy_haproxy.yml剧本,为play设置become: true提权
[student@workstation task-review]$ vim deploy_haproxy.yml
---
- name: Ensure HAProxy is deployedhosts: lb_serversforce_handlers: truegather_facts: falsebecome: true # 设置提权roles:# The "haproxy" role has a dependency on the "firewall" role.# The "firewall" role requires the "firewall_rules" variable.- role: haproxy
4)修改deploy_webapp.yml剧本,为play设置become: true提权
[student@workstation task-review]$ vim deploy_webapp.yml
---
- name: Ensure Web App is deployedhosts: web_serversgather_facts: falsebecome: true # 设置提权vars:- webapp_version: v1.0roles:- role: webapp
3. 在deploy_haproxy.yml剧本中,添加一个前置的copy任务。用来将文本“Playbook site.yml ready to start”写入到lb_servers组的受管主机的/tmp/site.ready文件
修改deploy_haproxy.yml文件,添加一个pre_tasks块及copy复制任务:
[student@workstation task-review]$ vim deploy_haproxy.yml
---
- name: Ensure HAProxy is deployedhosts: lb_serversforce_handlers: truegather_facts: falsebecome: truepre_tasks: # 添加前置任务(用于提醒剧本开始)- name: Setting the maintenance messageansible.builtin.copy:dest: /tmp/site.readycontent: "Playbook site.yml ready to start\n"roles:# The "haproxy" role has a dependency on the "firewall" role.# The "firewall" role requires the "firewall_rules" variable.- role: haproxy
4. 向haproxy角色添加一个名为haproxy filehandler的处理程序,将消息Reloaded写入/tmp/haproxy.status文件。如果在haproxy角色中使用template模块的任务报告状态为changed,通知这个handler处理程序
1)修改roles/haproxy/handlers/main.yml文件,添加复制文本“Reloaded”到状态文件的处理程序,必要时可以在字串末尾添加一个换行符
[student@workstation task-review]$ vim roles/haproxy/handlers/main.yml
---
# handlers file for haproxy- name: restart haproxyansible.builtin.service:name: haproxystate: restarted- name: reload haproxyansible.builtin.service:name: haproxystate: reloaded- name: haproxy filehandler # 添加handler处理程序ansible.builtin.copy:dest: /tmp/haproxy.statuscontent: "Reloaded\n"
2)修改roles/haproxy/tasks/main.yml文件,确保Ensure haproxy configuration is set任务在报告当前系统状态有变更时通知haproxy filehandler处理程序
因为修改后的任务要通知两个handler处理程序,所以必须将现有的reload haproxy处理程序移到一个单独的行。修改后的文件包含以下内容:
[student@workstation task-review]$ vim roles/haproxy/tasks/main.yml
---
# tasks file for haproxy- name: Ensure haproxy packages are presentansible.builtin.yum:name:- haproxy- socatstate: present- name: Ensure haproxy is started and enabledansible.builtin.service:name: haproxystate: startedenabled: true- name: Ensure haproxy configuration is setansible.builtin.template:src: haproxy.cfg.j2dest: /etc/haproxy/haproxy.cfgowner: rootgroup: rootmode: 0644notify:- reload haproxy- haproxy filehandler # 通知haproxy filehandler处理程序
5. 为apache角色中的yum任务添加apache_installer标签
修改roles/apache/tasks/main.yml文件,为yum任务配置好apache_installer 标签,修改好的文件包含以下内容:
[student@workstation task-review]$ vim roles/apache/tasks/main.yml
---
# tasks file for apache- name: Ensure httpd packages are installedansible.builtin.yum:name: "{{ item }}"state: presentloop:- httpd- php- git- php-mysqlndtags: apache_installer # 添加apache_installer标签- name: Ensure SELinux allows httpd connections to a remote databaseansible.posix.seboolean:name: httpd_can_network_connect_dbstate: truepersistent: true- name: Ensure httpd service is started and enabledansible.builtin.service:name: httpdstate: startedenabled: true
6. 为剧本启用timer和profile_tasks回调插件。这两个插件是ansible.posix集合的一部分,必要时可以为回调插件指定FQCN
修改ansible.cfg配置文件,添加callbacks_enabled语句来启用回调插件,修改好的文件包含以下内容:
[student@workstation task-review]$ vim ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
callbacks_enabled=ansible.posix.timer,ansible.posix.profile_tasks
7. 使用ansible-navigator导航器以stdout模式运行site.yml剧本,分析输出结果找到耗时最多的任务
以stdout模式运行site.yml剧本,输出可能会像这样:
[student@workstation task-review]$ ansible-navigator run site.yml -m stdout
前面的输出根据每个任务完成所需的时间对任务进行排序。以下任务耗时最多:
apache : Ensure httpd packages are installed --------------------------- 25.32s
8. 优化剧本以提高剧本运行效率,优化前后可运行cleanup_apache.yml剧本来清理web服务器,以便比较结果。要验证更改,请运行带有适当标记的site.yml剧本,以便只运行修改后的任务
使用grep找到包含Ensure httpd package任务的文件,其中-R表示递归搜索,-l限制仅显示文件名:
[student@workstation task-review]$ grep -Rl 'Ensure httpd packages are installed'
roles/apache/tasks/main.yml
site-artifact-2025-05-18T10:46:53.703876+00:00.json
修改roles/apache/tasks/main.yml文件,删除yum任务中的循环,改成一次性事务,修改后的文件包含以下内容:
[student@workstation task-review]$ vim roles/apache/tasks/main.yml
---
# tasks file for apache- name: Ensure httpd packages are installedansible.builtin.yum: # 删除yum任务中的循环,改成一次性事务name:- httpd- php- git- php-mysqLndstate: presenttags: apache_installer- name: Ensure SELinux allows httpd connections to a remote databaseansible.posix.seboolean:name: httpd_can_network_connect_dbstate: truepersistent: true- name: Ensure httpd service is started and enabledansible.builtin.service:name: httpdstate: startedenabled: true
运行cleanup_apache.yml剧本清理web服务器。
[student@workstation task-review]$ ansible-navigator run cleanup_apache.yml -m stdout
使用导航器在stdout模式下运行带有apache_installer标记的site.yml剧本。确认这一次Ensure httpd package任务完成所需的时间更短。
[student@workstation task-review]$ ansible-navigator run site.yml --tags apache_installer -m stdout
...output omitted...
PLAY RECAP *********************************************************************
serverb.lab.example.com : ok=1 changed=1 unreachable=0 failed=0 ...
serverc.lab.example.com : ok=1 changed=1 unreachable=0 failed=0 ...
Playbook run took 0 days, 0 hours, 0 minutes, 15 seconds
Monday 07 November 2022 21:56:20 +0000 (0:00:14.971) 0:00:15.060 *******
===============================================================================
apache : Ensure httpd packages are installed --------------------------- 14.97s
9. 提交更改并将其推送到远程Git存储库
执行git add、git commit、git push系列操作,Git密码为Student@123:
[student@workstation task-review]$ git add .
[student@workstation task-review]$ git commit -m "Lab updates"
[student@workstation task-review]$ git push -u origin exercise
Password for 'https://student@git.lab.example.com': Student@123
结束实验(清理环境):
在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。
[student@workstation ~]$ lab finish task-review
思维导图:

小结:
本篇为 【RHCA认证 - DO374 | Day06:管理任务执行】的学习笔记,希望这篇笔记可以让您初步了解如何控制提权、 控制任务执行、 运行选定任务、优化任务执行速度、综合实验:管理任务执行等,不妨跟着我的笔记步伐亲自实践一下吧
Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关环境、视频,可评论666并私信小安,请放下你的羞涩,花点时间直到你真正的理解。
