发现变量:事实和魔法变量

使用 Ansible,您可以检索或发现某些包含有关远程系统或 Ansible 本身的信息的变量。与远程系统相关的变量称为事实。使用事实,您可以将一个系统的行为或状态用作其他系统的配置。例如,您可以将一个系统的 IP 地址用作另一个系统的配置值。与 Ansible 相关的变量称为魔法变量。

Ansible 事实

Ansible 事实是与您的远程系统相关的数据,包括操作系统、IP 地址、附加的文件系统等等。您可以在 ansible_facts 变量中访问此数据。默认情况下,您还可以使用 ansible_ 前缀访问一些 Ansible 事实作为顶级变量。您可以使用 INJECT_FACTS_AS_VARS 设置禁用此行为。要查看所有可用的事实,请将此任务添加到剧本中

- name: Print all available facts
  ansible.builtin.debug:
    var: ansible_facts

如果您已经创建了 清单 并配置了有效的 SSH 凭据,您可以在命令行运行此 临时 Ansible 命令 来查看清单中任何主机的“原始”信息

ansible <hostname> -m ansible.builtin.setup

事实包括大量变量数据,可能看起来像这样

{
    "ansible_all_ipv4_addresses": [
        "REDACTED IP ADDRESS"
    ],
    "ansible_all_ipv6_addresses": [
        "REDACTED IPV6 ADDRESS"
    ],
    "ansible_apparmor": {
        "status": "disabled"
    },
    "ansible_architecture": "x86_64",
    "ansible_bios_date": "11/28/2013",
    "ansible_bios_version": "4.1.5",
    "ansible_cmdline": {
        "BOOT_IMAGE": "/boot/vmlinuz-3.10.0-862.14.4.el7.x86_64",
        "console": "ttyS0,115200",
        "no_timer_check": true,
        "nofb": true,
        "nomodeset": true,
        "ro": true,
        "root": "LABEL=cloudimg-rootfs",
        "vga": "normal"
    },
    "ansible_date_time": {
        "date": "2018-10-25",
        "day": "25",
        "epoch": "1540469324",
        "hour": "12",
        "iso8601": "2018-10-25T12:08:44Z",
        "iso8601_basic": "20181025T120844109754",
        "iso8601_basic_short": "20181025T120844",
        "iso8601_micro": "2018-10-25T12:08:44.109968Z",
        "minute": "08",
        "month": "10",
        "second": "44",
        "time": "12:08:44",
        "tz": "UTC",
        "tz_offset": "+0000",
        "weekday": "Thursday",
        "weekday_number": "4",
        "weeknumber": "43",
        "year": "2018"
    },
    "ansible_default_ipv4": {
        "address": "REDACTED",
        "alias": "eth0",
        "broadcast": "REDACTED",
        "gateway": "REDACTED",
        "interface": "eth0",
        "macaddress": "REDACTED",
        "mtu": 1500,
        "netmask": "255.255.255.0",
        "network": "REDACTED",
        "type": "ether"
    },
    "ansible_default_ipv6": {},
    "ansible_device_links": {
        "ids": {},
        "labels": {
            "xvda1": [
                "cloudimg-rootfs"
            ],
            "xvdd": [
                "config-2"
            ]
        },
        "masters": {},
        "uuids": {
            "xvda1": [
                "cac81d61-d0f8-4b47-84aa-b48798239164"
            ],
            "xvdd": [
                "2018-10-25-12-05-57-00"
            ]
        }
    },
    "ansible_devices": {
        "xvda": {
            "holders": [],
            "host": "",
            "links": {
                "ids": [],
                "labels": [],
                "masters": [],
                "uuids": []
            },
            "model": null,
            "partitions": {
                "xvda1": {
                    "holders": [],
                    "links": {
                        "ids": [],
                        "labels": [
                            "cloudimg-rootfs"
                        ],
                        "masters": [],
                        "uuids": [
                            "cac81d61-d0f8-4b47-84aa-b48798239164"
                        ]
                    },
                    "sectors": "83883999",
                    "sectorsize": 512,
                    "size": "40.00 GB",
                    "start": "2048",
                    "uuid": "cac81d61-d0f8-4b47-84aa-b48798239164"
                }
            },
            "removable": "0",
            "rotational": "0",
            "sas_address": null,
            "sas_device_handle": null,
            "scheduler_mode": "deadline",
            "sectors": "83886080",
            "sectorsize": "512",
            "size": "40.00 GB",
            "support_discard": "0",
            "vendor": null,
            "virtual": 1
        },
        "xvdd": {
            "holders": [],
            "host": "",
            "links": {
                "ids": [],
                "labels": [
                    "config-2"
                ],
                "masters": [],
                "uuids": [
                    "2018-10-25-12-05-57-00"
                ]
            },
            "model": null,
            "partitions": {},
            "removable": "0",
            "rotational": "0",
            "sas_address": null,
            "sas_device_handle": null,
            "scheduler_mode": "deadline",
            "sectors": "131072",
            "sectorsize": "512",
            "size": "64.00 MB",
            "support_discard": "0",
            "vendor": null,
            "virtual": 1
        },
        "xvde": {
            "holders": [],
            "host": "",
            "links": {
                "ids": [],
                "labels": [],
                "masters": [],
                "uuids": []
            },
            "model": null,
            "partitions": {
                "xvde1": {
                    "holders": [],
                    "links": {
                        "ids": [],
                        "labels": [],
                        "masters": [],
                        "uuids": []
                    },
                    "sectors": "167770112",
                    "sectorsize": 512,
                    "size": "80.00 GB",
                    "start": "2048",
                    "uuid": null
                }
            },
            "removable": "0",
            "rotational": "0",
            "sas_address": null,
            "sas_device_handle": null,
            "scheduler_mode": "deadline",
            "sectors": "167772160",
            "sectorsize": "512",
            "size": "80.00 GB",
            "support_discard": "0",
            "vendor": null,
            "virtual": 1
        }
    },
    "ansible_distribution": "CentOS",
    "ansible_distribution_file_parsed": true,
    "ansible_distribution_file_path": "/etc/redhat-release",
    "ansible_distribution_file_variety": "RedHat",
    "ansible_distribution_major_version": "7",
    "ansible_distribution_release": "Core",
    "ansible_distribution_version": "7.5.1804",
    "ansible_dns": {
        "nameservers": [
            "127.0.0.1"
        ]
    },
    "ansible_domain": "",
    "ansible_effective_group_id": 1000,
    "ansible_effective_user_id": 1000,
    "ansible_env": {
        "HOME": "/home/zuul",
        "LANG": "en_US.UTF-8",
        "LESSOPEN": "||/usr/bin/lesspipe.sh %s",
        "LOGNAME": "zuul",
        "MAIL": "/var/mail/zuul",
        "PATH": "/usr/local/bin:/usr/bin",
        "PWD": "/home/zuul",
        "SELINUX_LEVEL_REQUESTED": "",
        "SELINUX_ROLE_REQUESTED": "",
        "SELINUX_USE_CURRENT_RANGE": "",
        "SHELL": "/bin/bash",
        "SHLVL": "2",
        "SSH_CLIENT": "REDACTED 55672 22",
        "SSH_CONNECTION": "REDACTED 55672 REDACTED 22",
        "USER": "zuul",
        "XDG_RUNTIME_DIR": "/run/user/1000",
        "XDG_SESSION_ID": "1",
        "_": "/usr/bin/python2"
    },
    "ansible_eth0": {
        "active": true,
        "device": "eth0",
        "ipv4": {
            "address": "REDACTED",
            "broadcast": "REDACTED",
            "netmask": "255.255.255.0",
            "network": "REDACTED"
        },
        "ipv6": [
            {
                "address": "REDACTED",
                "prefix": "64",
                "scope": "link"
            }
        ],
        "macaddress": "REDACTED",
        "module": "xen_netfront",
        "mtu": 1500,
        "pciid": "vif-0",
        "promisc": false,
        "type": "ether"
    },
    "ansible_eth1": {
        "active": true,
        "device": "eth1",
        "ipv4": {
            "address": "REDACTED",
            "broadcast": "REDACTED",
            "netmask": "255.255.224.0",
            "network": "REDACTED"
        },
        "ipv6": [
            {
                "address": "REDACTED",
                "prefix": "64",
                "scope": "link"
            }
        ],
        "macaddress": "REDACTED",
        "module": "xen_netfront",
        "mtu": 1500,
        "pciid": "vif-1",
        "promisc": false,
        "type": "ether"
    },
    "ansible_fips": false,
    "ansible_form_factor": "Other",
    "ansible_fqdn": "centos-7-rax-dfw-0003427354",
    "ansible_hostname": "centos-7-rax-dfw-0003427354",
    "ansible_interfaces": [
        "lo",
        "eth1",
        "eth0"
    ],
    "ansible_is_chroot": false,
    "ansible_kernel": "3.10.0-862.14.4.el7.x86_64",
    "ansible_lo": {
        "active": true,
        "device": "lo",
        "ipv4": {
            "address": "127.0.0.1",
            "broadcast": "host",
            "netmask": "255.0.0.0",
            "network": "127.0.0.0"
        },
        "ipv6": [
            {
                "address": "::1",
                "prefix": "128",
                "scope": "host"
            }
        ],
        "mtu": 65536,
        "promisc": false,
        "type": "loopback"
    },
    "ansible_local": {},
    "ansible_lsb": {
        "codename": "Core",
        "description": "CentOS Linux release 7.5.1804 (Core)",
        "id": "CentOS",
        "major_release": "7",
        "release": "7.5.1804"
    },
    "ansible_machine": "x86_64",
    "ansible_machine_id": "2db133253c984c82aef2fafcce6f2bed",
    "ansible_memfree_mb": 7709,
    "ansible_memory_mb": {
        "nocache": {
            "free": 7804,
            "used": 173
        },
        "real": {
            "free": 7709,
            "total": 7977,
            "used": 268
        },
        "swap": {
            "cached": 0,
            "free": 0,
            "total": 0,
            "used": 0
        }
    },
    "ansible_memtotal_mb": 7977,
    "ansible_mounts": [
        {
            "block_available": 7220998,
            "block_size": 4096,
            "block_total": 9817227,
            "block_used": 2596229,
            "device": "/dev/xvda1",
            "fstype": "ext4",
            "inode_available": 10052341,
            "inode_total": 10419200,
            "inode_used": 366859,
            "mount": "/",
            "options": "rw,seclabel,relatime,data=ordered",
            "size_available": 29577207808,
            "size_total": 40211361792,
            "uuid": "cac81d61-d0f8-4b47-84aa-b48798239164"
        },
        {
            "block_available": 0,
            "block_size": 2048,
            "block_total": 252,
            "block_used": 252,
            "device": "/dev/xvdd",
            "fstype": "iso9660",
            "inode_available": 0,
            "inode_total": 0,
            "inode_used": 0,
            "mount": "/mnt/config",
            "options": "ro,relatime,mode=0700",
            "size_available": 0,
            "size_total": 516096,
            "uuid": "2018-10-25-12-05-57-00"
        }
    ],
    "ansible_nodename": "centos-7-rax-dfw-0003427354",
    "ansible_os_family": "RedHat",
    "ansible_pkg_mgr": "yum",
    "ansible_processor": [
        "0",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "1",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "2",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "3",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "4",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "5",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "6",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "7",
        "GenuineIntel",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz"
    ],
    "ansible_processor_cores": 8,
    "ansible_processor_count": 8,
    "ansible_processor_nproc": 8,
    "ansible_processor_threads_per_core": 1,
    "ansible_processor_vcpus": 8,
    "ansible_product_name": "HVM domU",
    "ansible_product_serial": "REDACTED",
    "ansible_product_uuid": "REDACTED",
    "ansible_product_version": "4.1.5",
    "ansible_python": {
        "executable": "/usr/bin/python2",
        "has_sslcontext": true,
        "type": "CPython",
        "version": {
            "major": 2,
            "micro": 5,
            "minor": 7,
            "releaselevel": "final",
            "serial": 0
        },
        "version_info": [
            2,
            7,
            5,
            "final",
            0
        ]
    },
    "ansible_python_version": "2.7.5",
    "ansible_real_group_id": 1000,
    "ansible_real_user_id": 1000,
    "ansible_selinux": {
        "config_mode": "enforcing",
        "mode": "enforcing",
        "policyvers": 31,
        "status": "enabled",
        "type": "targeted"
    },
    "ansible_selinux_python_present": true,
    "ansible_service_mgr": "systemd",
    "ansible_ssh_host_key_ecdsa_public": "REDACTED KEY VALUE",
    "ansible_ssh_host_key_ed25519_public": "REDACTED KEY VALUE",
    "ansible_ssh_host_key_rsa_public": "REDACTED KEY VALUE",
    "ansible_swapfree_mb": 0,
    "ansible_swaptotal_mb": 0,
    "ansible_system": "Linux",
    "ansible_system_capabilities": [
        ""
    ],
    "ansible_system_capabilities_enforced": "True",
    "ansible_system_vendor": "Xen",
    "ansible_uptime_seconds": 151,
    "ansible_user_dir": "/home/zuul",
    "ansible_user_gecos": "",
    "ansible_user_gid": 1000,
    "ansible_user_id": "zuul",
    "ansible_user_shell": "/bin/bash",
    "ansible_user_uid": 1000,
    "ansible_userspace_architecture": "x86_64",
    "ansible_userspace_bits": "64",
    "ansible_virtualization_role": "guest",
    "ansible_virtualization_type": "xen",
    "gather_subset": [
        "all"
    ],
    "module_setup": true
}

