如何在Ansible中动态构建或更新hostvars关联AWS实例?
如何通过AWS实例ID动态填充Ansible Hostvars并实现正确SSH连接
问题需求
希望仅通过aws_instance_id在Inventory中定义易读名称的主机(如DEV-BATCH_1),从AWS拉取实例的私有/公有IP、DNS主机名、实例类型等数据,将这些数据注入到hostvars中,替代手动声明的方式,让Ansible使用拉取到的IP/DNS进行SSH连接,而非Inventory中的易读名称。
尝试过的无效方法
- 在主机的
host_vars文件中直接写入模板字符串:hostname: "{{ ec2_info.results[0].instances[0].public_dns_name }}",但最终hostvars仅保留未解析的模板文本 - 使用
add_host模块添加动态主机,但原Inventory中易读名称主机的hostvars未被更新 - 使用
set_fact模块尝试设置变量,但未成功注入到目标主机的hostvars
示例配置
Inventory文件(hosts)
my_hosts: hosts: DEV-BATCH_1: aws_instance_id: i-xyz DEV-QA_1: aws_instance_id: i-yzx
主机变量文件(host_vars/DEV-BATCH_1等)
hostname: ""
尝试的Playbook代码
- name: Gather EC2 information and set hostvars hosts: localhost gather_facts: false vars: ec2_host_data: {} tasks: - name: Get EC2 instance info for each host amazon.aws.ec2_instance_info: instance_ids: "{{ hostvars[item]['aws_instance_id'] }}" region: us-east-1 loop: "{{ groups['all'] }}" loop_control: loop_var: item register: ec2_info no_log: false - name: Build a dictionary of EC2 instance details set_fact: ec2_host_data: >- {{ ec2_host_data | combine({ ec2_result.instances[0].instance_id: { 'private_ip': ec2_result.instances[0].private_ip_address, 'private_dns_name': ec2_result.instances[0].private_dns_name, 'instance_type': ec2_result.instances[0].instance_type, 'availability_zone': ec2_result.instances[0].placement.availability_zone } }) }} loop: "{{ ec2_info.results }}" loop_control: loop_var: ec2_result no_log: false - name: Add EC2 instances as inventory hosts add_host: name: "{{ item }}" groups: dynamic_ec2_hosts ansible_host: "{{ ec2_host_data[item].private_ip }}" hostname: "{{ ec2_host_data[item].private_dns_name }}" instance_id: "{{ item }}" instance_type: "{{ ec2_host_data[item].instance_type }}" availability_zone: "{{ ec2_host_data[item].availability_zone }}" loop: "{{ ec2_host_data.keys() }}" loop_control: loop_var: item - name: Debug updated hostvars debug: var: hostvars - name: Print all available facts ansible.builtin.debug: var: ansible_facts
调试结果
- 原Inventory主机的
hostname仍为空:
[...] "hostname": "", "iam_instance_profile": "SomeRole", [...] "inventory_hostname": "DEV-QA_2", [...]
ansible_facts输出为空:
TASK [Print all available facts] *********************************************************************************************************************************************** ok: [localhost] => { "ansible_facts": {} }
解决方案
核心问题分析
原Playbook运行在localhost,set_fact和add_host默认仅操作当前主机(localhost)的变量或添加新动态主机,无法更新原Inventory中已存在主机的hostvars。需针对目标主机运行任务,或通过delegate_to结合set_fact直接赋值。
修正后的Playbook
- name: Gather EC2 info and update original hostvars hosts: my_hosts # 直接针对原Inventory主机组执行 gather_facts: false tasks: - name: Fetch EC2 instance details for current host amazon.aws.ec2_instance_info: instance_ids: "{{ aws_instance_id }}" region: us-east-1 register: ec2_instance delegate_to: localhost # 在控制节点执行AWS API调用,无需目标主机安装依赖 - name: Inject EC2 data into hostvars set_fact: ansible_host: "{{ ec2_instance.instances[0].private_ip_address }}" # 关键:设置SSH连接用的IP hostname: "{{ ec2_instance.instances[0].private_dns_name }}" instance_type: "{{ ec2_instance.instances[0].instance_type }}" availability_zone: "{{ ec2_instance.instances[0].placement.availability_zone }}" - name: Verify hostvars update debug: var: hostvars[inventory_hostname] delegate_to: localhost
方案说明
- 针对原主机组执行:Playbook直接作用于
my_hosts组,每个主机独立执行任务,无需遍历全组 - 本地执行AWS调用:通过
delegate_to: localhost在控制节点完成EC2信息拉取,避免目标主机配置AWS环境 - 直接注入目标主机变量:
set_fact自动将变量写入当前执行任务的主机hostvars,其中ansible_host会被Ansible优先用于SSH连接,替代inventory_hostname - 简化逻辑:每个主机单独获取自身EC2信息,无需构建全局字典,降低复杂度
长期方案:使用AWS动态Inventory插件
如果需要持续基于EC2实例管理Inventory,推荐使用官方aws_ec2动态Inventory插件,可自动拉取实例信息并设置连接变量。
配置示例(aws_ec2.yml):
plugin: aws_ec2 regions: - us-east-1 filters: instance-id: - i-xyz - i-yzx keyed_groups: - key: tags.Name prefix: tag_Name compose: ansible_host: private_ip_address # 可替换为public_ip_address hostname: private_dns_name
使用方式:
ansible-playbook -i aws_ec2.yml your_playbook.yml
内容的提问来源于stack exchange,提问作者Will1v
相关产品推荐
相关产品推荐

