21 Dynamische Inventories

21.1 Konzept und Einsatzbereich

Dynamische Inventories generieren die Host-Liste zur Laufzeit durch ausführbare Skripte, anstatt statische Inventory-Dateien zu verwenden. Dies ist besonders vorteilhaft bei:

21.2 Funktionsweise

Ein dynamisches Inventory-Skript muss zwei Parameter unterstützen:

Das JSON-Format folgt der Ansible-Konvention für Gruppierung und Variablen.

21.3 Grundlegendes Beispiel

inventory.sh:

#!/bin/bash

if [ "$1" = "--list" ]; then
    cat << 'EOF'
{
  "webservers": {
    "hosts": ["web1.example.com", "web2.example.com"],
    "vars": {
      "http_port": 80,
      "ssl_enabled": true
    }
  },
  "databases": {
    "hosts": ["db1.example.com", "db2.example.com"],
    "vars": {
      "mysql_port": 3306
    }
  }
}
EOF
elif [ "$1" = "--host" ]; then
    echo '{}'
fi

Verwendung:

chmod +x inventory.sh
ansible-playbook -i inventory.sh site.yml

21.4 Python-Implementation

inventory.py:

#!/usr/bin/env python3
import json
import sys

def build_inventory():
    return {
        'webservers': {
            'hosts': ['web1.example.com', 'web2.example.com'],
            'vars': {'http_port': 80}
        },
        'databases': {
            'hosts': ['db1.example.com'],
            'vars': {'mysql_port': 3306}
        },
        '_meta': {
            'hostvars': {
                'web1.example.com': {'ansible_host': '192.168.1.10'},
                'web2.example.com': {'ansible_host': '192.168.1.11'},
                'db1.example.com': {'ansible_host': '192.168.1.20'}
            }
        }
    }

if __name__ == '__main__':
    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        print(json.dumps(build_inventory(), indent=2))
    elif len(sys.argv) == 3 and sys.argv[1] == '--host':
        print('{}')
    else:
        sys.exit(1)

21.5 Integration externer Datenquellen

21.5.1 CSV-Datei

servers.csv:

hostname,group,ip,environment
web1.example.com,webservers,192.168.1.10,production
web2.example.com,webservers,192.168.1.11,production
db1.example.com,databases,192.168.1.20,production

csv_inventory.py:

#!/usr/bin/env python3
import json
import csv
import sys
from collections import defaultdict

def load_from_csv(filename):
    inventory = defaultdict(lambda: {'hosts': []})
    hostvars = {}
    
    with open(filename, 'r') as f:
        for row in csv.DictReader(f):
            group = row['group']
            hostname = row['hostname']
            
            inventory[group]['hosts'].append(hostname)
            hostvars[hostname] = {
                'ansible_host': row['ip'],
                'environment': row['environment']
            }
    
    result = dict(inventory)
    result['_meta'] = {'hostvars': hostvars}
    return result

if __name__ == '__main__':
    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        inventory = load_from_csv('servers.csv')
        print(json.dumps(inventory, indent=2))
    elif len(sys.argv) == 3 and sys.argv[1] == '--host':
        print('{}')

21.5.2 API-Integration

#!/usr/bin/env python3
import json
import sys
import requests
from urllib.parse import urljoin

class APIInventory:
    def __init__(self, api_base):
        self.api_base = api_base
        
    def fetch_servers(self):
        try:
            url = urljoin(self.api_base, '/api/servers')
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            return response.json()
        except requests.RequestException:
            return []
    
    def build_inventory(self):
        servers = self.fetch_servers()
        inventory = {'_meta': {'hostvars': {}}}
        
        for server in servers:
            group = server.get('role', 'ungrouped')
            hostname = server['hostname']
            
            if group not in inventory:
                inventory[group] = {'hosts': []}
            
            inventory[group]['hosts'].append(hostname)
            inventory['_meta']['hostvars'][hostname] = {
                'ansible_host': server['ip_address'],
                'server_role': server['role'],
                'environment': server.get('environment', 'unknown')
            }
        
        return inventory

if __name__ == '__main__':
    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        api = APIInventory('https://cmdb.company.com')
        inventory = api.build_inventory()
        print(json.dumps(inventory, indent=2))
    elif len(sys.argv) == 3 and sys.argv[1] == '--host':
        print('{}')

21.6 Erweiterte Gruppierung

def create_dynamic_groups(servers):
    inventory = {'_meta': {'hostvars': {}}}
    
    for server in servers:
        hostname = server['hostname']
        
        # Basis-Gruppierung
        role_group = f"role_{server['role']}"
        env_group = f"env_{server['environment']}"
        
        for group in [role_group, env_group]:
            if group not in inventory:
                inventory[group] = {'hosts': []}
            inventory[group]['hosts'].append(hostname)
        
        # Host-Variablen
        inventory['_meta']['hostvars'][hostname] = {
            'ansible_host': server['ip'],
            'server_role': server['role'],
            'environment': server['environment']
        }
    
    return inventory

21.7 Testing und Debugging

# JSON-Syntax validieren
./inventory.py --list | python3 -m json.tool

# Ansible-Validierung
ansible-inventory -i inventory.py --list

# Struktur visualisieren
ansible-inventory -i inventory.py --graph

# Verbindungstest
ansible -i inventory.py all -m ping --limit webservers

21.8 Performance-Optimierung

Bei größeren Umgebungen sollten Caching-Mechanismen implementiert werden:

import os
import time
import pickle

class CachedInventory:
    def __init__(self, cache_file='/tmp/ansible_inventory.cache', ttl=300):
        self.cache_file = cache_file
        self.ttl = ttl
    
    def get_cached_data(self):
        if not os.path.exists(self.cache_file):
            return None
        
        if time.time() - os.path.getmtime(self.cache_file) > self.ttl:
            return None
        
        with open(self.cache_file, 'rb') as f:
            return pickle.load(f)
    
    def save_to_cache(self, data):
        with open(self.cache_file, 'wb') as f:
            pickle.dump(data, f)

21.9 Hinweise

21.10 Häufige Probleme

Problem Lösung
Skript nicht ausführbar chmod +x inventory.py
Ungültiges JSON Mit python3 -m json.tool testen
API-Timeouts Timeout-Parameter setzen, Retry-Logik
Encoding-Probleme UTF-8 explizit verwenden

Dynamische Inventories bieten die Flexibilität, Ansible optimal in bestehende Infrastruktur-Landschaften zu integrieren, ohne auf externe Plugins angewiesen zu sein.