您可以在模板或剧本中引用上面显示的事实中的第一个磁盘的型号,如下所示

{{ ansible_facts['devices']['xvda']['model'] }}

引用系统主机名

{{ ansible_facts['nodename'] }}

您可以在条件语句中使用事实(参见 条件),也可以在模板中使用。您还可以使用事实来创建匹配特定条件的主机的动态组,有关详细信息,请参见 group_by 模块 文档。

注意

因为 ansible_date_time 是在 Ansible 在每次剧本运行之前收集事实时创建和缓存的,所以它在长时间运行的剧本中可能会变得过时。如果您的剧本运行时间很长,请使用 pipe 过滤器(例如,lookup('pipe', 'date +%Y-%m-%d.%H:%M:%S'))或使用 Jinja 2 模板的 now(),而不是使用 ansible_date_time

事实收集的包要求

在某些发行版上,您可能会看到缺少的事实值或设置为默认值的事实,因为默认情况下没有安装支持收集这些事实的软件包。您可以使用操作系统包管理器在远程主机上安装必要的软件包。已知的依赖项包括

  • Linux 网络事实收集 - 依赖于 ip 二进制文件,通常包含在 iproute2 软件包中。

缓存事实

与注册变量一样,事实默认情况下存储在内存中。但是,与注册变量不同,事实可以独立收集并缓存以供重复使用。使用缓存的事实,您可以在配置第二个系统时引用第一个系统的事实,即使 Ansible 首先在第二个系统上执行当前剧本也是如此。例如

