官方文档
working-with-dynamic-inventory
inventory-plugins
developing_inventory
红帽文档
ansible-dynamic-inventories
ansible-dynamic-inventory-python
ansible-plugin-inventory-files
一、使用
安装依赖:
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
python get-pip.py
pip install boto3 botocore
主要研究调用、开发py脚本,使用范例如下
https://docs.ansible.com/ansible/latest/inventory_guide/intro_dynamic_inventory.html#inventory-script-example-openstack
ansible -i openstack_inventory.py all -m ansible.builtin.ping
inventory plugins 的使用稍后介绍
二、自定义脚本开发
1) 脚本输出要求
Inventory scripts 必须接受 --list
and --host <hostname>
参数。脚本也可以带其他参数,但是 Ansible 不会用到。
当使用参数 --list
时,脚本须输出一个JSON对象,包含要管理的所有组。每个组的值应该是一个对象 or 一个主机列表,对象包含主机列表、子组和组变量:
{
"group001": {
"hosts": ["host001", "host002"],
"vars": {
"var1": true
},
"children": ["group002"]
},
"group002": {
"hosts": ["host003","host004"],
"vars": {
"var2": 500
},
"children":[]
}
}
如果组中的任何元素为空,则可以从输出中省略它们。
脚本通过 --list 返回如上, ansible 就可以运行了。
https://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html#inventory-script-conventions
当使用参数 --host <hostname>
时,脚本须打印一个JSON对象,该对象要么为空,要么包含变量以使它们可用于templates and playbooks。比如:
{
"VAR001": "VALUE",
"VAR002": "VALUE"
}
打印变量是可选的。如果脚本不打印变量,那么它应该打印一个空的JSON对象。
2) 写一个脚本
依赖:
pip install tencentcloud-sdk-python
三、inventory plugins 使用简介
ansible-doc -t inventory -l
查看可用插件;
ansible-doc -t inventory <plugin name>
查看指定插件使用文档;
下面以 aws 为例:
[root@VM_99_101_centos ~]# ansible-doc -t inventory -l
nmap Uses nmap to find hosts to target
host_list Parses a 'host list' string
hcloud Ansible dynamic inventory plugin for the Hetzner Cloud
openstack OpenStack inventory source
vultr Vultr inventory source
aws_ec2 EC2 inventory source
... 略
[root@VM_99_101_centos ~]# ansible-doc -t inventory aws_ec2
> AWS_EC2 (/usr/lib/python2.7/site-packages/ansible/plugins/inventory/aws_ec2.py)
Get inventory hosts from Amazon Web Services EC2. Uses a YAML configuration file that ends with `aws_ec2.(yml|yaml').
... 略
创建 demo.aws_ec2.yml
作为 inventory_source,如下:
# Minimal example using environment vars or instance role credentials
# Fetch all hosts in us-east-1, the hostname is the public DNS if it exists, otherwise the private IP address
plugin: aws_ec2
regions:
- ap-southeast-1
aws_access_key: AKIASQVFPHD7RJZ6VRNN
aws_secret_key: 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
执行 ansible-inventory -i demo.aws_ec2.yml --graph
查看主机
[root@VM_99_101_centos ~]# ansible-inventory -i demo.aws_ec2.yml --graph
@all:
|--@aws_ec2:
| |--ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com
|--@ungrouped:
执行 ansible-inventory -i demo.aws_ec2.yml --list
查看所有主机详细信息
[root@VM_99_101_centos ~]# ansible-inventory -i demo.aws_ec2.yml --list
{
"_meta": {
"hostvars": {
"ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com": {
"ami_launch_index": 0,
"architecture": "x86_64",
"block_device_mappings": [
{
"device_name": "/dev/sda1",
"ebs": {
"attach_time": "2022-11-11T16:43:48+00:00",
"delete_on_termination": true,
"status": "attached",
"volume_id": "vol-0b90f9db3ee65ed86"
}
}
],
"capacity_reservation_specification": {
"capacity_reservation_preference": "open"
},
"client_token": "4a1146a1-3fb7-495d-9280-eb238587b79a",
"cpu_options": {
"core_count": 1,
"threads_per_core": 1
},
"ebs_optimized": false,
"ena_support": true,
"enclave_options": {
"enabled": false
},
"hibernation_options": {
"configured": false
},
"hypervisor": "xen",
"image_id": "ami-07651f0c4c315a529",
"instance_id": "i-065925eccd49c4fa0",
"instance_type": "t2.micro",
"key_name": "aws-robin",
"launch_time": "2022-11-11T16:43:47+00:00",
"metadata_options": {
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "optional",
"state": "applied"
},
"monitoring": {
"state": "disabled"
},
"network_interfaces": [
{
"association": {
"ip_owner_id": "amazon",
"public_dns_name": "ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com",
"public_ip": "54.251.25.60"
},
"attachment": {
"attach_time": "2022-11-11T16:43:47+00:00",
"attachment_id": "eni-attach-0f30ee3f8ee78b9bb",
"delete_on_termination": true,
"device_index": 0,
"network_card_index": 0,
"status": "attached"
},
"description": "",
"groups": [
{
"group_id": "sg-0a4bbb4a6ebd28e51",
"group_name": "launch-wizard-3"
}
],
"interface_type": "interface",
"ipv6_addresses": [],
"mac_address": "06:a0:44:60:cb:0c",
"network_interface_id": "eni-08da0e84ee48b42aa",
"owner_id": "173219461375",
"private_dns_name": "ip-172-31-44-53.ap-southeast-1.compute.internal",
"private_ip_address": "172.31.44.53",
"private_ip_addresses": [
{
"association": {
"ip_owner_id": "amazon",
"public_dns_name": "ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com",
"public_ip": "54.251.25.60"
},
"primary": true,
"private_dns_name": "ip-172-31-44-53.ap-southeast-1.compute.internal",
"private_ip_address": "172.31.44.53"
}
],
"source_dest_check": true,
"status": "in-use",
"subnet_id": "subnet-d1fcfe98",
"vpc_id": "vpc-98daf3ff"
}
],
"owner_id": "173219461375",
"placement": {
"availability_zone": "ap-southeast-1a",
"group_name": "",
"region": "ap-southeast-1",
"tenancy": "default"
},
"private_dns_name": "ip-172-31-44-53.ap-southeast-1.compute.internal",
"private_ip_address": "172.31.44.53",
"product_codes": [],
"public_dns_name": "ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com",
"public_ip_address": "54.251.25.60",
"requester_id": "",
"reservation_id": "r-04a46ce11dfd49283",
"root_device_name": "/dev/sda1",
"root_device_type": "ebs",
"security_groups": [
{
"group_id": "sg-0a4bbb4a6ebd28e51",
"group_name": "launch-wizard-3"
}
],
"source_dest_check": true,
"state": {
"code": 16,
"name": "running"
},
"state_transition_reason": "",
"subnet_id": "subnet-d1fcfe98",
"tags": {},
"virtualization_type": "hvm",
"vpc_id": "vpc-98daf3ff"
}
}
},
"all": {
"children": [
"aws_ec2",
"ungrouped"
]
},
"aws_ec2": {
"hosts": [
"ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com"
]
}
}
...
"aws_ec2": {
"hosts": [
"ec2-13-213-50-61.ap-southeast-1.compute.amazonaws.com",
"ec2-18-136-213-37.ap-southeast-1.compute.amazonaws.com",
"ec2-18-139-161-121.ap-southeast-1.compute.amazonaws.com"
]
}
...
执行 ansible-inventory -i demo.aws_ec2.yml --host [host]
查看某个主机详细信息
[root@VM_99_101_centos ~]# ansible-inventory -i demo.aws_ec2.yml --host ec2-54-251-25-60.ap-southeast-1.compute.amazonaws.com
{
"ami_launch_index": 0,
"architecture": "x86_64",
... 省略 ...
"state": {
"code": 16,
"name": "running"
},
"state_transition_reason": "",
"subnet_id": "subnet-d1fcfe98",
"tags": {},
"virtualization_type": "hvm",
"vpc_id": "vpc-98daf3ff"
}
AWS Automation with boto3 of Python and Lambda Functions
https://learn.microsoft.com/en-us/azure/developer/ansible/dynamic-inventory-configure?tabs=azure-cli
https://www.zippyops.com/ansible-dynamic-inventory
使用
ansible -i ec2.py -u ubuntu us-east-1d -m ping
ansible -i ec2.py tag_OS_UBUNTU14 -m ping -u ubuntu – private-key=<keyfilename.pem>
ansible -i ec2.py tag_OS_UBUNTU14 -m shell -a "df -k" -u ubuntu – private-key=<keyfilename.pem>
ansible-playbook playbookname.yml -i ec2.py -u ubuntu – private-key=<keyfilename.pem>
ansible --inventory scripts/nmap_inventory.py --user josevnz -m ping all
ansible -i openstack_inventory.py all -m ansible.builtin.ping
[root@VM_99_101_centos ~]# ansible all -i demo.aws_ec2.yml -m ping -u ubuntu --private-key='aws-v2ray.pem'
ec2-18-139-161-121.ap-southeast-1.compute.amazonaws.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
ec2-18-136-213-37.ap-southeast-1.compute.amazonaws.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
ec2-13-213-50-61.ap-southeast-1.compute.amazonaws.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
四、Ansible Inventory Script
1) 脚本返回格式
{
"_meta": {
"hostvars": {}
},
"all": {
"children": [
"ungrouped"
]
},
"ungrouped": {
"children": [
]
}
}
{
"_meta": {
"hostvars": {
"android-1c5660ab7065af69.home": {
"ip": "192.168.1.4",
"ports": []
},
"dmaf5.home": {
"ip": "192.168.1.26",
"ports": []
}
},
"all": {
"children": [
"ungrouped"
]
},
"ungrouped": {
"hosts": [
"android-1c5660ab7065af69.home",
"dmaf5.home",
"macmini2",
"new-host-2.home",
"new-host-6.home",
"raspberrypi"
]
}
}
}
{
"_meta": {
"hostvars": {
"dmaf5.home": {
"ip": [
"192.168.1.26",
"192.168.1.25"
]
},
"macmini2": {
"ip": [
"192.168.1.16"
]
},
"raspberrypi": {
"ip": [
"192.168.1.11"
]
}
}
},
"all": {
"children": [
"ungrouped"
]
},
"ungrouped": {
"hosts": [
"dmaf5.home",
"macmini2",
"raspberrypi"
]
}
}
2) 范例脚本 sample.py (自带数据)
#!/usr/bin/python
import os
import sys
import argparse
try:
import json
except ImportError:
import simplejson as json
class ExampleInventory(object):
def __init__(self):
self.inventory = {}
self.read_cli_args()
# Called with `--list`.
if self.args.list:
self.inventory = self.example_inventory()
# Called with `--host [hostname]`.
elif self.args.host:
# Not implemented, since we return _meta info `--list`.
self.inventory = self.empty_inventory()
# If no groups or vars are present, return empty inventory.
else:
self.inventory = self.empty_inventory()
print json.dumps(self.inventory);
# Example inventory for testing.
def example_inventory(self):
return {
'group': {
'hosts': ['ansible2.zippyops.com', 'ansible3.zippyops.com'],
'vars': {
'ansible_ssh_user': 'ansible',
'test_variable': 'nonspecific_value'
}
},
'_meta': {
'hostvars': {
'ansible2.zippyops.com': {
'log_folder': '/var/log'
},
'ansible3.zippyops.com': {
'log_folder': '/var/log2'
}
}
}
}
# Empty inventory for testing.
def empty_inventory(self):
return {'_meta': {'hostvars': {}}}
# Read the command line args passed to the script.
def read_cli_args(self):
parser = argparse.ArgumentParser()
parser.add_argument('--list', action = 'store_true')
parser.add_argument('--host', action = 'store')
self.args = parser.parse_args()
# Get the inventory.
ExampleInventory()
执行结果
[root@VM_99_101_centos ~]# ./sample1.py --list
{"group": {"hosts": ["ansible2.zippyops.com", "ansible3.zippyops.com"], "vars": {"ansible_ssh_user": "ansible", "test_variable": "nonspecific_value"}}, "_meta": {"hostvars": {"ansible2.zippyops.com": {"log_folder": "/var/log"}, "ansible3.zippyops.com": {"log_folder": "/var/log2"}}}}
[root@VM_99_101_centos ~]# ansible-inventory -i sample1.py --list
{
"_meta": {
"hostvars": {
"ansible2.zippyops.com": {
"ansible_ssh_user": "ansible",
"log_folder": "/var/log",
"test_variable": "nonspecific_value"
},
"ansible3.zippyops.com": {
"ansible_ssh_user": "ansible",
"log_folder": "/var/log2",
"test_variable": "nonspecific_value"
}
}
},
"all": {
"children": [
"group",
"ungrouped"
]
},
"group": {
"hosts": [
"ansible2.zippyops.com",
"ansible3.zippyops.com"
]
}
}
[root@VM_99_101_centos ~]# ansible-inventory -i sample1.py --host ansible2.zippyops.com
{
"ansible_ssh_user": "ansible",
"log_folder": "/var/log",
"test_variable": "nonspecific_value"
}
[root@VM_99_101_centos ~]# ansible all -i sample1.py -m ping
ansible3.zippyops.com | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname ansible3.zippyops.com: Name or service not known",
"unreachable": true
}
ansible2.zippyops.com | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname ansible2.zippyops.com: Name or service not known",
"unreachable": true
}
五、腾讯云 inventory script
AWS EC2 范例:
https://github.com/vshn/ansible-dynamic-inventory-ec2/blob/master/ec2.py
1) 脚本
#!/usr/bin/env python
# coding=utf8
import sys
import os
import argparse
import bisect
import re
from time import time
from copy import deepcopy
from datetime import date, datetime
import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.cvm.v20170312 import cvm_client, models
SecretId="xxxxxxxxxxxxxxxxxxxx"
SecretKey="xxxxxxxxxxxxxxxxxxxx"
VpcID="vpc-xxxxxxxx"
class CvmInventory(object):
def _empty_inventory(self):
return {"_meta": {"hostvars": {}}}
def _json_serial(self, obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj))
def __init__(self):
''' Main execution path '''
# Inventory grouped by instance IDs, tags, security groups, regions,
# and availability zones
self.inventory = self._empty_inventory()
# Get Instance by Region
self.get_instances_by_vpc(VpcID)
data_to_print = self.json_format_dict(self.inventory, True)
print(data_to_print)
def get_instance(self, region, instance_id):
conn = self.connect(region)
reservations = conn.get_all_instances([instance_id])
for reservation in reservations:
for instance in reservation.instances:
return instance
def get_host_info(self):
''' Get variables about a specific host '''
instance = self.get_instance(region, instance_id)
return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True)
def get_instances_by_vpc(self, vpcid):
''' Makes an AWS EC2 API call to the list of instances in a particular
region '''
try:
cred = credential.Credential(SecretId, SecretKey)
httpProfile = HttpProfile()
httpProfile.endpoint = "cvm.tencentcloudapi.com"
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
client = cvm_client.CvmClient(cred, "ap-shanghai", clientProfile)
req = models.DescribeInstancesRequest()
# 根据 limit 和 offset 循环获取
count = 1
cvmlist = []
offsetx = 0
limitx = 20
params = {
"Filters": [
{
"Name": "vpc-id",
"Values": [ vpcid ]
}
],
"Offset": 0,
"Limit": limitx
}
while offsetx < count:
if offsetx == 0:
params['Offset']=offsetx
req.from_json_string(json.dumps(params))
resp = client.DescribeInstances(req)
resptmp = resp.InstanceSet
count=resp.TotalCount
offsetx=offsetx+limitx
else:
params['Offset']=offsetx
req.from_json_string(json.dumps(params))
resp = client.DescribeInstances(req)
resp.InstanceSet.extend(resptmp)
resptmp = resp.InstanceSet
count=resp.TotalCount
offsetx=offsetx+limitx
#print([instance.Uuid for instance in resp.InstanceSet])
for instance in resp.InstanceSet:
self.add_instance(instance, vpcid)
except TencentCloudSDKException as err:
print(err)
def add_instance(self, instance, vpcid):
''' Adds an instance to the inventory and index, as long as it is
addressable '''
dest = instance.PrivateIpAddresses[0]
hostname = instance.InstanceName
#print(vars(instance))
# 按操作系统分组
if instance.OsName:
self.push(self.inventory, instance.OsName.split()[0], hostname)
else:
self.push(self.inventory, noOS, hostname)
# 获取 hostname 属性
self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance)
# 增加 hostname 属性 ansible_host
self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest
def push(self, my_dict, key, element):
''' Insert an element into an array that may not have been defined in
the dict. The elements are inserted to preserve asciibetical ordering
of the array '''
group_info = my_dict.setdefault(key, [])
if isinstance(group_info, dict):
host_list = group_info.setdefault('hosts', [])
bisect.insort(host_list, element)
else:
bisect.insort(group_info, element)
def get_host_info_dict_from_instance(self, instance):
instance_vars = {}
#print(vars(instance))
instance_vars['instance.OsName'] = instance.OsName
instance_vars['InstanceName'] = instance.InstanceName
instance_vars['instance.Tags'] = instance.Tags
instance_vars['instance.InstanceId'] = instance.InstanceId
instance_vars['instance.Uuid'] = instance.Uuid
#instance_vars['instance.Placement'] = instance.Placement
#instance_vars['instance.InstanceState'] = instance.InstanceState
#instance_vars['instance.IPv6Addresses'] = instance.IPv6Addresses
#instance_vars['instance.PublicIpAddresses'] = instance.PublicIpAddresses
#instance_vars['instance.PrivateIpAddresses'] = instance.PrivateIpAddresses
#instance_vars['instance.CPU'] = instance.CPU
#instance_vars['instance.Memory'] = instance.Memory
#instance_vars['instance.SystemDisk'] = instance.SystemDisk
#instance_vars['instance.DataDisks'] = instance.DataDisks
for key in vars(instance):
value = getattr(instance, key)
return instance_vars
def json_format_dict(self, data, pretty=False):
''' Converts a dict to a JSON object and dumps it as a formatted
string '''
if pretty:
return json.dumps(data, sort_keys=True, indent=2, default=self._json_serial)
else:
return json.dumps(data, default=self._json_serial)
if __name__ == '__main__':
# Run the script
CvmInventory()
2) 执行
ansible-inventory -i cvm.py --list
[root@VM_99_101_centos ~]# ansible-inventory -i cvm.py --list
{
"CentOS": {
"hosts": [
"GS_APM_out",
"GS_mail_reply",
"GS_\u7f51\u5173"
]
},
"Ubuntu": {
"hosts": [
"GS_webhook_server"
]
},
"_meta": {
"hostvars": {
"GS_APM_out": {
"InstanceName": "GS_APM_out",
"ansible_host": "10.68.1.5",
"instance.InstanceId": "ins-988wvacj",
"instance.OsName": "CentOS 7.8 64\u4f4d",
"instance.Tags": [],
"instance.Uuid": "0d3e824d-1039-4a0a-9e79-a4a22ed4085e"
},
"GS_mail_reply": {
"InstanceName": "GS_mail_reply",
"ansible_host": "10.68.100.9",
"instance.InstanceId": "ins-aiua9zyd",
"instance.OsName": "CentOS 8.0 64\u4f4d",
"instance.Tags": [],
"instance.Uuid": "8826dd8f-54bd-4ae0-a471-60ac7fa65575"
},
"GS_webhook_server": {
"InstanceName": "GS_webhook_server",
"ansible_host": "10.68.100.15",
"instance.InstanceId": "ins-kmy9rulh",
"instance.OsName": "Ubuntu Server 20.04 LTS 64\u4f4d",
"instance.Tags": [],
"instance.Uuid": "ad2d1b33-62b9-4bd7-9b90-69573ef65337"
},
"GS_\u7f51\u5173": {
"InstanceName": "GS_\u7f51\u5173",
"ansible_host": "10.68.0.7",
"instance.InstanceId": "ins-fgft1u8z",
"instance.OsName": "CentOS 8.0 64\u4f4d",
"instance.Tags": [],
"instance.Uuid": "7a55263a-027d-43b8-bbe9-56c8c42f681f"
}
}
},
"all": {
"children": [
"CentOS",
"Ubuntu",
"ungrouped"
]
}
}
ansible-inventory -i cvm.py --host GS_mail_reply
[root@VM_99_101_centos ~]# ansible-inventory -i cvm.py --host GS_mail_reply
{
"InstanceName": "GS_mail_reply",
"ansible_host": "10.68.100.9",
"instance.InstanceId": "ins-aiua9zyd",
"instance.OsName": "CentOS 8.0 64\u4f4d",
"instance.Tags": [],
"instance.Uuid": "8826dd8f-54bd-4ae0-a471-60ac7fa65575"
}
ansible CentOS -i cvm.py -m ping
[root@VM_99_101_centos ~]# ansible CentOS -i cvm.py -m ping
GS_mail_reply | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 10.68.100.9 port 22: Connection timed out",
"unreachable": true
}
GS_APM_out | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 10.68.1.5 port 22: Connection timed out",
"unreachable": true
}
GS_网关 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 10.68.0.7 port 22: Connection timed out",
"unreachable": true
}
网友评论