cc-cli-Inventar — Ansible-Inventory für CIVITAS/CORE
Ziel
Dieses Dokument beschreibt die Struktur des Ansible-Inventorys, das cc_cli für das Deployment der CIVITAS/CORE-Plattform benötigt. Es dient als Referenz für den Bau des Templates templates/inventory.yml.tpl und der render_inventory()-Funktion in modules/06_civitas.sh.
Hintergrund
Das Inventory wird vom cc_cli wizard erzeugt. Die Befragung ist interaktiv. Für die automatisierte Installation stellen wir ein vorbereitetes Template bereit, dessen Platzhalter durch render_inventory() ersetzt werden.
Das Inventory ist kein einfaches YAML, sondern ein Ansible-Inventory mit der Standardstruktur all → vars → children → controller → hosts → vars.
Repository-Integration für cc_cli exec
Das Inventory allein genügt nicht für cc_cli exec. Die ausführbaren Ansible-Playbooks liegen nicht im pip-Paket cc-cli, sondern im CIVITAS/CORE-Repository. Die Bereitstellung des Repository-Arbeitskontexts ist wie folgt entschieden:
| Aspekt | Festlegung |
|---|---|
| Repository-URL | https://gitlab.com/civitas-connect/civitas-core/civitas-core-v1/civitas-core.git |
| Repository-Pfad (VM) | /opt/civitas-core-v1 |
| Symlink (aktive Version) | /opt/civitas-core → /opt/civitas-core-v1 |
| Inventory-Ablage | ${CC_CLI_REPO_PATH}/cc_cli_inventory.yml |
Arbeitsverzeichnis für cc_cli | ${CC_CLI_REPO_PATH} (cd vor validate/exec) |
| Schema-Datei | ./core_platform/inventory_schema.json im Repository |
Dieses Dokument spezifiziert den Inhalt des Inventorys und den Dateinamen. Der Arbeitskontext (Repository-Workspace) ist in installationsphasen-und-abnahme.md (Phase 2, Schritte 2.2–2.4) und skriptarchitektur.md (Modul 06, Abschnitt „Repository-Workspace") spezifiziert.
Wizard-Fragen und Antworten
| Frage | Antwort |
|---|---|
| Wizard mode | expert |
| Deployment target | remote production deployment |
| Domain | udp.data-dna.eu |
| Environment name | cc-prd |
| Kubernetes context | default |
| Ingress controller class | nginx |
| Storage class (RWO/RWX/LOC) | local-path |
| Cert-Manager-Issuer-Name | selfsigned-issuer |
| CA certificate path | (leer) |
| Ansible health checks | No (deaktiviert — TLS endet an Caddy, Health-Check in der VM nicht sinnvoll) |
| Email server | mxe92c.netcup.net |
| Email user | admin@data-dna.eu |
| Email password | (maskiert) |
| Email from address | no-reply@data-dna.eu |
| Passwords selbst setzen? | No (Auto-Generierung) |
| Private GitLab-Repositories | No |
| Access-Komponenten | APISIX |
| Context-Komponenten | Frost |
| Dashboard-Komponenten | Service Portal, Superset |
| Geodata-Komponenten | GeoServer, Masterportal, Portal Backend |
| Operation-Komponenten | Monitoring, PgAdmin, Velero Backup |
| Datacatalog-Komponenten | (none) |
| Monitoring-Komponenten | Prometheus, Grafana, Alertmanager, Loki, Promtail |
| CA cert download from Service Portal? | No |
Hinweis context: k3s schreibt
/etc/rancher/k3s/k3s.yamlmit dem Context-Namendefault, nichtk3s. Der Wizard-Output enthältk3sals Antwort — das ist ein bekannter Fehler in der Wizard-UI. Im generierten Inventory und im Templateinventory.yml.tplistdefaultverbindlich. Im Inventory-Abschnittinv_k8s.config.contextsteht entsprechend"default".
Inventory-Struktur
Das Inventory folgt der Ansible-Konvention:
all:
vars:
DOMAIN: "udp.data-dna.eu"
ENVIRONMENT: "cc-prd"
kubeconfig_file: config
children:
controller:
hosts:
localhost:
ansible_host: 127.0.0.1
ansible_connection: local
ansible_python_interpreter: "{{ ansible_playbook_python }}"
vars:
inv_k8s: # Kubernetes-Konfiguration
config:
context: "default" # war: "k3s" — k3s verwendet intern den Context-Namen "default"
storage_class:
rwo: "local-path"
rwx: "local-path"
loc: "local-path"
ingress:
ca_path: ""
http: false
cert_manager:
issuer_name: "selfsigned-issuer"
ingress_class: nginx
gitlab_access:
user_email: ''
user: ''
token: ''
inv_op_stack: # Operation Stack (Monitoring, Backup, PGAdmin)
keel_operator:
enable: false
admin: "admin@{{ DOMAIN }}"
password: "***"
pgadmin:
enable: true
default_email: "admin@{{ DOMAIN }}"
default_password: "***"
kyverno_operator:
enable: false
monitoring:
enable: true
prometheus:
enable: true
grafana:
enable: true
alertmanager:
enable: true
loki:
enable: true
alloy:
enable: true
promtail:
enable: true
velero:
enable: true
backup:
location_name: ""
access_key: ""
bucket: ""
region: ""
endpoint: ""
secret: ""
prometheus:
enable: false
inv_access: # Access Stack (Keycloak, APISIX, Service Portal)
enable: true
platform:
admin_first_name: "Admin"
admin_surname: "Admin"
admin_email: "admin@{{ DOMAIN }}"
master_username: "admin@{{ DOMAIN }}"
master_password: "***"
k8s_secret_name: "{{ ENVIRONMENT }}-keycloak-admin"
hostname: "https://idm.{{ DOMAIN }}"
keycloak:
enable: true
log_level: "INFO"
replicas: 1
enable_logical_backup: false
theme: "keycloak"
password_policy:
length: 12
digits: 1
lowerCase: 1
upperCase: 1
specialChars: 1
notUsername: true
forceExpiredPasswordChange: false
passwordHistory: 5
apisix:
enable: true
dashboard:
enable: false
api_credentials:
admin_role: "***"
viewer_role: "***"
service_portal:
enable: true
certs:
enable: false
oidc:
enable: false
inv_cm: # Context Management (Frost)
frost:
enable: true
mqtt:
enable: false
session_affinity: "None"
quantumleap:
enable: false
stellio:
enable: false
inv_da: # Dashboards (Superset)
superset:
enable: true
mapbox_api_token: "TODO_PLEASE_SET_A_VALUE"
db_secret: "***"
admin_user_name: admin
admin_user_password: "***"
redis_auth_password: "***"
grafana:
enable: false
inv_gd: # Geodata Stack
enable: true
gd_components:
- enable: true
mapfish:
enable: false
geoserver:
enable: true
geoserverPassword: "***"
portal_backend:
enable: true
inv_addons:
import: false
addons: []
inv_checks:
enable: true
api:
default_max_retries: 20
deployment:
default_max_retries: 30
inv_email:
server: mxe92c.netcup.net
user: admin@data-dna.eu
password: "***"
email_from: no-reply@data-dna.eu
inv_datacatalog:
piveau:
enable: falseWichtig: Passwörter wurden vom Wizard auto-generiert (
--set passwords yourself: No). Bei manuellem Setzen wären die Werte in der Inventory-Datei Klartext. Der_patch_ingress_for_external_tlsbleibt erhalten, da das Inventoryingress.http: falsesetzt (kein HTTP ohne SSL) – der nginxssl-redirect muss dennoch deaktiviert werden, da TLS auf Caddy terminiert wird.
Abweichungen von der bisherigen Annahme
| Bisherige Annahme (falsch) | Tatsächliche Struktur |
|---|---|
domain: ... (Top-Level) | all.vars.DOMAIN: "..." |
smtp: { host, port, user, password } | all.children.controller.vars.inv_email: { server, user, password, email_from } |
admin: { email } | inv_access.platform.admin_email, inv_op_stack.pgadmin.default_email |
kubernetes: { namespace, ingressClass } | inv_k8s: { config.context, storage_class, ingress, cert_manager, ingress_class } |
| Einfaches YAML | Ansible-Inventory mit all → children → controller → vars |
Konsequenzen für das Installationsskript
- Template-Datei: Die Vorlage liegt als
templates_V1/inventory.yml.tplund wird durchrender_inventory()in Module 06 zucc_cli_inventory.ymlverarbeitet. Ursprünglich alsconfig.yaml.tplgeplant, wurde der Name zur besseren Unterscheidbarkeit aufinventory.yml.tplgeändert. render_inventory()ersetzt alle Platzhalter (PLACEHOLDER_*) des Inventars, insbesonderePLACEHOLDER_DOMAIN,PLACEHOLDER_ENVIRONMENT,PLACEHOLDER_SMTP_HOST,PLACEHOLDER_SMTP_USER,PLACEHOLDER_SMTP_PASS,PLACEHOLDER_ADMIN_EMAILsowie alle Komponenten-Passwörter.- Passwörter: Das Inventory enthält viele Passwort-Felder. Von außen gesetzte Passwörter (
ADMIN_PASS,TENANT_ADMIN_PASS) werden aus Umgebungsvariablen übernommen; alle weiteren Passwörter werden pro Skriptlauf viagen_policy_password()frisch generiert. Die Inventory-Datei wird nachcc_cli execdurch den EXIT-Trap gelöscht. - Komponenten-Auswahl: Die im Wizard gewählten Komponenten (
enable: true/false) sind als Template-Defaults gesetzt. Werte, die vom Zielsystem abhängen (z. B. Velero-Credentials), bleiben als Platzhalter (CHANGE_ME) erhalten und müssen vor dem ersten Skriptlauf manuell gesetzt werden. PLACEHOLDER_*statt: Anders als im initialen Entwurf (Jinja-Notation) verwendet das Template das SchemaPLACEHOLDER_UPPER_CASE, da die Inventory-Datei im YAML-Format vorliegt und-Klammern mit YAML-/Ansible-Syntax kollidieren würden. Die Ersetzung erfolgt ausschließlich durchsedinrender_inventory().- Ausgabepfad:
render_inventory()schreibt die fertige Inventory-Datei nach${CC_CLI_PLAYBOOK_DIR}/cc_cli_inventory.yml(d. h./opt/civitas-core-v1/core_platform/cc_cli_inventory.yml). Der EXIT-Trap des Entry-Points löscht diese Datei nach Skriptende (rm -f "${CONFIG_YAML_PATH:-}"). - Schema-Referenz: Die erste Zeile des Wizard-Outputs enthält einen
$schema-Verweis auf das JSON-Schema des Projekts. Dieser sollte im Template erhalten bleiben. - Repository-Arbeitskontext: Das Inventory wird im Repository-Workspace unter
${CC_CLI_PLAYBOOK_DIR}/cc_cli_inventory.ymlabgelegt. Der Workspace wird durch Schritt 2.2 (setup_repo_workspace) bereitgestellt. Das Repository liegt unter/opt/civitas-core-v1, der Symlink/opt/civitas-corezeigt auf die aktive Version. - Velero: Im Template wird
velero.enable: falseals Default gesetzt. Das Feld wird nur auftruegeändert, wenn alle fünf Velero-Felder (access_key,bucket,region,endpoint,secret) als Env-Vars gesetzt und nicht leer sind. Die Prüfung erfolgt inrender_inventory()vor dem sed-Schritt. Solange ein Feld fehlt oder den Wert""hat, bleibtvelero.enable: falseim gerenderten Inventory. - Health-Checks aktiviert:
inv_checks.enableist auftruegesetzt. Der incc_cli execintegrierte Ansible-Health-Check ruft die externen Endpunkte (https://idm.${DOMAIN}/) auf. Dank der HAProxy- TCP-Passthrough-Architektur terminiert nginx in der VM das TLS selbst und routet korrekt zum Ziel-Service (HTTP 200). Der frühere Workaround (ssl-redirect=false, tls-Sektion entfernen) entfällt. Voraussetzung: Das Root-CA-Cert (Variante C, self-signed-CA) muss im certifi-Bundle des venv eingetragen sein (Schritt 1.5d), sonst scheitern die HTTPS-Health-Checks mitCERTIFICATE_VERIFY_FAILED. - Python-Abhängigkeiten im venv: Zusätzlich zu
cc-cliundansiblewerden die Paketekubernetes,openshift(für k8s-Ansible-Module) undjmespath(fürjson_query-Filter in Playbooks) im venv installiert. - Ansible-Collections: Nach der pip-Installation müssen die benötigten Ansible-Collections über
ansible-galaxy collection installbezogen werden:kubernetes.core– Kubernetes-Ansible-Modulecommunity.grafana– Grafana-Integrationcommunity.mongodb==1.3.2– MongoDB-Integration Ohne diese Collections schlagen Playbooks, die die entsprechenden Module verwenden, mitmodule not found-Fehlern fehl.
Secrets-Management und Admin-Accounts
Datenbankpasswörter: vollautomatisch via Zalando-Operator
Alle Komponenten (Keycloak, Frost, Stellio, QuantumLeap, Superset, GeoData) folgen demselben Schema:
- Zalando Postgres-Operator erstellt einen PostgreSQL-Cluster.
- Ein Kubernetes-Secret wird automatisch mit
usernameundpassword(base64) generiert. - Das Ansible-Playbook liest das Secret via
kubernetes.core.k8s_infound dekodiert es. - Die Werte werden per
set_factalsCOMPONENT_POSTGRES_USERNAME/PASSWORDbereitgestellt. - Helm-Values und Deployment-Templates greifen auf diese decodierten Werte zu.
Kein einziges Datenbankpasswort wird im Inventory oder in .env.local konfiguriert. Der Zalando-Operator generiert sämtliche DB-Credentials vollautomatisch. Dies betrifft:
- Keycloak-Datenbank
- Frost-Server-Datenbank
- Superset-Datenbank
- GeoServer-Datenbank
- Grafana-Datenbank
- QuantumLeap-Datenbank (inkl. separatem Superuser-Secret für TimescaleDB)
- Stellio-Datenbank
Diese Passwörter dürfen nicht als Umgebungsvariablen externalisiert werden.
Die drei Admin-Accounts im Inventory
Aus der Analyse der Playbook-Struktur ergeben sich genau drei Accounts, die im Inventory konfiguriert werden müssen:
| Account | Inventory-Schlüssel | Zweck |
|---|---|---|
| Keycloak Master-Admin | inv_access.platform.master_username | Keycloak-Admin-UI + API während des Deployments |
inv_access.platform.master_password | ||
| Platform-Admin (IAM) | inv_access.platform.admin_email | Erster Realm-User in Keycloak (Template platform_admin.json); |
inv_access.platform.admin_first_name | wird auch als pgAdmin-Login verwendet | |
inv_access.platform.admin_surname | ||
| Tenant-Admin | inv_access.tenant.tenant_email | Tenant-Verwaltung im Realm; OIDC-Login |
inv_access.tenant.tenant_username | ||
inv_access.tenant.tenant_password |
Keycloak Master-Admin + Platform-Admin: Ein gemeinsamer Wert
Die Recherche in den Ansible-Playbooks ergibt einen durchgehenden Flow ohne zweites Inventory-Feld:
Inventory → K8S-Secret → Ansible-Fact
inv_access.platform.master_username ──b64encode──→ MASTER_USERNAME ──b64decode──→ ADMIN_USERNAME
inv_access.platform.master_password ──b64encode──→ MASTER_PASSWORD ──b64decode──→ ADMIN_PASSWORDADMIN_PASSWORD erfüllt beide Rollen:
- API-Login bei der Keycloak Admin-REST-API (setup_keycloak_tenant.yml, Zeile 21-23)
- Initiales Passwort des platform_admin-Realm-Users (keycloak_8_users.yml, Zeile 163-166)
Es existiert kein separates Inventory-Feld admin_password. Wird im Inventory inv_access.platform.master_password gesetzt, ist dieser Wert automatisch auch das initiale Passwort des platform_admin-Users. Eine abweichende Konfiguration ist nicht vorgesehen.
pgAdmin-Login-Mechanismus
Der pgAdmin-Admin-Account wird nicht separat konfiguriert. Das Playbook tasks/operation/pgadmin.yml setzt:
pgadmin_admin_email: >-
{{ inv_access.tenant.tenant_email if configure_central_idm | default(false)
else inv_access.platform.admin_email }}pgAdmin verwendet also denselben Account wie der Platform-Admin (inv_access.platform.admin_email). Ein separater Wert ist nicht erforderlich.
Keycloak-Master-Secret in Kubernetes
Frost und Superset lesen das Keycloak-Master-Credentials-Secret aus Kubernetes:
inv_access.platform.k8s_secret_name: "{{ ENVIRONMENT }}-keycloak-admin"Dieses Secret wird vom Keycloak-Playbook während Phase 2 angelegt und enthält MASTER_USERNAME und MASTER_PASSWORD (base64). Voraussetzung: die Felder master_username und master_password im Inventory müssen korrekt gesetzt sein.
Konsequenzen für .env.local und render_inventory()
Die .env.local-Datei benötigt exakt diese Passwort-Variablen (keine weiteren):
export ADMIN_EMAIL="admin@data-dna.eu"
# → inv_access.platform.master_username (auch admin_email im platform_admin-User)
export ADMIN_PASS="..."
# → inv_access.platform.master_password (auch initiales platform_admin-Passwort, identisch!)
export TENANT_ADMIN_PASS="..."
# → inv_access.tenant.tenant_password (separat, nur bei configure_central_idm aktiv)Passwort-Policy für ADMIN_PASS: Das Passwort muss die Keycloak-Policy erfüllen, die im Inventory konfiguriert ist:
- Mindestens 12 Zeichen
- Mindestens 1 Ziffer
- Mindestens 1 Großbuchstabe
- Mindestens 1 Kleinbuchstabe
- Mindestens 1 Sonderzeichen
Hinweis zu TENANT_ADMIN_PASS: Der Tenant-Admin wird nur angelegt, wenn der Keycloak-Flow mit --tags tenant läuft (configure_central_idm: true). Die Inventory-Felder (tenant_email, tenant_username, tenant_password) sollten trotzdem immer gesetzt sein, da das Playbook sie beim Einlesen des Inventars erwartet.
Alle anderen Passwörter (APISIX, Superset, Grafana, GeoServer, Piveau, Redis) werden in render_inventory() automatisch via gen_policy_password() generiert. Sie sind flüchtig: das Inventory wird nach cc_cli exec gelöscht.
Wichtig: Es gibt keinen separaten Platzhalter für das Passwort des platform_admin-Users. master_password übernimmt beide Rollen, daher erscheint ${ADMIN_PASS} im Inventory-Template nur einmal (an der Stelle von master_password). Die frühere Annahme eines zweiten Platzhalters PLACEHOLDER_KC_ADMIN_PASS war falsch und wurde entfernt.
Mapping der Platzhalter in render_inventory():
| Platzhalter | Env-Var / Quelle | Zweck |
|---|---|---|
PLACEHOLDER_DOMAIN | ${DOMAIN} | Basis-Domain |
PLACEHOLDER_ENVIRONMENT | ${CC_ENVIRONMENT} | Environment-Name |
PLACEHOLDER_ADMIN_EMAIL | ${ADMIN_EMAIL} | Platform-Admin-E-Mail (= master_username) |
PLACEHOLDER_KC_MASTER_PASS | ${ADMIN_PASS} | Keycloak-Master-Password (auch platform_admin-Passwort) |
PLACEHOLDER_TENANT_PASS | ${TENANT_ADMIN_PASS} | Tenant-Admin-Passwort (separat) |
PLACEHOLDER_SMTP_PORT | ${SMTP_PORT:-587} | SMTP-Port (Standard 587) |
| Alle weiteren | gen_policy_password() | Auto-generiert, flüchtig |
Festlegungen
- Das Installationsskript verwendet ein Template im Ansible-Inventory-Format.
- Der Dateiname lautet
inventory.yml.tpl(bzw. im Skripttemplates/inventory.yml.tpl). - Die Funktion
render_inventory()erzeugt die Inventory-Datei unter${CC_CLI_REPO_PATH}/cc_cli_inventory.yml(im Repository-Workspace, nicht in/tmp). - Alle Secrets werden durch Platzhalter ersetzt, die über Env-Vars befüllt werden.
- Die Komponenten-Auswahl (enable/disable) wird zunächst als Template-Default gesetzt. Eine spätere Externalisierung über Env-Vars ist möglich.
- Der Dateiname
cc_cli_inventory.ymlist verbindlich –cc_clisucht diese Datei im Arbeitsverzeichnis. - Das Inventory ist ohne den Repository-Kontext (Playbooks, Schema) nicht ausführbar. Der Kontext wird durch Schritt 2.2 (Repository-Klon nach
/opt/civitas-core-v1) bereitgestellt.