{{ hostvars['asdf.example.com']['ansible_facts']['os_family'] }}

缓存由缓存插件控制。默认情况下,Ansible 使用内存缓存插件,该插件在当前剧本运行期间将事实存储在内存中。要保留 Ansible 事实以供重复使用,请选择不同的缓存插件。有关详细信息,请参见 缓存插件

事实缓存可以提高性能。如果您管理数千台主机,您可以配置事实缓存每天晚上运行,然后定期在一天中管理一小部分服务器上的配置。使用缓存的事实,即使您只管理少数服务器,您也可以访问所有主机的变量和信息。

禁用事实

默认情况下,Ansible 在每个剧本开始时收集事实。如果您不需要收集事实(例如,如果您集中了解有关系统的所有信息),您可以在剧本级别关闭事实收集以提高可扩展性。禁用事实可能会特别提高推模式下具有大量系统的性能,或者如果您在实验平台上使用 Ansible。要禁用事实收集

- hosts: whatever
  gather_facts: false

添加自定义事实

Ansible 中的 setup 模块会自动发现有关每个主机的标准事实集。如果您想向事实添加自定义值,您可以编写自定义事实模块,使用 ansible.builtin.set_fact 任务设置临时事实,或者使用 facts.d 目录提供永久的自定义事实。

