Dynamisches Consul-Inventory mit Semaphore
Überblick
Diese Anleitung zeigt, wie Sie HashiCorp Consul als dynamische Inventory-Quelle in Semaphore verwenden. Anstatt Hosts manuell aufzulisten, fragt Ansible zur Laufzeit den Consul-Katalog ab, um zu ermitteln, welche Hosts angesprochen werden sollen.
Dieser Ansatz verwendet ein Python-Inventory-Skript, das in Ihrem Git-Repository eingecheckt ist. Semaphore führt das Skript beim Ausführen eines Playbooks automatisch aus.
Voraussetzungen
- Ein laufender Consul-Cluster mit registrierten Nodes
- Ein Consul-ACL-Token mit Lesezugriff auf den Katalog (nur wenn ACLs aktiviert sind)
- Python 3 auf dem Semaphore-Host (oder Runner) installiert
- Ein Git-Repository zum Speichern Ihres Playbooks und Inventory-Skripts
Schritt 1 — Das Inventory-Skript erstellen
Erstellen Sie in Ihrem Repository eine Datei namens inventory/consul_inventory.py. Dieses Skript fragt die Consul-HTTP-API ab und gibt Host-Informationen in dem Format zurück, das Ansible erwartet.
#!/usr/bin/env python3
"""
Consul dynamic inventory for Ansible.
Groups nodes by node_meta values and filters out unhealthy nodes.
"""
import json
import os
import sys
import urllib.request
import ssl
CONSUL_ADDR = os.environ.get("CONSUL_HTTP_ADDR", "https://consul.example.com")
CONSUL_TOKEN = os.environ.get("CONSUL_HTTP_TOKEN", "")
def consul_get(path):
url = f"{CONSUL_ADDR}/v1/{path}"
req = urllib.request.Request(url)
if CONSUL_TOKEN:
req.add_header("X-Consul-Token", CONSUL_TOKEN)
ctx = ssl.create_default_context()
with urllib.request.urlopen(req, context=ctx) as resp:
return json.loads(resp.read())
def is_healthy(node_name):
"""Return True if the node has a passing serfHealth check."""
try:
checks = consul_get(f"health/node/{node_name}")
return any(
c["CheckID"] == "serfHealth" and c["Status"] == "passing"
for c in checks
)
except Exception:
return False
def build_inventory():
inventory = {"_meta": {"hostvars": {}}}
all_hosts = []
for node in consul_get("catalog/nodes"):
name = node["Node"]
if not is_healthy(name):
continue
all_hosts.append(name)
inventory["_meta"]["hostvars"][name] = {
"ansible_host": node["Address"],
"ansible_user": "your_ssh_user",
"ansible_python_interpreter": "/usr/bin/python3",
}
inventory["all"] = {"hosts": all_hosts}
return inventory
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--host":
print(json.dumps({}))
else:
print(json.dumps(build_inventory(), indent=2))
Machen Sie das Skript ausführbar:
chmod +x inventory/consul_inventory.py
Sie können dieses Skript anpassen, um Hosts nach Consul-Node-Metadaten, Service-Tags oder Datacentern zu gruppieren. Das obige Beispiel ist ein minimaler Ausgangspunkt.
Schritt 2 — Das Repository einrichten
Ihr Repository sollte wie folgt aussehen:
my-project/
├── inventory/
│ └── consul_inventory.py
└── playbook.yml
Dieser Ansatz verwendet ausschließlich die Python-Standardbibliothek, um die Consul-API direkt abzufragen. Es sind keine zusätzlichen Ansible-Collections erforderlich, damit das Inventory-Skript funktioniert.
Ein einfaches Test-Playbook (playbook.yml):
- hosts: all
gather_facts: false
tasks:
- name: Hello World
ansible.builtin.debug:
msg: "Hello from {{ inventory_hostname }}"
Pushen Sie dieses Repository zu Ihrem Git-Anbieter.
Schritt 3 — Semaphore konfigurieren
Eine Variablengruppe hinzufügen
Das Inventory-Skript liest die Consul-Adresse und das Token aus Umgebungsvariablen. Erstellen Sie in Semaphore eine Variablengruppe, um diese Werte zu übergeben.
- Gehen Sie zu Ihrem Projekt und klicken Sie auf Variablengruppe
- Klicken Sie auf Neue Variablengruppe
- Vergeben Sie einen Namen (z. B.
consul-inventory) - Fügen Sie unter Umgebungsvariablen Folgendes hinzu:
CONSUL_HTTP_ADDR=https://consul.example.comCONSUL_HTTP_TOKEN=your-consul-acl-token(nur erforderlich, wenn ACLs in Ihrem Consul-Cluster aktiviert sind)
- Klicken Sie auf Erstellen
Wenn in Ihrem Consul-Cluster keine ACLs aktiviert sind, können Sie die Variable CONSUL_HTTP_TOKEN weglassen. Das Inventory-Skript funktioniert trotzdem — es sendet dann einfach kein Authentifizierungstoken mit seinen API-Anfragen.
Das Repository hinzufügen
- Gehen Sie zu Repositories und klicken Sie auf Neues Repository
- Geben Sie die Git-URL Ihres Repositories ein
- Wählen Sie den Zugangsschlüssel für Ihren Git-Anbieter aus
- Klicken Sie auf Erstellen
Das Inventory hinzufügen
- Gehen Sie zu Inventory und klicken Sie auf Neues Inventory
- Vergeben Sie einen Namen (z. B.
consul-dynamic-inventory) - Wählen Sie Datei als Typ
- Geben Sie den Pfad ein:
inventory/consul_inventory.py - Wählen Sie den SSH-Schlüssel aus, den Ansible für die Verbindung zu Ihren Hosts verwendet
- Klicken Sie auf Erstellen
Der Pfad ist relativ zum Stammverzeichnis Ihres Git-Repositories. Semaphore klont das Repository und übergibt diesen Pfad an ansible-playbook -i inventory/consul_inventory.py.
Eine Aufgabenvorlage erstellen
- Gehen Sie zu Aufgabenvorlagen und klicken Sie auf Neue Vorlage
- Vergeben Sie einen Namen (z. B.
Consul Hello World) - Setzen Sie Playbook auf
playbook.yml - Wählen Sie das Repository, das Inventory und die Variablengruppe aus, die Sie oben erstellt haben
- Klicken Sie auf Erstellen
Schritt 4 — Ausführen
Klicken Sie in Ihrer Aufgabenvorlage auf Ausführen. Semaphore wird:
- Ihr Repository klonen
- Das Playbook mit Ihrem Consul-Inventory-Skript ausführen
- Die Ausgabe im Aufgabenprotokoll anzeigen
Sie sollten eine Ausgabe wie diese sehen:
TASK [Hello World] *************************************************************
ok: [node-01] => {
"msg": "Hello from node-01"
}
ok: [node-02] => {
"msg": "Hello from node-02"
}
Hosts nach Metadaten gruppieren
Consul unterstützt Node-Metadaten — Schlüssel-Wert-Paare, die jedem Node zugeordnet sind. Sie können diese verwenden, um Ansible-Gruppen automatisch zu erstellen.
Fügen Sie Folgendes in die Funktion build_inventory() Ihres Skripts ein, nach dem Setzen der Host-Variablen:
# Get node metadata
node_detail = consul_get(f"catalog/node/{name}")
meta = node_detail.get("Node", {}).get("Meta", {})
# Group by metadata keys
for key in ("role", "env", "os"):
val = meta.get(key)
if val:
group = f"{key}_{val}"
inventory.setdefault(group, {"hosts": []})
inventory[group]["hosts"].append(name)
Dadurch entstehen Gruppen wie role_webserver, env_production oder os_ubuntu. Diese können Sie anschließend in Ihren Playbooks ansprechen:
- hosts: role_webserver
tasks:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted