无需任何魔法即可使用 Ansible 的神奇变量“hostvars”

2023年 8月 27日 40.3k 0

黑桃 A 的魔术

首先,一些基本的定义和规则: 在Ansible中,有一组保留变量,称为魔术 变量。由于它们是保留的,因此用户无法设置它们,如果尝试设置,Ansible 将覆盖它们。

本文特别关注一个神奇变量:  hostvars,它可以在 playbook 运行的任何时刻访问为 play 中的任何主机定义的变量。

大多数 Ansible 用户都知道清单可能包含分配给特定主机的其他变量。有时,在 playbook 运行期间,其他主机可能需要这些与主机关联的变量。在本文中,我将扩展您对使用hostvars线性主变量与主关系之外的关系的理解。

创建一个简单的库存

首先,我将ini根据一些通用角色扮演角色类型创建一个简单的类型清单。为了简单起见,我将避免使用groupsor 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_hostcharacter_typespell1spell2。除 之外的每个值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   

在最后一个示例中,我将使用mapjson_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困难的幻想,开始在你的剧本、任务和角色中使用它们。您会惊讶地发现它是如此简单,并且您的代码将得到如此大的改进。

相关文章

对接alertmanager创建钉钉卡片(1)
手把手教你搭建OpenFalcon监控系统
openobseve HA本地单集群模式
基于k8s上loggie/vector/openobserve日志收集
openobseve单节点和查询语法
2023 年需要学习和掌握的 30 个最佳 DevOps 工具:Git、Docker 等

发布评论