Ich habe den Artikel massiv von einer KI “glätten” lassen, da ich mich beim Mitschreiben mehrmals verzettelt habe und ich den ursprünglichen Verlauf weder meinem zukünftigen Ich noch irgendjemand anderem antun wollte. Ich kann nicht so schön schreiben.
Damit ich mit Ansible ein dynamisches Inventory von NetBox abrufen kann, brauche ich zunächst ein paar Bausteine.
Ich erzeuge zunächst einen neuen zufälligen API-Token ohne Schreibrechte in der GUI des NetBox-LXC. Dabei wird zunächst ein fehlender API Token Pepper bemängelt. Man kann einen zufälligen Wert z. B. mit
python3 -c "import secrets; print(secrets.token_hex(32))"
in der NetBox-Shell erzeugen. Den soeben erzeugten Zufallswert trage ich danach in der /opt/netbox/netbox/netbox/configuration.py bei API_TOKEN_PEPPERS ein.
Verpixelt sieht viel besser aus als der rote Balken. 😎
Danach NetBox neu starten, um die Konfiguration mit den Änderungen neu einzulesen:
systemctl restart netbox netbox-rq
Der Token wird nur einmal angezeigt, also wandert er ebenfalls in meine KeePass-Datenbank (KeePass und die Synchronisierung einer Datenbankdatei über alle Endgeräte wäre bei Gelegenheit einen eigenen Artikel wert).
Als Nächstes lege ich ein neues Inventory an, das ich NetBox Dynamic nenne, Type ist zunächst Static YAML. Sobald man den Type ausgewählt hat, kann man in dem neuen Fenster diesen Inhalt eintragen:
plugin: netbox.netbox.nb_inventory api_endpoint: https://<netbox-lxc-ip> token: "<dein-token>" validate_certs: false config_context: false group_by: - site - role - tags
Der zuvor erzeugte Token beginnt mit Bearer nbt_…. Der eigentliche Token startet erst ab nbt – “Bearer” und das Leerzeichen davor sind nicht Teil des Tokens.
Credentials lasse ich vorerst auf None. Ansible greift grundsätzlich via SSH auf die Endgeräte zu. Damit man nicht unzählige Username/Passwort-Kombinationen (gefährlich) bei Ansible hinterlegen muss, wird der Zugriff über einen SSH-Key geregelt. Den öffentlichen Teil des Ansible-SSH-Schlüssels kann ich dann auf allen Endgeräten hinterlegen.
Bei dem neuen Semaphore-LXC waren noch keine SSH-Keys hinterlegt; ich erzeuge in der Shell von Semaphore einfach einen neuen:
ssh-keygen -t ed25519 -f /opt/semaphore/ansible-key -N "" -C "semaphore-ansible"
Den öffentlichen Schlüssel kann man sich anzeigen lassen mit
cat /opt/semaphore/ansible-key.pub
Den privaten Key (nicht den mit .pub am Ende!) hinterlege ich in der Semaphore-GUI im Key Store.
Sobald das erledigt ist, kann ich im Inventory-Objekt, das ich vorher erstellt hatte, die Credentials von None auf den soeben neu hinterlegten SSH-Key umstellen.
Den öffentlichen Schlüssel habe ich mir mit cat in der Shell anzeigen lassen und den Inhalt in die Zwischenablage kopiert.
In dem NetBox-LXC lege ich die Datei ~/.ssh/authorized_keys an:
mkdir -p ~/.ssh nano ~/.ssh/authorized_keys
und befülle die Datei mit dem Inhalt aus der Zwischenablage. Anschließend die Rechte einschränken:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
Den Key teste ich gleich; dazu wechsle ich in die Shell des Semaphore-LXC und verbinde mich mit dem NetBox-LXC, wo ich soeben den Schlüssel hinterlegt habe:
ssh -i /opt/semaphore/ansible-key root@192.168.2.10
192.168.2.10 ist die IP des NetBox-LXC. Klappt 😁
Ich erstelle ein Test-Playbook in der Shell von Semaphore und committe es in mein lokales Git-Repository:
cd /opt/semaphore/playbooks
cat > test-inventory.yml << 'EOF'
---
- hosts: all
gather_facts: false
tasks:
- name: Zeige erkannten Host
debug:
msg: "Host: {{ inventory_hostname }} | IP: {{ ansible_host | default('n/a') }} | Groups: {{ group_names }}"
EOF
git add test-inventory.yml
git commit -m "Add inventory test playbook"
Ich lege ein neues Task Template an.
Das funktioniert nicht.
Semaphore meldet: “Failed to install inventory: file path must be inside secrets path”. Eine Internetsuche mit der Fehlermeldung liefert keine direkten Treffer, wohl aber ein passendes GitHub-Issue zum Semaphore-Projekt: Der Fehler tritt in Zusammenhang mit dem Inventory-Typ Static YAML auf, wenn Semaphore versucht, den Inventory-Inhalt intern als temporäre Datei zu schreiben, dabei aber eine interne Pfadvalidierung greift.
Der praktische Workaround: Inventory-Typ auf File umstellen. Statt den YAML-Inhalt direkt im Semaphore-Textfeld zu pflegen, lagere ich die Datei stattdessen ins Repo aus – das umgeht den Bug komplett, weil Semaphore dann nur eine echte Datei aus dem geklonten Repository referenziert, statt selbst etwas in einen validierten Pfad zu schreiben:
cd /opt/semaphore/playbooks mkdir -p inventory cat > inventory/netbox.yml << 'EOF' plugin: netbox.netbox.nb_inventory api_endpoint: https://<netbox-lxc-ip> token: "<dein-token>" validate_certs: false config_context: false group_by: - site - role - tags EOF git add inventory/netbox.yml git commit -m "Add NetBox dynamic inventory as file-based inventory"
Im Inventory-Objekt selbst stelle ich den Type entsprechend auf File um und trage als Pfad inventory/netbox.yml ein.
Ein Punkt, den ich hier bewusst in Kauf nehme: Der Token landet damit im Klartext im Repository. Da es sich um ein rein lokales Repo ohne externen Push handelt, ist das Risiko für mich vertretbar – für eine sauberere Lösung würde sich später Ansible Vault anbieten.
Beim erneuten Ausführen taucht ein neuer, klar eingrenzbarer Fehler auf: Ansible kennt das Plugin netbox.netbox.nb_inventory schlicht noch nicht, weil es nicht aktiviert ist. Das lässt sich über eine ansible.cfg im Repo lösen:
cd /opt/semaphore/playbooks cat > ansible.cfg << 'EOF' [inventory] enable_plugins = netbox.netbox.nb_inventory, auto, yaml, ini EOF git add ansible.cfg git commit -m "Enable netbox.netbox.nb_inventory plugin"
Semaphore kennt selbst keine globalen Umgebungsvariablen, insofern ist der Weg über die ansible.cfg im Repo hier der richtige.
Beim erneuten Testen zeigt sich außerdem, dass die group_by-Werte im Plural stehen müssen (sites, device_roles statt site, role) – das verlangt die bei mir installierte Version der netbox.netbox-Collection (3.23.0) so:
plugin: netbox.netbox.nb_inventory api_endpoint: https://192.168.2.10 token: "<dein-token>" validate_certs: false config_context: false group_by: - sites - device_roles - tags
Damit läuft der Test endlich durch: Sämtliche rund 100 Devices aus beiden Standorten werden korrekt aus NetBox gezogen, sauber gruppiert nach Site, Geräterolle und Tags.
Ein Denkfehler ist mir dabei aber zunächst nicht aufgefallen: Das debug-Modul in meinem Test-Playbook läuft komplett lokal auf dem Semaphore-Controller – es baut gar keine SSH-Verbindung zum jeweiligen Zielhost auf, sondern gibt nur bereits aus dem Inventory bekannte Variablen aus. Der “ok”-Status für alle Hosts hat also nur bestätigt, dass das Inventory korrekt befüllt wurde – nicht, dass SSH-Zugriff funktioniert.
Für einen echten Verbindungstest tausche ich das debug-Modul gegen das eingebaute ping-Modul aus, das tatsächlich eine SSH-Verbindung aufbaut:
--- - hosts: all gather_facts: false tasks: - name: Teste SSH-Verbindung ansible.builtin.ping:
Nur die zwei Hosts, auf denen ich den Ansible-Key bereits manuell hinterlegt hatte (NetBox-LXC und einer meiner DNS-Server), laufen mit “ok” durch. Alle anderen rund 100 Hosts melden erwartungsgemäß UNREACHABLE, da dort noch kein Schlüssel verteilt ist. Semaphore wertet den Task deshalb insgesamt als fehlgeschlagen (Exit-Code 4, “mindestens ein Host unreachable”) – inhaltlich ist das aber genau das erwartete, korrekte Ergebnis.
Damit steht das dynamische Inventory: NetBox liefert live und korrekt gruppiert alle Hosts, Semaphore kann darauf zugreifen, und der SSH-Verbindungstest zeigt, wo noch Schlüssel fehlen. Als Nächstes steht die gezielte Verteilung des Ansible-SSH-Keys auf die tatsächlich relevanten Zielsysteme an. Dazu dann mehr in einem der nächsten Artikel.