facts.d 或本地事实

版本 1.3 中的新增功能。

您可以通过将静态文件添加到 facts.d 来添加静态自定义事实,也可以通过将可执行脚本添加到 facts.d 来添加动态事实。例如,您可以通过在 facts.d 中创建和运行脚本,将主机上的所有用户列表添加到您的事实中。

要使用 facts.d,请在远程主机上创建 /etc/ansible/facts.d 目录。如果您更喜欢其他目录,请创建它并使用 fact_path 剧本关键字指定它。将文件添加到目录以提供您的自定义事实。所有文件名必须以 .fact 结尾。这些文件可以是 JSON、INI 或返回 JSON 的可执行文件。

要添加静态事实,只需添加一个具有 .fact 扩展名的文件即可。例如,创建 /etc/ansible/facts.d/preferences.fact,其内容如下

[general]
asdf=1
bar=2

注意

确保文件不可执行,因为这会破坏 ansible.builtin.setup 模块。

下次事实收集运行时,您的事实将包含一个名为 general 的哈希变量事实,其中 asdfbar 作为成员。要验证这一点,请运行以下命令

ansible <hostname> -m ansible.builtin.setup -a "filter=ansible_local"

您将看到添加了您的自定义事实

{
    "ansible_local": {
        "preferences": {
            "general": {
                "asdf" : "1",
                "bar"  : "2"
            }
        }
    }
}

