发现变量:事实和魔法变量
使用 Ansible,咱们可以检索或发现一些包含远端系统,或 Ansible 本身信息的变量。与远端系统相关的变量,被称为事实,facts。有了事实,咱们就可以把一个系统的行为或状态,用作别的系统的配置。例如,咱们可以把某个系统的 IP 地址,作为另一系统的配置值。与 Ansible 相关的变量,被称为魔法变量,magic variables。
Ansible 事实
所谓 Ansible 事实,是与咱们远端系统相关的数据,包括操作系统、IP 地址、连接的文件系统等。咱们可在 ansible_facts
变量中,访问这些数据。默认情况下,咱们也可以 ansible_
前缀的顶层变量形式,访问某些 Ansible 事实。咱们可使用 INJECT_FACTS_AS_VARS
,设置禁用这一行为。要查看所有可用事实,请将此任务添加到某个 play:
- name: Print all available facts
ansible.builtin.debug:
var: ansible_facts
而要查看收集到的 “原始” 信息,请在命令行下运行此命令:
ansible <hostname> -m ansible.builtin.setup
译注:这里仍然需要指定仓库文件,如下所示:
ansible -i ansible_quickstart/inventory_updated.yaml debian-199 -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
}
咱们可在模板或 playbook 中,引用以上所示事实中第一个磁盘的型号:
{{ ansible_facts['devices']['xvda']['model'] }}
要引用系统的主机名:
{{ ansible_facts['nodename'] }}
可以在条件(参见 条件)以及模板中,使用事实。咱们还可以使用事实,创建符合特定条件的动态主机组,详情请查看 group_by
模组 文档。
注意:由于
ansible_date_time
是每次运行 playbook 前,Ansible 收集事实时创建并缓存的,因此在一些长时间运行的 playbook 下,就可能会过时。如果咱们的 playbook 需要长时间运行,就要使用pipe
过滤器(例如,lookup('pipe', 'date +%Y-%m-%d.%H:%M:%S')
),或使用 Jinja 2 模板的now()
,代替ansible_date_time
。
事实收集的软件包需求
在某些发行版上,咱们可能会看到一些缺失事实值,或设置为默认值的事实,因为支持收集这些事实的软件包默认未安装。咱们可在远端主机上,使用操作系统的软件包管理器,安装这些必要软件包。已知的依赖包包括:
- Linux 的网络事实收集 - 依赖于
ip
这个二进制命令,通常包含在iproute2
软件包中。
缓存事实
与注册的变量一样,事实默认都存储在内存中。不过,与注册的变量不同,事实可被独立收集并缓存下来,供重复使用。有了缓存的事实,在配置某第二个系统时,即使 Ansible 先在第二个系统上执行当前 play,咱们也可以参考某一个系统中的事实。例如:
{{ hostvars['asdf.example.com']['ansible_facts']['os_family'] }}
缓存是由缓存插件控制的。默认情况下,Ansible 使用的是在当前 playbook 运行期间,将事实存储在内存中的内存缓存插件。要保留 Ansible 事实以供重复使用,就要选择别的缓存插件。详情请参阅 缓存插件。
事实的缓存,可以提高性能。若咱们管理着数千台主机,那么就可以将事实缓存配置为每晚运行,然后在全天定期对较小的某组服务器上的配置进行管理。有了缓存事实,即使只管理少量服务器,咱们也能访问所有主机的变量和信息。
关闭事实
默认情况下,Ansible 会在每个 play 开始时收集事实。如果咱们不需要收集事实(例如,如果咱们对咱们的系统所有情况都了如指掌),就可以在 play 级别关闭事实收集,以提高可扩张性。在托管系统数量非常多的推送模式下,或者在实验平台上使用 Ansible,关闭事实收集尤其能提高性能。要关闭事实收集:
- hosts: whatever
gather_facts: false
添加定制事实
Ansible 中的设置模组,会自动发现每台主机的标准事实集。若咱们打算在事实中,添加一些定制的值,则可以编写某个定制的事实模组、使用一个 ansible.builtin.set_fact
任务设置一些临时事实,或使用 facts.d
目录提供一些永久的定制事实。
facts.d
或本地事实
版本 1.3 中的新特性。
咱们可以通过向 facts.d
添加静态文件,来添加一些静态定制事实,或者向 facts.d
添加可执行脚本来添加一些动态事实。例如,咱们可以通过在 facts.d
中创建并运行脚本,来添加主机上所有用户的一个列表。
要使用 facts.d
,就在远端主机上,创建 /etc/ansible/facts.d
目录。若咱们偏好别的目录,可以创建出目录,并使用 fact_path
这个 play 关键字指定出来。向该目录添加文件,以提供到咱们的定制事实。所有文件名必须以 .fact
结尾。文件可以是 JSON、INI 或返回 JSON 的可执行文件。
要添加静态事实,只需添加扩展名为 .fact
的文件即可。例如,创建包含以下内容的 /etc/ansible/facts.d/preferences.fact
:
[general]
asdf=1
bar=2
注意:要确保该文件不可执行,否则会破坏
ansible.builtin.setup
模组。
下次事实收集运行时,咱们的事实将包含一个名为 general
的哈希变量事实,其成员为 asdf
和 bar
。要验证这点,请运行以下命令:
ansible -i ansible_quickstart/inventory_updated.yaml almalinux-5 -m ansible.builtin.setup -a "filter=ansible_local"
咱们将看到,咱们的定制事实已添加:
almalinux-5 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"preferences": {
"general": {
"asdf": "1",
"bar": "2"
}
}
},
"discovered_interpreter_python": "/usr/bin/python3.9"
},
"changed": false
}
ansible_local
这个命名空间,将由 facts.d
创建的自定义事实,与系统事实或 playbook 中其他地方定义的变量分开,因此变量之间不会相互覆盖。咱们可在模板或 playbook 中,访问该定制事实:
{{ ansible_local['preferences']['general']['asdf'] }}
注意:
key=value
键值对中的关键字部分,在ansible_local
变量中将被转换为小写。以上面的例子为例,如果那个ini
文件的[general]
小节包含了XYZ=3
,那么访问他的方式应该是{{ ansible_local['preferences']['general']['xyz'] }}
,而不是{{ ansible_local['preferences']['general']['XYZ'] }}
。这是因为 Ansible 使用了 Python 的ConfigParser
,他通过optionxform
方法,传递所有选项名,而该方法的默认实现会将选项名转换为小写。
咱们还可使用 facts.d
,在远端主机上执行脚本,向 ansible_local
命名空间生成一些动态的定制事实。例如,咱们可以生成一个远端主机上所有用户的列表,作为该主机的一项事实。要使用 facts.d
生成动态的定制事实:
- 编写并测试一个生成所需 JSON 数据的脚本;
- 将该脚本保存在咱们的
facts.d
目录下; - 确保咱们的脚本有着
.fact
文件扩展名; - 确保咱们的搅拌可由 Ansible 连接用户执行;
- 要开启
gather_facts
以执行到该脚本,并将其 JSON 输出添加到ansible_local
。
译注:通常咱们需要借助
jq
这个实用工具,从脚本产生 JSON 输出。参考:
默认情况下,事实收集会在每个 play 开始时运行一次。如果咱们在某个 playbook 中使用 facts.d
创建了某项定制事实,那么他将在下一个收集事实的 play 中可用。如果咱们想在创建事实的同一 play 中使用他,就必须显式地重新运行 setup
模组。例如:
- hosts: webservers
gather_facts: no
tasks:
- name: Install the package jq, tool for JSON like sed for text
ansible.builtin.package:
name: jq
state: present
- 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
mode: '0755'
dest: /etc/ansible/facts.d
- name: Re-read facts after adding custom fact
ansible.builtin.setup:
filter: ansible_local
- name: Show ansible_local
debug:
msg: "{{ ansible_local['ipmi'] }}"
如果咱们频繁使用这种模式,那么定制事实模组,会比 facts.d
效率更高。
译注:下面是个输出 Linux 主机上用户列表的脚本,其借助
jq
产生 JSON 字符串。
#!/usr/bin/env bash
user_list=$(
getent passwd | \
grep -vE '(nologin|false)$' | \
awk -F: -v min=`awk '/^UID_MIN/ {print $2}' /etc/login.defs` \
-v max=`awk '/^UID_MAX/ {print $2}' /etc/login.defs` \
'{if(($3 >= min)&&($3 <= max)) print $1}' | \
sort -u
)
users_str=""
while IFS=' ' read -ra u; do
users_str="${users_str}${u},"
done <<< "$user_list"
users_str="${users_str::-1}"
json_str=$(
jq --null-input \
--arg users "${users_str}" \
'{users: $users}'
)
echo "${json_str}"
关于 Ansible 的信息:魔法变量
咱们可以使用 “魔法” 变量,访问有关 Ansible 操作的信息,包括正使用的 Python 版本、仓库中的主机和组,以及 playbook 和角色的目录等。与连接变量一样,魔法变量也属于 特殊变量。魔法变量名称是保留的 - 因此不要使用这些名称设置变量。environment
这个变量也是保留的。
最常用的魔法变量分别是 hostvars
、groups
、group_names
及 inventory_hostname
。使用 hostvars
,咱们就能在 playbook 的任何位置,访问为 play 中任何主机定义的变量。咱们也可使用 hostvars
这个变量,访问到 Ansible 事实,但只能在收集(或缓存)了事实之后。请注意,在 play 的对象中定义的变量,不是为特定主机定义的,因此不会映射到 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 %}
咱们可以同时使用 groups
和 hostvars
,找到某个组中的所有 IP 地址。
{% for host in groups['app_servers'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endfor %}
译注:模板文件中的组名
app_servers
要与 playbook YAML 文件中hosts: app_servers
组别一致,否则会报出错误:"AnsibleUndefinedVariable: 'dict object' has no attribute 'enp1s0'"
。
咱们可以使用这种方法,将某个前端代理服务器,指向咱们应用程序服务器组中的所有主机,以及在服务器之间设置正确的防火墙规则等。在填充模板的任务前,咱们必须为这些主机缓存好事实,或收集到事实。
使用 group_names
,即当前主机所在的全部组的一个列表(数组),咱们就可以创建出根据主机的所属组(或角色),而不同的模板文件:
{% if 'webserver' in group_names %}
# some part of a configuration file that only applies to webservers
{% endif %}
当事实收集被关闭时,咱们可以使用魔法变量 inventory_hostname
,即在仓库中配置的主机名,作为 ansible_hostname
的替代。如果咱们的 FQDN 较长,则可以使用 inventory_hostname_short
,其包含第一个句点之前的部分,而不包含域的其余部分。
别的一些有用魔法变量,指向了当前 play 或 playbook。对于要以多个主机名的填充模板,或将列表注入负载均衡器的规则中等情况,这些变量就很有用。
-
ansible_play_hosts
为当前 play 中,仍处于活动状态的所有主机列表; -
ansible_play_batch
是该 play 的当前 “批次” 范围内的主机名列表; 批次数量由serial
定义,在未设置时相当于整个 play(从而令到其与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
这个变量,其具有以下结构:
{
"full": "2.18.1",
"major": 2,
"minor": 18,
"revision": 1,
"string": "2.18.1"
}
(End)