首先,一些基本的定义和规则: 在Ansible中,有一组保留变量,称为魔术 变量。由于它们是保留的,因此用户无法设置它们,如果尝试设置,Ansible 将覆盖它们。
本文特别关注一个神奇变量: hostvars
,它可以在 playbook 运行的任何时刻访问为 play 中的任何主机定义的变量。
大多数 Ansible 用户都知道清单可能包含分配给特定主机的其他变量。有时,在 playbook 运行期间,其他主机可能需要这些与主机关联的变量。在本文中,我将扩展您对使用hostvars
线性主变量与主关系之外的关系的理解。
创建一个简单的库存
首先,我将ini
根据一些通用角色扮演角色类型创建一个简单的类型清单。为了简单起见,我将避免使用groups
or group_vars
。我还将通过使用变量的环回来避免为此练习启动任何其他主机ansible_host
。
库存.ini
servera ansible_host=127.0.0.1 character_type=Bard spell1=Blindness spell2=Confusion
serverb ansible_host=127.0.0.1 character_type=Wizard spell1=Lightning spell2=Fireball
serverc ansible_host=127.0.0.1 character_type=Druid spell1=Poison spell2=Plague
serverd ansible_host=127.0.0.1 character_type=Paladin spell1="Magic Missile" spell2=Fear
每个主机都分配有以下变量:ansible_host
、character_type
、spell1
和spell2
。除 之外的每个值ansible_host
对于清单中的主机来说都是唯一的。
显示主机变量
我将创建一个剧本来展示hostvars
可用的内容。我还将跳过收集事实来简化剧本。
示例1.yml
---
- name: Example playbook to showcase hostvars
hosts: all
connection: local
gather_facts: false
tasks:
- name: Display the specific hostvars that are set in the inventory for each host
ansible.builtin.debug:
var: hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')
注意:您必须安装community.general集合才能使用json_query过滤器。
现在,我将运行第一个剧本:
$ ansible-playbook -i inventory.ini example1.yml
example1.yml 结果
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Display all of the hostvars for each host] *******************************
Sunday 09 April 2023 13:09:46 -0400 (0:00:00.018) 0:00:00.018 **********
ok: [servera] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Bard",
"Blindness",
"Confusion"
]
}
ok: [serverb] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Wizard",
"Lightning",
"Fireball"
]
}
ok: [serverc] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Druid",
"Poison",
"Plague"
]
}
ok: [serverd] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Paladin",
"Magic Missile",
"Fear"
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
正如您在上面看到的,剧本hostvars
在针对每个主机运行时提取了唯一的内容。这是 的典型用法hostvars
。
如果我需要使用来自不同主机的主机变量怎么办?
有时,单个主机可能需要来自另一个主机的变量。我将在第二本剧本中解决这个问题。
示例2.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Set a fact for servera using serverd host variable
ansible.builtin.set_fact:
opponent: "{{ hostvars['serverd']['character_type'] }}"
when: inventory_hostname == "servera"
- name: Print servera hostvars setting derived from a variable from serverd
ansible.builtin.debug:
var: hostvars[inventory_hostname]['opponent']
when:
- opponent is defined
- name: Print PvP message
ansible.builtin.debug:
msg:
- "Round1: {{ character_type }} vs {{ opponent }}"
when: opponent is defined
$ ansible-playbook -i inventory.ini example2.yml
example2.yml 结果
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Set a fact from serverd to use for servera] ******************************
Sunday 09 April 2023 14:34:19 -0400 (0:00:00.029) 0:00:00.030 **********
skipping: [serverb]
ok: [servera]
skipping: [serverc]
skipping: [serverd]
TASK [Print servera hostvars setting derived from a variable from serverd] ***************
Sunday 09 April 2023 14:34:20 -0400 (0:00:00.061) 0:00:00.091 **********
skipping: [serverb]
ok: [servera] => {
"hostvars[inventory_hostname]['opponent']": "Paladin"
}
skipping: [serverc]
skipping: [serverd]
TASK [Print PvP message] *******************************************************
Sunday 09 April 2023 14:34:20 -0400 (0:00:00.079) 0:00:00.170 **********
ok: [servera] => {
"msg": [
"Round1: Bard vs Paladin"
]
}
skipping: [serverb]
skipping: [serverc]
skipping: [serverd]
PLAY RECAP *********************************************************************
servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverc : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverd : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
嗯,那太酷了!使用character_type
变量 from serverd
,剧本可以将其分配为要servera
使用的变量。
如果我需要在所有主机上使用每个主机的所有特定主机变量怎么办?
有时您需要从所有主机动态获取所需的主机变量并在所有服务器上使用它们。我将创建 playbook 3 来展示这一点。
example3.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Set a fact called all_character_types
ansible.builtin.set_fact:
all_character_types: "{{ all_character_types|default([]) + [ hostvars[item]['character_type'] ] }}"
loop: "{{ groups['all'] }}"
run_once: true
- name: Print hostvars that shows servera has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "servera"
- name: Print hostvars that shows serverb has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverb"
- name: Print hostvars that shows serverc has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverc"
- name: Print hostvars that shows serverd has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverd"
现在我将运行 playbook 3:
$ ansible-playbook -i inventory.ini example3.yml
example3.yml 结果
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Set a fact called all_character_types] ******************************************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.030) 0:00:00.030 **********
ok: [servera] => (item=servera)
ok: [servera] => (item=serverb)
ok: [servera] => (item=serverc)
ok: [servera] => (item=serverd)
TASK [Print hostvars that shows servera has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.080) 0:00:00.111 **********
skipping: [serverb]
ok: [servera] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverc]
skipping: [serverd]
TASK [Print hostvars that shows serverb has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.056) 0:00:00.167 **********
skipping: [servera]
skipping: [serverc]
ok: [serverb] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverd]
TASK [Print hostvars that shows serverc has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.054) 0:00:00.221 **********
skipping: [servera]
skipping: [serverb]
ok: [serverc] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverd]
TASK [Print hostvars that shows serverd has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.053) 0:00:00.275 **********
skipping: [servera]
skipping: [serverb]
skipping: [serverc]
ok: [serverd] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
PLAY RECAP *********************************************************************
servera : ok=2 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
在上面的输出中,您可以看到剧本设置了一个名为 的事实all_character_types
。您还可以看到每个主机现在都可以使用该事实。通过根据hostvars
每个主机创建事实,您现在拥有所有character_types
. 您稍后可以使用这个事实。
法术战斗
character_types
我将为每个角色创建一个匹配的剧本和一个随机咒语。
我将循环访问清单中的所有主机,并hostvars
在循环期间利用每个主机。我还将补充剧本以使用以下 Jinja 过滤器:
- 随机的
- 拒绝
example4.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Select a spell to be used against another opponent
ansible.builtin.debug:
msg:
- "{{ character_type }} uses his {{ [spell1,spell2]|random }} spell against {{ hostvars[item]['character_type'] }}"
- "{{ hostvars[item]['character_type'] }} uses {{ hostvars[item][('spell1','spell2')|random] }} spell against {{ character_type }}"
loop: "{{ groups['all']|reject('search',inventory_hostname)|sort|list }}"
现在我将运行 playbook 4:
$ ansible-playbook -i inventory.ini example4.yml
example4.yml 结果
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Select a spell to be used against another opponent] *******************
Sunday 09 April 2023 17:48:38 -0400 (0:00:00.030) 0:00:00.030 **********
ok: [servera] => (item=serverb) => {
"msg": [
"Bard uses his Blindness spell against Wizard",
"Wizard uses Fireball spell against Bard"
]
}
ok: [serverb] => (item=servera) => {
"msg": [
"Wizard uses his Fireball spell against Bard",
"Bard uses Blindness spell against Wizard"
]
}
ok: [serverc] => (item=servera) => {
"msg": [
"Druid uses his Plague spell against Bard",
"Bard uses Confusion spell against Druid"
]
}
ok: [servera] => (item=serverc) => {
"msg": [
"Bard uses his Confusion spell against Druid",
"Druid uses Poison spell against Bard"
]
}
ok: [serverd] => (item=servera) => {
"msg": [
"Paladin uses his Fear spell against Bard",
"Bard uses Confusion spell against Paladin"
]
}
ok: [serverb] => (item=serverc) => {
"msg": [
"Wizard uses his Lightning spell against Druid",
"Druid uses Poison spell against Wizard"
]
}
ok: [serverc] => (item=serverb) => {
"msg": [
"Druid uses his Poison spell against Wizard",
"Wizard uses Lightning spell against Druid"
]
}
ok: [servera] => (item=serverd) => {
"msg": [
"Bard uses his Confusion spell against Paladin",
"Paladin uses Fear spell against Bard"
]
}
ok: [serverb] => (item=serverd) => {
"msg": [
"Wizard uses his Fireball spell against Paladin",
"Paladin uses Fear spell against Wizard"
]
}
ok: [serverd] => (item=serverb) => {
"msg": [
"Paladin uses his Fear spell against Wizard",
"Wizard uses Lightning spell against Paladin"
]
}
ok: [serverc] => (item=serverd) => {
"msg": [
"Druid uses his Poison spell against Paladin",
"Paladin uses Magic Missile spell against Druid"
]
}
ok: [serverd] => (item=serverc) => {
"msg": [
"Paladin uses his Magic Missile spell against Druid",
"Druid uses Plague against spell Paladin"
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在最后一个示例中,我将使用map
和json_query
针对hostvars
. 虽然这与第一个示例非常相似,但我实际上是hostvars
使用 Jinja 地图过滤器来提取的。然后我将该信息传递给json_query
并获取 2 个特定的信息位
注意:json_query
过滤器要求您安装该community.general
集合。
example5.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Print specific hostvars from all groups
ansible.builtin.debug:
var: groups['all'] | map('extract',hostvars)|json_query('[].[character_type,spell2]')
delegate_to: localhost
become: false
run_once: true
现在我将运行 playbook 5:
$ ansible-playbook -i inventory.ini example5.yml
example5.yml 结果
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Print specific hostvars from all groups ] ******************
ok: [servera -> localhost] => {
"groups['all'] | map('extract',hostvars)|json_query('[].[character_type,spell2]')": [
[
"Bard",
"Confusion"
],
[
"Wizard",
"Fireball"
],
[
"Druid",
"Plague"
],
[
"Paladin",
"Fear"
]
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
包起来
就我个人而言,我花了一段时间才开始 hostvars
按照本文所示的方式使用魔法变量。但是,您不必是魔术师才能使用,hostvars
因为魔术就在上面的示例中。摆脱hostvars
困难的幻想,开始在你的剧本、任务和角色中使用它们。您会惊讶地发现它是如此简单,并且您的代码将得到如此大的改进。