
本文详细介绍了如何使用Python脚本为Ansible生成符合其动态清单插件规范的JSON格式主机清单。通过分析常见的解析错误,文章阐明了Ansible期望的清单结构,特别是关于组、主机列表和`_meta`中主机变量的定义。教程提供了修正后的Python代码示例,并强调了使用`ansible-inventory --list`进行验证的重要性,以确保Ansible能够正确识别和使用动态生成的主机信息。
Ansible的动态主机清单功能允许用户通过外部脚本(如Python、Bash等)实时生成主机列表及其变量,这对于管理动态变化的云环境(如AWS EC2、Azure VM、Kubernetes Pods等)至关重要。当Ansible执行一个作为清单的脚本时,它会运行该脚本并解析其标准输出(stdout)中的JSON格式数据。然而,这个JSON格式必须遵循Ansible脚本清单插件的特定约定。
在尝试使用Python脚本生成动态清单时,一个常见的错误是输出的JSON格式不符合Ansible的预期。例如,直接将主机信息作为字典列表放入组中,会导致Ansible解析失败。
考虑以下Python脚本,它尝试从Terraform输出中获取AWS实例的DNS和名称,并将其组织成master和workers组:
立即学习“Python免费学习笔记(深入)”;
#!/usr/bin/python3
import subprocess
import json
def run_terraform():
"""运行Terraform获取输出,并解析为JSON。"""
result = subprocess.run(["terraform", "output", "-json"], capture_output=True, text=True, cwd="../terraform")
return json.loads(result.stdout)
def generate_ansible_inventory_incorrect():
"""生成不符合Ansible脚本清单插件规范的清单。"""
terraform_outputs = run_terraform()
instance_ips = terraform_outputs.get("instance_public_dns", {}).get("value", [])
instance_names = terraform_outputs.get("instance_name", {}).get("value", [])
master = []
worker = []
inventory = {}
items = zip(instance_ips, instance_names)
for item in items:
host_vars = {
"ansible_ssh_host": item[0],
"ansible_ssh_user": "ubuntu",
"ansible_ssh_private_key_file": "kanban.pem"
}
if "master" in item[1]:
master.append(host_vars)
else:
worker.append(host_vars)
inventory["master"] = master
inventory["workers"] = worker
print(json.dumps(inventory, indent=2))
if __name__== "__main__":
generate_ansible_inventory_incorrect()当运行此脚本时,它会生成如下JSON输出:
{
"master": [
{
"ansible_ssh_host": "ec2-54-165-95-159.compute-1.amazonaws.com",
"ansible_ssh_user": "ubuntu",
"ansible_ssh_private_key_file": "kanban.pem"
}
],
"workers": [
{
"ansible_ssh_host": "ec2-3-238-58-66.compute-1.amazonaws.com",
"ansible_ssh_user": "ubuntu",
"ansible_ssh_private_key_file": "kanban.pem"
}
]
}然而,当尝试使用ansible all -i get_dns.py -m ping命令测试时,Ansible会报错:
[WARNING]: * Failed to parse /home/vagrant/fullstack-kanban-app/ansible/get_dns.py with script plugin: unhashable type: 'dict' [WARNING]: Unable to parse /home/vagrant/fullstack-kanban-app/ansible/get_dns.py as an inventory source [WARNING]: No inventory was parsed, only implicit localhost is available
这表明Ansible的脚本插件未能正确解析该JSON结构。错误信息unhashable type: 'dict'暗示了问题出在将字典直接作为列表元素传递给预期为字符串(主机名)的地方。
Ansible的脚本清单插件期望的JSON格式与静态YAML/JSON文件略有不同。它要求:
此外,需要注意的是,自Ansible 2.0起,ansible_ssh_*变量已被弃用,应使用ansible_*变量(例如,ansible_host、ansible_user、ansible_private_key_file)。
一个符合规范的JSON输出示例结构如下:
{
"master": {
"hosts": [
"master-host-1.example.com",
"master-host-2.example.com"
]
},
"worker": {
"hosts": [
"worker-host-1.example.com",
"worker-host-2.example.com"
]
},
"_meta": {
"hostvars": {
"master-host-1.example.com": {
"ansible_host": "master-host-1.example.com",
"ansible_user": "ubuntu",
"ansible_private_key_file": "path/to/key.pem"
},
"worker-host-1.example.com": {
"ansible_host": "worker-host-1.example.com",
"ansible_user": "ubuntu",
"ansible_private_key_file": "path/to/key.pem"
}
}
}
}根据上述规范,我们需要修改generate_ansible_inventory函数,使其生成正确的JSON结构。
#!/usr/bin/python3
import subprocess
import json
def run_terraform():
"""运行Terraform获取输出,并解析为JSON。"""
result = subprocess.run(["terraform", "output", "-json"], capture_output=True, text=True, cwd="../terraform")
return json.loads(result.stdout)
def generate_ansible_inventory_correct():
"""生成符合Ansible脚本清单插件规范的清单。"""
terraform_outputs = run_terraform()
instance_ips = terraform_outputs.get("instance_public_dns", {}).get("value", [])
instance_names = terraform_outputs.get("instance_name", {}).get("value", [])
# 初始化符合Ansible规范的清单结构
inventory = {
"master": {
"hosts": [],
},
"workers": { # 使用 "workers" 而不是 "worker" 以匹配原始问题
"hosts": [],
},
"_meta": {
"hostvars": {},
}
}
items = zip(instance_ips, instance_names)
for ip, name in items:
# 定义主机特有变量,并使用ansible_*前缀
host_vars = {
"ansible_host": ip, # 使用ansible_host
"ansible_user": "ubuntu",
"ansible_private_key_file": "kanban.pem"
}
# 将主机名添加到对应的组的hosts列表中
if "master" in name:
inventory["master"]["hosts"].append(ip)
# 将主机变量添加到_meta.hostvars
inventory["_meta"]["hostvars"][ip] = host_vars
else:
inventory["workers"]["hosts"].append(ip)
inventory["_meta"]["hostvars"][ip] = host_vars
print(json.dumps(inventory, indent=2))
if __name__== "__main__":
generate_ansible_inventory_correct()运行修正后的脚本,将得到以下有效的JSON输出:
{
"master": {
"hosts": [
"ec2-54-165-95-159.compute-1.amazonaws.com"
]
},
"workers": {
"hosts": [
"ec2-3-238-58-66.compute-1.amazonaws.com"
]
},
"_meta": {
"hostvars": {
"ec2-54-165-95-159.compute-1.amazonaws.com": {
"ansible_host": "ec2-54-165-95-159.compute-1.amazonaws.com",
"ansible_user": "ubuntu",
"ansible_private_key_file": "kanban.pem"
},
"ec2-3-238-58-66.compute-1.amazonaws.com": {
"ansible_host": "ec2-3-238-58-66.compute-1.amazonaws.com",
"ansible_user": "ubuntu",
"ansible_private_key_file": "kanban.pem"
}
}
}
}为了正确验证动态清单脚本,不应直接使用ansible -m ping命令,而应使用ansible-inventory --list。这个命令专门用于显示Ansible解析后的清单结构。
执行以下命令来测试你的Python脚本:
ansible-inventory --list -i get_dns.py
如果输出与上述JSON结构一致,且没有警告或错误,则表明你的动态清单脚本已成功被Ansible解析。为了更详细地了解解析过程,可以添加-vvv参数:
ansible-inventory --list -i get_dns.py -vvv
你将看到类似Parsed (..)/get_dns.py inventory source with script plugin的调试信息,确认Ansible正在使用正确的脚本插件。
值得注意的是,Ansible的“脚本插件”和“YAML/JSON插件”对清单格式的期望有所不同。
例如,如果你将修正后的Python脚本的输出保存到一个名为my_inventory.json的文件中,并尝试用ansible -i my_inventory.json -m ping命令加载,Ansible的YAML/JSON插件会尝试解析它。虽然它通常也能处理带有_meta的结构,但其内部处理逻辑与脚本插件不同。理解这一点有助于在调试时区分是脚本输出格式问题还是文件解析问题。
通过遵循这些指导原则,你可以高效且可靠地构建和管理Ansible的动态主机清单。
以上就是如何为Ansible构建符合规范的动态Python主机清单的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号