Inventar-Plugins in Ansible erweitern die Flexibilität und Skalierbarkeit von Inventories, indem sie es ermöglichen, dynamische Inventories aus verschiedenen Quellen zu generieren. Diese Plugins können auf eine Vielzahl von Datenquellen zugreifen, darunter Cloud-Dienste, Datenbanken und externe APIs.
Wichtiger Hinweis: Ab Ansible 2.8 müssen
Inventar-Plugins nicht mehr manuell aktiviert werden, sofern sie korrekt
mit plugin: ... in der YAML-Konfiguration spezifiziert sind
und die Plugin-Dateien im erkannten Pfad (inventory/,
./, etc.) liegen.
Um alle verfügbaren Inventar-Plugins anzuzeigen:
ansible-doc -t inventory -lFür detaillierte Dokumentation eines spezifischen Plugins:
ansible-doc -t inventory aws_ec2ansible-project/
├── ansible.cfg
├── inventory/
│ ├── aws_ec2.yml
│ ├── gcp_compute.yml
│ └── group_vars/
├── inventory_plugins/
│ └── custom_plugin.py
└── playbooks/
pip install boto3 botocoreinventory/aws_ec2.ymlplugin: aws_ec2
# Regionen definieren
regions:
- us-west-1
- us-west-2
- eu-central-1
# Filter für Instance-Status
filters:
instance-state-name: running
# Zusätzliche Filter möglich:
# "tag:Environment": production
# Hostnamen-Mapping (wichtig für dynamische Umgebungen)
hostnames:
- dns-name
- private-dns-name
- tag:Name
- instance-id
# Variablen zusammenstellen
compose:
ansible_host: public_ip_address | default(private_ip_address)
ec2_arch: architecture
ec2_instance_type: instance_type
ec2_placement_region: placement.region
ec2_security_groups: security_groups | map(attribute='group_name') | list
ec2_vpc_id: vpc_id
# Dynamische Gruppenerstellung
groups:
# Gruppen basierend auf Instance-Typ
ec2_micro: instance_type == "t2.micro"
ec2_small: instance_type == "t2.small"
# Gruppen basierend auf Tags
webservers: "'web' in (tags.Role | default(''))"
databases: "'db' in (tags.Role | default(''))"
# Gruppen basierend auf Availability Zone
us_west_1a: placement.availability_zone == "us-west-1a"
# Gruppierung nach Tags mit Präfix
keyed_groups:
- key: tags.Environment | default('unknown')
prefix: env
- key: tags.Application | default('misc')
prefix: app
- key: placement.availability_zone
prefix: az
# Strenge Validierung aktivieren
strict: false
# Caching für bessere Performance
cache: true
cache_plugin: memory
cache_timeout: 3600# Inventory anzeigen
ansible-inventory -i inventory/aws_ec2.yml --list
# Spezifische Gruppe anzeigen
ansible-inventory -i inventory/aws_ec2.yml --list --host webserver-01
# Graph-Darstellung
ansible-inventory -i inventory/aws_ec2.yml --graphpip install google-auth google-api-python-clientinventory/gcp_compute.ymlplugin: gcp_compute
# Projekte und Zonen
projects:
- my-production-project
- my-staging-project
zones:
- us-central1-a
- us-central1-b
- europe-west1-a
# Service Account Key (alternativ: Application Default Credentials)
auth_kind: serviceaccount
service_account_file: /path/to/service-account.json
# Filter
filters:
- status = RUNNING
- machineType:n1-standard-*
# Hostnamen-Konfiguration
hostnames:
- name
- networkInterfaces[0].accessConfigs[0].natIP
- networkInterfaces[0].networkIP
# Variablen-Mapping
compose:
ansible_host: networkInterfaces[0].accessConfigs[0].natIP | default(networkInterfaces[0].networkIP)
gcp_machine_type: machineType | regex_replace('.*/', '')
gcp_zone: zone | regex_replace('.*/', '')
gcp_project: selfLink | regex_replace('.*/projects/([^/]*)/.*', '\\1')
# Gruppierung
groups:
gcp_preemptible: scheduling.preemptible | default(false)
gcp_production: "'production' in (labels.environment | default(''))"
keyed_groups:
- key: labels.environment | default('unknown')
prefix: env
- key: zone | regex_replace('.*/', '')
prefix: zoneinventory_plugins/database_inventory.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils.six.moves import configparser
import sqlite3
import json
DOCUMENTATION = '''
name: database_inventory
plugin_type: inventory
short_description: Database-basiertes Inventory Plugin
description:
- Lädt Host-Informationen aus einer SQLite-Datenbank
- Unterstützt dynamische Gruppenerstellung
requirements:
- sqlite3
options:
database_path:
description: Pfad zur SQLite-Datenbank
required: true
type: str
host_table:
description: Name der Hosts-Tabelle
required: false
default: hosts
type: str
group_table:
description: Name der Groups-Tabelle
required: false
default: groups
type: str
'''
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = 'database_inventory'
def verify_file(self, path):
"""Prüft, ob die Datei von diesem Plugin verarbeitet werden kann"""
if super(InventoryModule, self).verify_file(path):
if path.endswith(('database_inventory.yml', 'database_inventory.yaml')):
return True
return False
def parse(self, inventory, loader, path, cache=True):
"""Hauptmethode zum Parsen des Inventories"""
# Basis-Parser aufrufen
super(InventoryModule, self).parse(inventory, loader, path, cache)
# Konfiguration laden
try:
config = self._read_config_data(path)
except Exception as e:
raise AnsibleParserError(f"Fehler beim Lesen der Konfiguration: {e}")
# Plugin-spezifische Konfiguration
database_path = self.get_option('database_path')
host_table = self.get_option('host_table')
group_table = self.get_option('group_table')
if not database_path:
raise AnsibleError("database_path ist erforderlich")
# Cache-Schlüssel generieren
cache_key = self.get_cache_key(path)
# Versuche aus Cache zu laden
if cache:
cache_data = self.cache.get(cache_key)
if cache_data:
self._populate_from_cache(cache_data)
return
try:
# Datenbankverbindung
conn = sqlite3.connect(database_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# Hosts laden
self._load_hosts(cursor, host_table)
# Gruppen laden
self._load_groups(cursor, group_table)
# Cache aktualisieren
if cache:
cache_data = {
'hosts': dict(self.inventory.hosts),
'groups': dict(self.inventory.groups)
}
self.cache.set(cache_key, cache_data)
except sqlite3.Error as e:
raise AnsibleError(f"Datenbankfehler: {e}")
except Exception as e:
raise AnsibleError(f"Unerwarteter Fehler: {e}")
finally:
if 'conn' in locals():
conn.close()
def _load_hosts(self, cursor, table_name):
"""Lädt Hosts aus der Datenbank"""
try:
cursor.execute(f"""
SELECT hostname, ip_address, variables
FROM {table_name}
WHERE active = 1
""")
for row in cursor.fetchall():
hostname = row['hostname']
ip_address = row['ip_address']
variables_json = row['variables'] or '{}'
# Host hinzufügen
self.inventory.add_host(hostname)
# IP-Adresse setzen
if ip_address:
self.inventory.set_variable(hostname, 'ansible_host', ip_address)
# Zusätzliche Variablen aus JSON
try:
variables = json.loads(variables_json)
for key, value in variables.items():
self.inventory.set_variable(hostname, key, value)
except json.JSONDecodeError:
self.display.warning(f"Ungültiges JSON für Host {hostname}")
except sqlite3.Error as e:
raise AnsibleError(f"Fehler beim Laden der Hosts: {e}")
def _load_groups(self, cursor, table_name):
"""Lädt Gruppen aus der Datenbank"""
try:
cursor.execute(f"""
SELECT g.groupname, g.variables, h.hostname
FROM {table_name} g
LEFT JOIN host_groups hg ON g.id = hg.group_id
LEFT JOIN hosts h ON hg.host_id = h.id
WHERE g.active = 1
""")
groups_data = {}
for row in cursor.fetchall():
groupname = row['groupname']
hostname = row['hostname']
variables_json = row['variables'] or '{}'
# Gruppe erstellen falls nicht vorhanden
if groupname not in groups_data:
groups_data[groupname] = {
'hosts': [],
'variables': {}
}
self.inventory.add_group(groupname)
# Gruppenvariablen setzen
try:
variables = json.loads(variables_json)
for key, value in variables.items():
self.inventory.set_variable(groupname, key, value)
except json.JSONDecodeError:
self.display.warning(f"Ungültiges JSON für Gruppe {groupname}")
# Host zur Gruppe hinzufügen
if hostname:
self.inventory.add_host(hostname, group=groupname)
except sqlite3.Error as e:
raise AnsibleError(f"Fehler beim Laden der Gruppen: {e}")
def _populate_from_cache(self, cache_data):
"""Lädt Daten aus dem Cache"""
# Implementierung für Cache-basierte Wiederherstellung
passinventory/database_inventory.ymlplugin: database_inventory
database_path: /path/to/inventory.db
host_table: hosts
group_table: groups
# Optional: Caching aktivieren
cache: true
cache_plugin: jsonfile
cache_timeout: 300
cache_connection: /tmp/ansible_inventory_cache-- Hosts-Tabelle
CREATE TABLE hosts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname VARCHAR(255) UNIQUE NOT NULL,
ip_address VARCHAR(45),
variables TEXT, -- JSON-String
active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Groups-Tabelle
CREATE TABLE groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
groupname VARCHAR(255) UNIQUE NOT NULL,
variables TEXT, -- JSON-String
active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Host-Group-Zuordnungen
CREATE TABLE host_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host_id INTEGER,
group_id INTEGER,
FOREIGN KEY (host_id) REFERENCES hosts(id),
FOREIGN KEY (group_id) REFERENCES groups(id),
UNIQUE(host_id, group_id)
);
-- Beispieldaten
INSERT INTO hosts (hostname, ip_address, variables) VALUES
('web-01', '192.168.1.10', '{"server_role": "web", "nginx_version": "1.18"}'),
('web-02', '192.168.1.11', '{"server_role": "web", "nginx_version": "1.18"}'),
('db-01', '192.168.1.20', '{"server_role": "database", "mysql_version": "8.0"}');
INSERT INTO groups (groupname, variables) VALUES
('webservers', '{"http_port": 80, "https_port": 443}'),
('databases', '{"mysql_port": 3306}');
INSERT INTO host_groups (host_id, group_id) VALUES
(1, 1), (2, 1), (3, 2);[defaults]
inventory_plugins = ./inventory_plugins
enable_plugins = auto, host_list, script, yaml, ini, database_inventory
[inventory]
cache = True
cache_plugin = jsonfile
cache_timeout = 3600
cache_connection = /tmp/ansible_inventory_cache# Plugin testen
ansible-inventory -i inventory/database_inventory.yml --list
# Spezifischen Host anzeigen
ansible-inventory -i inventory/database_inventory.yml --host web-01
# Plugin-Dokumentation anzeigen
ansible-doc -t inventory database_inventory# Phase 1: Parallel testing
ansible-inventory -i inventory/static.ini,inventory/aws_ec2.yml --list
# Phase 2: Graduelle Übernahme einzelner Hostgruppen
ansible-playbook site.yml -i inventory/aws_ec2.yml --limit webservers
# Phase 3: Vollständige Migration
ansible-playbook site.yml -i inventory/# In Plugin-Konfiguration
strict: false # Ignoriert Fehler bei Variablen-Zusammensetzung
compose:
ansible_host: public_ip_address | default(private_ip_address, true)# Debugging aktivieren
ANSIBLE_DEBUG=1 ansible-inventory -i inventory/aws_ec2.yml --list
# Verbosity erhöhen
ansible-inventory -i inventory/aws_ec2.yml --list -vvv# Caching für bessere Performance
cache: true
cache_plugin: redis
cache_timeout: 1800
cache_connection: redis://localhost:6379/1
# Parallele API-Calls (nur bei unterstützten Plugins)
ansible_host_key_checking: false# Credentials aus Umgebungsvariablen
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_SESSION_TOKEN="your-session-token"
# Alternativ: AWS CLI Profile
export AWS_PROFILE="production"
# GCP Service Account
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"# In Plugin-Konfiguration: Keine Credentials hardcoden!
# Schlecht:
# access_key: AKIAIOSFODNN7EXAMPLE
# Gut: Umgebungsvariablen oder IAM Roles verwenden
# (Plugin nutzt automatisch AWS CLI/SDK Credential Chain)# Inventory-Validierung als Pre-Deployment Check
ansible-inventory -i inventory/ --list > /dev/null
if [ $? -ne 0 ]; then
echo "FEHLER: Inventory konnte nicht geladen werden"
exit 1
fi
# Anzahl Hosts überwachen
HOST_COUNT=$(ansible-inventory -i inventory/ --list | jq '._meta.hostvars | length')
if [ $HOST_COUNT -lt 10 ]; then
echo "WARNUNG: Ungewöhnlich wenige Hosts gefunden ($HOST_COUNT)"
fi# Plugin-Pfade prüfen
ansible-config dump | grep INVENTORY_PLUGINS
# Plugin-Liste anzeigen
ansible-doc -t inventory -l | grep your_plugin# AWS Credentials testen
aws sts get-caller-identity
# GCP Credentials testen
gcloud auth list# Cache-Status prüfen
ls -la /tmp/ansible_inventory_cache/
# Cache löschen
rm -rf /tmp/ansible_inventory_cache/*# inventory/combined.yml
plugin: advanced
sources:
- plugin: aws_ec2
regions: ['us-west-1', 'us-west-2']
- plugin: gcp_compute
projects: ['my-project']
- plugin: azure_rm
subscription_id: 'your-subscription-id'
# Namespace-Trennung
keyed_groups:
- key: inventory_source
prefix: source# tests/test_database_inventory.py
import pytest
import tempfile
import sqlite3
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
def test_database_inventory_plugin():
# Temporäre Datenbank erstellen
with tempfile.NamedTemporaryFile(suffix='.db') as db_file:
# Schema und Testdaten erstellen
conn = sqlite3.connect(db_file.name)
conn.execute('''CREATE TABLE hosts (
hostname TEXT, ip_address TEXT, variables TEXT, active INTEGER
)''')
conn.execute('''INSERT INTO hosts VALUES
('test-host', '192.168.1.1', '{}', 1)''')
conn.commit()
conn.close()
# Plugin testen
loader = DataLoader()
inventory = InventoryManager(loader=loader, sources=[f'database_inventory.yml'])
assert 'test-host' in inventory.hosts
assert inventory.get_host('test-host').vars['ansible_host'] == '192.168.1.1'