Ansible 任务委派:控制任务在哪里运行

默认情况下,Ansible 会在 playbook 的 hosts 行所匹配的机器上收集 facts,并执行所有任务。通过任务委派,可以让任务在另一台机器或另一组机器上运行,把 facts 归属于指定机器,也可以让整个 playbook 在本地执行。这些方式适合精确地管理彼此关联的环境。例如,更新 Web 服务器时,可能需要暂时把它们从负载均衡池中移除。

移出负载均衡池的操作不能由这些 Web 服务器自身完成。把这类操作委派给 localhost,就能把相关任务保留在同一个 play 中。

不能委派的任务

某些任务始终在控制节点上执行,不能进行委派,包括 include、add_host 和 debug。判断某项 action 能否委派,可以查看它的 connection 属性文档:如果该属性的 support 为 False 或 None,说明该 action 不使用连接,也就不能委派。

委派任务

如果希望在一台主机上执行操作,同时引用其他主机的信息,可以在任务上使用 delegate_to。这适合管理负载均衡池中的节点,或控制停机窗口。把委派与 serial 配合,可以限制每次处理的主机数量:

---
- hosts: webservers
  serial: 5

  tasks:
    - name: Take out of load balancer pool
      ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

    - name: Actual steps would go here
      ansible.builtin.yum:
        name: acme-web-stack
        state: latest

    - name: Add back to load balancer pool
      ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

这里每批处理 5 台 Web 服务器。第一个和第三个任务在 127.0.0.1 上运行,也就是运行 Ansible 的控制机器;中间的更新任务仍在当前 Web 服务器上执行。inventory_hostname 提供当前 play 所处理的 Web 服务器名称,使控制机器知道应该移出或放回哪一个节点。

对于单个任务,还有简写形式 local_action。下面展示同一 playbook 中委派给 127.0.0.1 的任务片段:

---
# ...

  tasks:
    - name: Take out of load balancer pool
      local_action: ansible.builtin.command
      args:
        cmd: /usr/bin/take_out_of_pool {{ inventory_hostname }}
# ...

    - name: Add back to load balancer pool
      local_action: ansible.builtin.command
      args:
        cmd: /usr/bin/add_back_to_pool {{ inventory_hostname }}

也可以通过本地 action 调用 rsync,把文件递归复制到受管理服务器:

---
# ...

  tasks:
    - name: Recursively copy files from management server to target
      local_action: ansible.builtin.command
      args:
        cmd: "rsync -a /path/to/files {{ inventory_hostname }}:/path/to/target/"

要让这个命令不需要交互输入,必须有不需要输入口令的 SSH 密钥,或配置好 ssh-agent;否则 rsync 会提示输入密钥口令。

需要指定更多参数时,可以使用下列写法:

---
# ...

  tasks:
    - name: Send summary mail
      local_action: community.general.mail
      args:
        subject: "Summary Mail"
        to: "{{ mail_recipient }}"
        body: "{{ mail_body }}"
      run_once: True

这里的邮件任务只是展示参数和 run_once 的用法,并没有实际发送邮件。执行前需要根据环境准备所用 collection、邮件参数和真实收件人。

委派时有两个变量规则需要注意:

  • ansible_host 以及其他连接变量如果存在,反映的是受委派主机的信息,而不是 inventory_hostname 指向的主机。
  • 受委派主机不会继承发起委派的主机的变量。

虽然 delegate_to 可以指向 inventory 中不存在的主机,例如一个 IP 地址、DNS 名称或连接插件接受的其他标识,但这样做不会自动把该主机加入 inventory,可能引起问题。这类主机可以继承 “all” 组的变量,前提是 VARIABLE_PRECEDENCE 包含 all_inventory。必须委派给 inventory 之外的主机时,应使用 add_host 模块 把它加入 inventory。

委派上下文中的模板求值

任务被委派后,执行解释器(通常为 Python)、connection、become 和 shell 插件选项的模板求值,会使用受委派主机的变量值。除了 inventory_hostname,这些选项使用的变量来自受委派主机,而不是原始任务主机。