ansible_local 命名空间将 facts.d 创建的自定义 facts 与系统 facts 或 playbook 中其他地方定义的变量分开,因此变量不会相互覆盖。您可以在模板或 playbook 中访问此自定义 fact,例如:

{{ ansible_local['preferences']['general']['asdf'] }}

注意

键值对中的键将被转换为小写,放入 ansible_local 变量中。使用上面的示例,如果 ini 文件在 [general] 部分包含 XYZ=3,则您应该以这种方式访问它:{{ ansible_local['preferences']['general']['xyz'] }} 而不是 {{ ansible_local['preferences']['general']['XYZ'] }}。这是因为 Ansible 使用 Python 的 ConfigParser,它将所有选项名称通过 optionxform 方法,而此方法的默认实现会将选项名称转换为小写。

您也可以使用 facts.d 在远程主机上执行脚本,生成动态自定义 facts 到 ansible_local 命名空间。例如,您可以生成远程主机上所有存在的用户的列表作为该主机的 fact。要使用 facts.d 生成动态自定义 facts,

  1. 编写并测试一个脚本,以生成您想要的 JSON 数据。

  2. 将脚本保存到您的 facts.d 目录中。

  3. 确保您的脚本具有 .fact 文件扩展名。

  4. 确保您的脚本可由 Ansible 连接用户执行。

  5. 收集 facts 以执行脚本并将 JSON 输出添加到 ansible_local。

默认情况下,fact 收集在每个 playbook 开始时只运行一次。如果您在 playbook 中使用 facts.d 创建了自定义 fact,它将在下一个收集 facts 的 playbook 中可用。如果您想在创建它的同一个 playbook 中使用它,您必须显式地重新运行 setup 模块。例如

