Ansible 动态主机清单
使用动态主机清单
Ansible 会根据提供的主机清单执行任务,默认我们会在主机清单中将所有主机列出来(静态主机清单),这种方式在传统的数据中心是没有问题的,因为传统的数据中心不会频繁的更换主机。但是在虚拟化或者云环境中仍使用静态主机清单可能不是一个很好的选择,因为虚拟化或者云环境中虚拟机数量可能会频繁变化,今天创建 3 台,明天删除 2 台,这个时候如果还用静态主机清单就需要反复修改清单以保证主机清单的准确性。
Ansible 支持动态的获取主机信息,以下列出了部分集合中可用的主机清单插件。
[root@remote-host ansible]# ansible-navigator doc -t inventory -l
[DEPRECATION WARNING]: community.general.stackpath_compute has been deprecated. The company and the service were sunset in June 2024. This feature will be removed from community.general in version
11.0.0. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
ansible.builtin.advanced_host_list Parses a 'host list' with ranges
ansible.builtin.auto Loads and executes an inventory plugin specified in a YAML config
ansible.builtin.constructed Uses Jinja2 to construct vars and groups based on existing inventory
ansible.builtin.generator Uses Jinja2 to construct hosts and groups from patterns
ansible.builtin.host_list Parses a 'host list' string
ansible.builtin.ini Uses an Ansible INI file as inventory source
ansible.builtin.script Executes an inventory script that returns JSON
ansible.builtin.toml Uses a specific TOML file as an inventory source
ansible.builtin.yaml Uses a specific YAML file as an inventory source
community.dns.hetzner_dns_records Create inventory from Hetzner DNS records
community.dns.hosttech_dns_records Create inventory from Hosttech DNS records
community.docker.docker_containers Ansible dynamic inventory plugin for Docker containers
community.docker.docker_machine Docker Machine inventory source
community.docker.docker_swarm Ansible dynamic inventory plugin for Docker swarm nodes
community.general.cobbler Cobbler inventory source
community.general.gitlab_runners Ansible dynamic inventory plugin for GitLab runners
community.general.icinga2 Icinga2 inventory source
community.general.iocage iocage inventory source
community.general.linode Ansible dynamic inventory plugin for Linode
community.general.lxd Returns Ansible inventory from lxd host
community.general.nmap Uses nmap to find hosts to target
community.general.online Scaleway (previously Online SAS or Online.net) inventory source
community.general.opennebula OpenNebula inventory source
community.general.proxmox Proxmox inventory source
community.general.scaleway Scaleway inventory source
community.general.stackpath_compute StackPath Edge Computing inventory source
community.general.virtualbox virtualbox inventory source
community.general.xen_orchestra Xen Orchestra inventory source
community.libvirt.libvirt Libvirt inventory source
containers.podman.buildah_containers Inventory plugin that discovers Buildah working containers as hosts
containers.podman.podman_containers Inventory plugin that discovers Podman containers as hosts
以下命令可以查看 PVE 的主机清单插件内容(就是 python 脚本):
[root@remote-host ansible]# ansible-navigator exec -- \
cat /usr/share/ansible/collections/ansible_collections/community/general/plugins/inventory/proxmox.py
接下来用 PVE 虚拟化平台来做个演示,PVE 虚拟化平台可以通过 community.general.proxmox 主机清单插件来获取主机信息:
[root@remote-host ansible]# cat inventory/proxmox.yml
# inventory/proxmox.yml
plugin: community.general.proxmox
url: https://192.168.50.206:8006
user: root@pam
password: "redhat"
validate_certs: false
exclude_nodes: true
want_facts: true
want_proxmox_nodes_ansible_host: false
这里要注意几点:
- 针对 PVE,动态主机清单文件名必须是
proxmox.yml plugin: community.general.proxmox表示调用community.general.proxmox主机清单插件exclude_nodes设置为true用于将 PVE 物理节点从主机清单中排除want_facts设置为true表示将虚拟机部分信息设置为 Ansible 变量want_proxmox_nodes_ansible_host用于设置是否自动将虚拟机第一个地址设置为ansible_host,建议为false
我们来查询下主机列表:
[root@remote-host ansible]# ansible-navigator inventory \
-i inventory/proxmox.yml --yaml --list
all:
children:
proxmox_all_qemu:
hosts:
mysql:
proxmox_agent: '1'
proxmox_agent_interfaces:
- ip-addresses:
- 127.0.0.1/8
- ::1/128
mac-address: 00:00:00:00:00:00
name: lo
- ip-addresses:
- 192.168.50.219/24
- fe80::6cf5:1ff:fe25:fd25/64
mac-address: 6e:f5:01:25:fd:25
name: ens18
proxmox_boot:
order: scsi0;ide2;net0
proxmox_cores: 4
proxmox_cpu: x86-64-v2-AES
proxmox_digest: b077ccef985e0f4b23f8062dda94b77f4c222687
proxmox_ide2:
disk_image: local:iso/Fedora-Server-dvd-x86_64-43-1.6.iso
media: cdrom
size: 3402816K
proxmox_ipconfig0:
gw: 192.168.50.1
ip: 192.168.50.219/24
proxmox_memory: 8192
proxmox_meta:
creation-qemu: 8.0.2
ctime: '1769431780'
proxmox_name: mysql
proxmox_net0:
bridge: vmbr0
firewall: '1'
virtio: 6E:F5:01:25:FD:25
proxmox_node: pve
proxmox_numa: 0
proxmox_ostype: l26
proxmox_qmpstatus: running
proxmox_sata0:
disk_image: local:101/vm-101-cloudinit.qcow2
media: cdrom
proxmox_scsi0:
disk_image: local-lvm:vm-101-disk-0
iothread: '1'
size: 32G
proxmox_scsihw:
disk_image: virtio-scsi-single
proxmox_smbios1:
uuid: 5aacfba5-8167-4465-97d7-1567f3d98a45
proxmox_snapshots: []
proxmox_sockets: 1
proxmox_status: running
proxmox_tags: database
proxmox_tags_parsed:
- database
proxmox_vmgenid: 3a88a3f5-210f-451a-bd1a-8701d18abec7
proxmox_vmid: 101
proxmox_vmtype: qemu
web:
proxmox_agent: '1'
proxmox_agent_interfaces:
- ip-addresses:
- 127.0.0.1/8
- ::1/128
mac-address: 00:00:00:00:00:00
name: lo
- ip-addresses:
- 192.168.50.209/24
- fe80::a4cb:47ff:fe06:c121/64
mac-address: a6:cb:47:06:c1:21
name: ens18
proxmox_boot:
order: scsi0;ide2;net0
proxmox_cores: 2
proxmox_cpu: x86-64-v2-AES
proxmox_digest: b28a87e97e83316175dc63f48bac1c94028e63ae
proxmox_ide0:
disk_image: local-lvm:vm-100-cloudinit
media: cdrom
proxmox_ide2:
disk_image: local:iso/Fedora-Server-dvd-x86_64-43-1.6.iso
media: cdrom
size: 3402816K
proxmox_ipconfig0:
gw: 192.168.50.1
ip: 192.168.50.209/24
proxmox_memory: 4096
proxmox_meta:
creation-qemu: 8.0.2
ctime: '1769431736'
proxmox_name: web
proxmox_net0:
bridge: vmbr0
firewall: '1'
virtio: A6:CB:47:06:C1:21
proxmox_node: pve
proxmox_numa: 0
proxmox_ostype: l26
proxmox_qmpstatus: running
proxmox_scsi0:
disk_image: local-lvm:vm-100-disk-0
iothread: '1'
size: 32G
proxmox_scsihw:
disk_image: virtio-scsi-single
proxmox_smbios1:
uuid: 6386b16c-95e4-4038-9566-9448631386d7
proxmox_snapshots: []
proxmox_sockets: 1
proxmox_status: running
proxmox_tags: web
proxmox_tags_parsed:
- web
proxmox_vmgenid: 9946e95e-8314-420f-9744-0b592956c8ef
proxmox_vmid: 100
proxmox_vmtype: qemu
proxmox_all_running:
hosts:
mysql: {}
web: {}
proxmox_pool_database:
hosts:
mysql: {}
proxmox_pool_web:
hosts:
web: {}
proxmox_pve_qemu:
hosts:
mysql: {}
web: {}
这里有个比较重要的变量 proxmox_ipconfig0,如果虚拟机使用了 cloud-init,那么就会有 proxmox_ipconfig0 变量,这个变量里有 cloud-init 要设置的虚拟机地址,可以将它设置为虚拟机的 ansible_host 变量用于主机连接。
参考以下例子:
[root@remote-host ansible]# cat inventory/proxmox.yml
# inventory/proxmox.yml
plugin: community.general.proxmox
url: https://192.168.50.206:8006
user: root@pam
password: "redhat"
validate_certs: false
exclude_nodes: true
want_facts: true
want_proxmox_nodes_ansible_host: false
keyed_groups:
- key: proxmox_tags_parsed
separator: ""
prefix: group
groups:
webservers: "'web' in (proxmox_tags_parsed|list)"
databaseservers: "'database' in (proxmox_tags_parsed|list)"
compose:
ansible_host: proxmox_ipconfig0.ip | default(proxmox_net0.ip) | ipaddr('address')
ansible_port: 22
ansible_user: "'root'"
ansible_password: >
"redhat"
compose用于设置变量,其中ansible_host通过proxmox_ipconfig0.ip | default(proxmox_net0.ip) | ipaddr('address')设置keyed_groups和groups可以根据虚拟机标签对虚拟机进行分组
通过上述动态主机清单获取的主机信息如下:
[root@remote-host ansible]# ansible-navigator inventory \
-i inventory/proxmox.yml --yaml --list
[DEPRECATION WARNING]: Use 'ansible.utils.ipaddr' module instead. This feature will be removed from ansible.netcommon in a release after 2024-01-01. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
all:
children:
databaseservers:
hosts:
mysql: {}
groupdatabase:
hosts:
mysql: {}
groupweb:
hosts:
web: {}
proxmox_all_qemu:
hosts:
mysql:
ansible_host: 192.168.50.219
ansible_password: redhat
ansible_port: '22'
ansible_user: root
proxmox_agent: '1'
proxmox_agent_interfaces:
- ip-addresses:
- 127.0.0.1/8
- ::1/128
mac-address: 00:00:00:00:00:00
name: lo
- ip-addresses:
- 192.168.50.219/24
- fe80::6cf5:1ff:fe25:fd25/64
mac-address: 6e:f5:01:25:fd:25
name: ens18
proxmox_boot:
order: scsi0;ide2;net0
proxmox_cores: 4
proxmox_cpu: x86-64-v2-AES
proxmox_digest: b077ccef985e0f4b23f8062dda94b77f4c222687
proxmox_ide2:
disk_image: local:iso/Fedora-Server-dvd-x86_64-43-1.6.iso
media: cdrom
size: 3402816K
proxmox_ipconfig0:
gw: 192.168.50.1
ip: 192.168.50.219/24
proxmox_memory: 8192
proxmox_meta:
creation-qemu: 8.0.2
ctime: '1769431780'
proxmox_name: mysql
proxmox_net0:
bridge: vmbr0
firewall: '1'
virtio: 6E:F5:01:25:FD:25
proxmox_node: pve
proxmox_numa: 0
proxmox_ostype: l26
proxmox_qmpstatus: running
proxmox_sata0:
disk_image: local:101/vm-101-cloudinit.qcow2
media: cdrom
proxmox_scsi0:
disk_image: local-lvm:vm-101-disk-0
iothread: '1'
size: 32G
proxmox_scsihw:
disk_image: virtio-scsi-single
proxmox_smbios1:
uuid: 5aacfba5-8167-4465-97d7-1567f3d98a45
proxmox_snapshots: []
proxmox_sockets: 1
proxmox_status: running
proxmox_tags: database
proxmox_tags_parsed:
- database
proxmox_vmgenid: 3a88a3f5-210f-451a-bd1a-8701d18abec7
proxmox_vmid: 101
proxmox_vmtype: qemu
web:
ansible_host: 192.168.50.209
ansible_password: redhat
ansible_port: '22'
ansible_user: root
proxmox_agent: '1'
proxmox_agent_interfaces:
- ip-addresses:
- 127.0.0.1/8
- ::1/128
mac-address: 00:00:00:00:00:00
name: lo
- ip-addresses:
- 192.168.50.209/24
- fe80::a4cb:47ff:fe06:c121/64
mac-address: a6:cb:47:06:c1:21
name: ens18
proxmox_boot:
order: scsi0;ide2;net0
proxmox_cores: 2
proxmox_cpu: x86-64-v2-AES
proxmox_digest: b28a87e97e83316175dc63f48bac1c94028e63ae
proxmox_ide0:
disk_image: local-lvm:vm-100-cloudinit
media: cdrom
proxmox_ide2:
disk_image: local:iso/Fedora-Server-dvd-x86_64-43-1.6.iso
media: cdrom
size: 3402816K
proxmox_ipconfig0:
gw: 192.168.50.1
ip: 192.168.50.209/24
proxmox_memory: 4096
proxmox_meta:
creation-qemu: 8.0.2
ctime: '1769431736'
proxmox_name: web
proxmox_net0:
bridge: vmbr0
firewall: '1'
virtio: A6:CB:47:06:C1:21
proxmox_node: pve
proxmox_numa: 0
proxmox_ostype: l26
proxmox_qmpstatus: running
proxmox_scsi0:
disk_image: local-lvm:vm-100-disk-0
iothread: '1'
size: 32G
proxmox_scsihw:
disk_image: virtio-scsi-single
proxmox_smbios1:
uuid: 6386b16c-95e4-4038-9566-9448631386d7
proxmox_snapshots: []
proxmox_sockets: 1
proxmox_status: running
proxmox_tags: web
proxmox_tags_parsed:
- web
proxmox_vmgenid: 9946e95e-8314-420f-9744-0b592956c8ef
proxmox_vmid: 100
proxmox_vmtype: qemu
proxmox_all_running:
hosts:
mysql: {}
web: {}
proxmox_pool_database:
hosts:
mysql: {}
proxmox_pool_web:
hosts:
web: {}
proxmox_pve_qemu:
hosts:
mysql: {}
web: {}
webservers:
hosts:
web: {}
这里的
[DEPRECATION WARNING]告警主要是提示ipaddr过滤器插件在未来的版本里将从ansible.netcommon集合迁移到ansible.utils中。
再来个简洁的:
[root@remote-host ansible]# ansible-navigator inventory \
-i inventory/proxmox.yml --graph
[DEPRECATION WARNING]: Use 'ansible.utils.ipaddr' module instead. This feature will be removed from ansible.netcommon in a release after 2024-01-01. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
@all:
|--@ungrouped:
|--@proxmox_all_lxc:
|--@proxmox_all_qemu:
| |--web
| |--mysql
|--@proxmox_all_running:
| |--web
| |--mysql
|--@proxmox_all_stopped:
|--@proxmox_pve_lxc:
|--@proxmox_pve_qemu:
| |--web
| |--mysql
|--@webservers:
| |--web
|--@groupweb:
| |--web
|--@databaseservers:
| |--mysql
|--@groupdatabase:
| |--mysql
|--@proxmox_pool_database:
| |--mysql
|--@proxmox_pool_web:
| |--web
最后测试一下是否能通过这个主机清单执行任务:
[root@remote-host ansible]# ansible-navigator exec \
-- "ansible -i inventory/proxmox.yml all -m ping"
[DEPRECATION WARNING]: Use 'ansible.utils.ipaddr' module instead. This feature will be removed from ansible.netcommon in a release after 2024-01-01. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
web | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
mysql | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
自定义动态主机清单
除了使用开源社区现有的动态主机清单插件,也可以自己写一个动态主机清单脚本。
我们先看下静态主机清单被 Ansible 解析的内容:
[root@remote-host ansible]# cat inventory.bak
[k8s_master]
base-k8s-master-1
[k8s_worker]
base-k8s-worker-1 base-k8s-worker-2
[k8s_master:vars]
role=master
[k8s_worker:vars]
role=worker
[ntp_client]
[ntp_client:children] k8s_worker
[all:vars]
ansible_ssh_user=remote-manager ansible_ssh_password=redhat ansible_become_password=redhat [root@remote-host ansible]# ansible-navigator inventory -i inventory.bak –list { “_meta”: { “hostvars”: { “base-k8s-master-1”: { “ansible_become_password”: “redhat”, “ansible_ssh_password”: “redhat”, “ansible_ssh_user”: “remote-manager”, “role”: “master” }, “base-k8s-worker-1”: { “ansible_become_password”: “redhat”, “ansible_ssh_password”: “redhat”, “ansible_ssh_user”: “remote-manager”, “role”: “worker” }, “base-k8s-worker-2”: { “ansible_become_password”: “redhat”, “ansible_ssh_password”: “redhat”, “ansible_ssh_user”: “remote-manager”, “role”: “worker” } } }, “all”: { “children”: [ “ungrouped”, “k8s_master”, “ntp_client” ] }, “k8s_master”: { “hosts”: [ “base-k8s-master-1” ] }, “k8s_worker”: { “hosts”: [ “base-k8s-worker-1”, “base-k8s-worker-2” ] }, “ntp_client”: { “children”: [ “k8s_worker” ] } } [root@remote-host ansible]# ansible-navigator inventory -i inventory.bak –host base-k8s-master-1 { “ansible_become_password”: “redhat”, “ansible_ssh_password”: “redhat”, “ansible_ssh_user”: “remote-manager”, “role”: “master” }
针对上边这个输出说一下动态主机清单脚本的要求:
- 脚本要添加适当的解析器,如
#!/usr/bin/python - 脚本的输出内容和上边
--list的输出结构类似 - 脚本必须支持
--list和--host <hostname>两个参数,--list列出所有主机(包括主机组信息)和其变量,--host <hostname>列出特定主机在主机清单中定义的变量
更多请参考 https://docs.ansible.com/projects/ansible/latest/dev_guide/developing_inventory.html#developing-inventory-scripts。
来个简单演示:
下边这个脚本是用 AI 写的,脚本支持 --list 和 --host 两个参数。
先执行下脚本看看:
# 直接执行脚本
[root@remote-host ansible]# inventory/custom_dynamic_inventory.py
{
"all": {
"children": [
"ungrouped",
"k8s_master",
"ntp_client"
]
},
"k8s_master": {
"hosts": [
"base-k8s-master-1"
],
"vars": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "master"
}
},
"k8s_worker": {
"hosts": [
"base-k8s-worker-1",
"base-k8s-worker-2"
],
"vars": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker"
}
},
"ntp_client": {
"children": [
"k8s_worker"
]
},
"_meta": {
"hostvars": {
"base-k8s-master-1": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "master",
"ansible_host": "base-k8s-master-1"
},
"base-k8s-worker-1": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker",
"ansible_host": "base-k8s-worker-1"
},
"base-k8s-worker-2": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker",
"ansible_host": "base-k8s-worker-2"
}
}
}
}
# 执行脚本并添加 --list 参数
[root@remote-host ansible]# inventory/custom_dynamic_inventory.py --list
{
"all": {
"children": [
"ungrouped",
"k8s_master",
"ntp_client"
]
},
"k8s_master": {
"hosts": [
"base-k8s-master-1"
],
"vars": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "master"
}
},
"k8s_worker": {
"hosts": [
"base-k8s-worker-1",
"base-k8s-worker-2"
],
"vars": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker"
}
},
"ntp_client": {
"children": [
"k8s_worker"
]
},
"_meta": {
"hostvars": {
"base-k8s-master-1": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "master",
"ansible_host": "base-k8s-master-1"
},
"base-k8s-worker-1": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker",
"ansible_host": "base-k8s-worker-1"
},
"base-k8s-worker-2": {
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker",
"ansible_host": "base-k8s-worker-2"
}
}
}
}
# 执行脚本并添加 --host 参数
[root@remote-host ansible]# inventory/custom_dynamic_inventory.py --host base-k8s-master-1
{
"ansible_become_password": "redhat",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "master",
"ansible_host": "base-k8s-master-1"
}
再用 Ansible 调用脚本看看:
[root@remote-host ansible]# ansible-navigator inventory -i inventory/custom_dynamic_inventory.py --graph
@all:
|--@ungrouped:
|--@k8s_master:
| |--base-k8s-master-1
|--@ntp_client:
| |--@k8s_worker:
| | |--base-k8s-worker-1
| | |--base-k8s-worker-2
[root@remote-host ansible]# ansible-navigator inventory -i inventory/custom_dynamic_inventory.py --host base-k8s-worker-1
{
"ansible_become_password": "redhat",
"ansible_host": "base-k8s-worker-1",
"ansible_ssh_password": "redhat",
"ansible_ssh_user": "remote-manager",
"role": "worker"
}
[root@remote-host ansible]# ansible-navigator exec -- ansible -i inventory/custom_dynamic_inventory.py all --list-hosts
hosts (3):
base-k8s-master-1
base-k8s-worker-1
base-k8s-worker-2