如果这些选项需要原始主机的变量,必须明确通过 hostvars[inventory_hostname]['varname'] 访问。甚至 inventory_hostname_short 也会指向受委派主机,不能依靠它代替原主机的短名称。

委派与并行执行

Ansible 默认并行执行任务。委派不会改变这一点,也不会自动解决并发问题,例如多个 fork 同时写同一个文件。

常见场景是:所有目标主机都委派到同一台机器,并使用 copy、template 或 lineinfile 更新这台机器上的单个文件。任务仍在多个并行 fork 中执行,默认数量为 5,写入可能互相覆盖。

一种处理方式,是让任务只执行一次,再循环处理所有主机:

- name: "handle concurrency with a loop on the hosts with `run_once: true`"
  lineinfile: "<options here>"
  run_once: true
  loop: '{{ ansible_play_hosts_all }}'

这里的 <options here> 是待补充的参数占位符,并不是可以直接用于实际配置的 lineinfile 参数。

还可以增加一个中间 play,并设置 serial: 1,或在任务级设置 throttle: 1。更多执行控制方法见 控制 playbook 执行:策略等。

委派 facts

委派任务可以类比生活中的代办:即使别人把买好的杂货送到你家,杂货仍然属于你。类似地,委派任务收集的 facts 默认归属于 inventory_hostname,也就是当前 play 正在处理的主机,而不是实际产生这些 facts 的受委派主机。

要把收集到的 facts 归给受委派主机,可以设置 delegate_facts: true:

---
- hosts: app_servers

  tasks:
    - name: Gather facts from db servers
      ansible.builtin.setup:
      delegate_to: "{{ item }}"
      delegate_facts: true
      loop: "{{ groups['dbservers'] }}"

这个任务收集 dbservers 组中机器的 facts,并把它们归属于对应的数据库服务器,尽管 play 的目标组是 app_servers。这样,即使 dbservers 没有出现在本次 play 中,或者被 --limit 排除,也能查询 hostvars['dbhost1']['ansible_default_ipv4']['address']。

本地 playbook

有时需要让远程主机在自身本地运行 playbook,而不通过 SSH 连接执行。例如,把 playbook 加入 crontab,可以定期确保系统配置符合要求;也可以在操作系统安装程序中运行 playbook,例如 Anaconda kickstart。

要让整个 playbook 本地执行,把 hosts: 设置为 hosts: 127.0.0.1,然后运行:

ansible-playbook playbook.yml --connection=local

也可以只让某个 play 使用本地连接,即使同一 playbook 的其他 play 使用默认的远程连接类型:

---
- hosts: 127.0.0.1
  connection: local

需要注意:设置 connection: local 而没有设置 ansible_python_interpreter 时,模块会使用 /usr/bin/python,而不会使用 {{ ansible_playbook_python }}。例如,可以在 host_vars/localhost.yml 中设置 ansible_python_interpreter: "{{ ansible_playbook_python }}"。使用 local_action 或 delegate_to: localhost 可以避免这里描述的解释器选择问题。

进一步阅读

示例边界与来源许可

本页 8 组代码保留了官方页面的任务名、参数、注释和缩进。示例中的 take_out_of_pool、add_back_to_pool、acme-web-stack、路径及邮件变量需要由实际环境提供;带省略号的片段没有包含完整 playbook。本次没有运行 Ansible、修改服务器、执行 rsync 或发送邮件。

来源为 Ansible 社区文档 Controlling where tasks run: delegation and local actions。对应正文的 RST 源文件 位于 Ansible 官方文档仓库,版权属于原文的 Ansible 文档贡献者;仓库 COPYING 提供 GNU General Public License 第 3 版。本地包保留完整 LICENSE-GPL-3.0.txt,本整理稿及其可编辑 Markdown 按 GPLv3 提供,适用原许可中的无担保条款。

修改日期:2026-10-03。修改内容:完整汉化六个主要小节及相关链接,保留 8 组原代码,补充占位符、片段和未执行范围说明。对外分发时应同时提供上述完整许可和可编辑稿,保留署名及修改说明。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容