Ansible 任务委派
任务委派
先说两个场景:
- 通过 Ansible 部署 K8S 集群(假设用
kubeadm
初始化集群),Playbook 刚开始会对all
主机组进行初始化配置(安装软件包,设置sysctl
、selinux
等),之后需要在一个节点执行kubeadm init
启动一个集群,但是这个命令只需要执行一次并不需要在所有节点执行 - 配置负载均衡集群,需要在负载均衡节点添加后端服务器的信息,需要在负载均衡节点执行
针对这两种情况,Ansible 提供了委派功能,委派实现的是将原本在其他节点执行的任务委派给一个特定主机,详细如图:
以 ansible.builtin.shell
模块为例,默认情况下模块会在所有被控节点执行,如果使用委派,那么原本应该在三个节点执行的任务会全部放到被委派的节点执行。
将所有任务委派到一个节点
看下边这个 YAML:
- name: test delegate_to
hosts: all
gather_facts: true
tasks:
- name: delegate_to command
ansible.builtin.command: "echo {{ ansible_facts.hostname }}"
delegate_to: servera
register: delegate_to
- name: debug
ansible.builtin.debug:
var: delegate_to.stdout
- name: delegate_to shell
ansible.builtin.shell: "echo {{ ansible_facts.hostname }} >> /tmp/testlog"
delegate_to: servera
上边这个就是将 ansible.builtin.command
委派给 servera
,如果有三个主机 servera
、serverb
、serverc
,就会有如下输出:
[root@study ansible]# ansible-playbook delegate.yml
PLAY [test delegate_to] *******************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************
ok: [serverb]
ok: [servera]
ok: [serverc]
TASK [delegate_to command] ****************************************************************************************************************************************
changed: [servera]
changed: [serverb -> servera]
changed: [serverc -> servera]
TASK [debug] ******************************************************************************************************************************************************
ok: [servera] => {
"delegate_to.stdout": "servera"
}
ok: [serverc] => {
"delegate_to.stdout": "serverc"
}
ok: [serverb] => {
"delegate_to.stdout": "serverb"
}
TASK [delegate_to shell] ******************************************************************************************************************************************
changed: [serverc -> servera]
changed: [servera]
changed: [serverb -> servera]
PLAY RECAP ********************************************************************************************************************************************************
servera : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@study ansible]# ssh servera cat /tmp/testlog
serverc
servera
serverb
可以看到,任务都在 servera
上执行了,但是使用的变量还是原被控节点的,检查委派节点的 /tmp/testlog
也可以看到有三个节点的主机名,所以可以通过委派实现所有任务在一个节点执行。
不是所有模块都适合委派,比方说
ansible.builtin.copy
在使用委派时,只会将文件复制到委派的节点。(想要实现委派主机复制文件到其他节点可以使用ansible.posix.synchronize
)委派默认会将所有任务放在委派节点执行,但是有时我们只希望任务只执行一次(比方说只想通过
ansible.builtin.command
在某个节点执行一次命令),这时就需要使用run_once
,这个看下边。
只在某个节点执行一次命令
delegate_to
可以和 run_once
结合使用,以实现只在某个节点执行一次命令,看下边这个 YAML:
- name: test delegate_to
hosts: all
become: true
gather_facts: false
tasks:
- name: file delegate to servera
ansible.builtin.file:
path: /tmp/hostlist
state: touch
delegate_to: servera
run_once: true
- name: lineinfile delegate to servera
ansible.builtin.lineinfile:
path: /tmp/hostlist
line: "{{ inventory_hostname }}"
delegate_to: servera
- name: shell delegate to servera
ansible.builtin.shell:
cmd: "echo 'test kubeadm init' > /tmp/kubeadm.log"
delegate_to: servera
run_once: true
这里用 echo 'test kubeadm init' > /tmp/kubeadm.log
代替 K8S 初始化,这个命令只需要在第一个节点执行,其他节点就不需要执行了,所以添加了 run_once: true
。
在本地执行委派
如果要在控制节点执行委派任务,可以使用 local_action
,它等效于 delegate_to: localhost
。
- name: file delegate to localhost
ansible.builtin.file:
path: /tmp/log
state: touch
delegate_to: localhost
run_once: true
- name: lineinfile delegate to localhost
ansible.builtin.lineinfile:
path: /tmp/log
line: "{{ inventory_hostname }}"
delegate_to: localhost
# 上下效果一样
- name: file with local_action
local_action:
module: ansible.builtin.file
path: /tmp/log
state: touch
- name: lineinfile with local_action
local_action: ansible.builtin.lineinfile path=/tmp/log line="{{ inventory_hostname }}"
# 上边这个也可以写成下边这个格式
- name: file with local_action
local_action:
module: ansible.builtin.file
path: /tmp/log
state: touch
- name: lineinfile with local_action
local_action:
module: ansible.builtin.lineinfile
path: /tmp/log
line: "{{ inventory_hostname }}"
事实委派
事实委派可以将不在被执行范围内节点的 ansible_facts
输出给其他节点,举个例子:
下边这个 Playbook 只对 webserver
主机组执行任务,但是有个任务需要使用 dbserver
主机组中的变量,因为 dbserver
主机组不在执行范围内,默认情况下无法获取到 dbserver
组的事实变量,所以需要通过 delegate_facts: true
来将 dbserver
事实变量委派给 webserver
主机组。
- name: test delegate_facts
hosts: webserver
become: true
gather_facts: true
tasks:
- name: file delegate to servera
ansible.builtin.setup:
delegate_to: "{{ item }}"
delegate_facts: true
loop: "{{ groups['dbserver'] }}"
- name: print hostname from dbserver groups
debug:
var: hostvars['{{ item }}']['ansible_facts'].hostname
loop: "{{ groups['dbserver'] }}"
- name: print hostname with delegate
debug:
var: ansible_facts.hostname
delegate_to: servera
默认情况下,anisble_facts.hostname
只会打印原本要被执行任务的节点的信息,比方说将 serverb
的任务委派给 servera
,那么结果就是任务在 servera
执行,但是输出是 serverb
的 anisble_facts.hostname
,可以看下边的输出:
[root@study ansible]# ansible-playbook delegate_test.yml
PLAY [test delegate_facts] ****************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************
ok: [servera]
ok: [serverc]
TASK [file delegate to servera] ***********************************************************************************************************************************
ok: [serverc -> serverb] => (item=serverb)
ok: [servera -> serverb] => (item=serverb)
TASK [print hostname from dbserver groups] ************************************************************************************************************************
ok: [servera] => (item=serverb) => {
"ansible_loop_var": "item",
"hostvars['serverb']['ansible_facts'].hostname": "serverb",
"item": "serverb"
}
ok: [serverc] => (item=serverb) => {
"ansible_loop_var": "item",
"hostvars['serverb']['ansible_facts'].hostname": "serverb",
"item": "serverb"
}
TASK [print hostname with delegate] *******************************************************************************************************************************
ok: [servera] => {
"ansible_facts.hostname": "servera"
}
ok: [serverc -> servera] => {
"ansible_facts.hostname": "serverc"
}
PLAY RECAP ********************************************************************************************************************************************************
servera : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
所以如果想获取特定主机的变量,需要通过 hostvars[]
,比方说获取 serverb
的信息,可以写 hostvars['serverb']
。