- hosts: webservers
  tasks:

    - name: Create directory for ansible custom facts
      ansible.builtin.file:
        state: directory
        recurse: true
        path: /etc/ansible/facts.d

    - name: Install custom ipmi fact
      ansible.builtin.copy:
        src: ipmi.fact
        dest: /etc/ansible/facts.d

    - name: Re-read facts after adding custom fact
      ansible.builtin.setup:
        filter: ansible_local

如果您经常使用这种模式,自定义 facts 模块将比 facts.d 更有效率。

关于 Ansible:魔法变量

您可以使用“魔法”变量访问有关 Ansible 操作的信息,包括正在使用的 Python 版本、清单中的主机和组以及 playbook 和角色的目录。与连接变量一样,魔法变量是 特殊变量。魔法变量名是保留的 - 不要使用这些名称设置变量。变量 environment 也是保留的。

最常用的魔法变量是 hostvarsgroupsgroup_namesinventory_hostname。使用 hostvars,您可以在 playbook 的任何地方访问为 playbook 中任何主机定义的变量。您也可以使用 hostvars 变量访问 Ansible facts,但前提是您已经收集(或缓存)了 facts。请注意,在 playbook 对象中定义的变量没有为特定主机定义,因此不会映射到 hostvars。

如果您想使用另一个节点的“fact”的值或分配给另一个节点的清单变量的值来配置您的数据库服务器,您可以在模板或操作行中使用 hostvars

{{ hostvars['test.example.com']['ansible_facts']['distribution'] }}

使用 groups,它是清单中所有组(和主机)的列表,您可以枚举组中的所有主机。例如

{% for host in groups['app_servers'] %}
   # something that applies to all app servers.
{% endfor %}

您可以将 groupshostvars 结合使用,以查找组中的所有 IP 地址。

{% for host in groups['app_servers'] %}
   {{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endfor %}

您可以使用这种方法将前端代理服务器指向应用程序服务器组中的所有主机,以在服务器之间设置正确的防火墙规则,等等。您必须在填写模板的任务之前缓存 facts 或收集这些主机的 facts。

使用 group_names,它是当前主机所在的所有组(数组)的列表,您可以创建基于主机组成员资格(或角色)而不同的模板文件

{% if 'webserver' in group_names %}
   # some part of a configuration file that only applies to webservers
{% endif %}

您可以使用魔法变量 inventory_hostname,即您清单中配置的主机名称,作为 ansible_hostname 的替代方案,当禁用 fact 收集时。如果您有一个很长的 FQDN,您可以使用 inventory_hostname_short,它包含第一个句点之前的部分,不包含域名中的其余部分。

其他有用的魔法变量指的是当前的 playbook 或 playbook。这些变量可能有助于使用多个主机名填写模板,或将列表注入到负载均衡器的规则中。

ansible_play_hosts 是当前 playbook 中仍然处于活动状态的所有主机的列表。

ansible_play_batch 是当前 playbook “批次”范围内的主机名列表。

批次大小由 serial 定义,当未设置时,它等效于整个 playbook(使其与 ansible_play_hosts 相同)。

ansible_playbook_python 是用来调用 Ansible 命令行工具的 Python 可执行文件的路径。

inventory_dir 是包含 Ansible 清单主机文件的目录的路径名。

inventory_file 是指向 Ansible 清单主机文件的路径名和文件名。

playbook_dir 包含 playbook 的基目录。

role_path 包含当前角色的路径名,只在角色内部有效。

ansible_check_mode 是一个布尔值,如果您使用 --check 运行 Ansible,则设置为 True

Ansible 版本

版本 1.8 中新增。

要使 playbook 行为适应不同版本的 Ansible,您可以使用变量 ansible_version,它具有以下结构

{
    "ansible_version": {
        "full": "2.10.1",
        "major": 2,
        "minor": 10,
        "revision": 1,
        "string": "2.10.1"
    }
}