Files
cluster-knaster/platform/proxmox/ansible/roles/infra-stack/tasks/main.yml
T
Robert RappandClaude Opus 5.5 bf7c137adf
ci / test (push) Successful in 28s
infra: Compose-Stack mit Monitoring, ohne LiteLLM (Task 5.2)
- deploy/compose/infra: Dienst litellm entfernt (D4), Router auf LITELLM_BASE_URL
  (appsrv08, Port noch offen), alle Images gepinnt, Grafana und pve-exporter
  ohne Profile, node-exporter für den LXC, Zustand über KNASTER_STATE_DIR
- Prometheus: GPU-VMs per file_sd (node, gpu), Proxmox-Knoten per IP, infra-node
- Grafana: Datasource und Dashboards provisioniert (grafana.com 10347, 1860,
  14574 sowie knaster-Übersicht)
- Alertmanager: n8n-Receiver nur vorbereitet (auskommentiert)
- Ansible: Rollen infra-stack und gpu-exporters, Playbooks monitoring.yml und
  monitoring-targets.yml, Skript monitoring-targets.py (tofu output → file_sd)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:22:26 +02:00

135 lines
4.7 KiB
YAML

# Compose-Stack infra: Router, TTS, Postgres, Prometheus, Alertmanager, Grafana, pve-exporter, node-exporter.
# Idempotent: Geheimnisse nur anlegen, wenn sie fehlen; compose up ändert nur, was sich geändert hat.
- name: Werkzeuge
ansible.builtin.apt:
name: [rsync, openssl, curl]
state: present
update_cache: true
cache_valid_time: 3600
- name: Verzeichnisse
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
mode: "{{ item.mode }}"
loop:
- {path: /opt/knaster, mode: "0755"}
- {path: "{{ infra_repo_dir }}", mode: "0755"}
- {path: "{{ infra_state_dir }}", mode: "0700"}
- {path: "{{ infra_state_dir }}/secrets", mode: "0700"}
# Nur die Teile, die Build und Konfiguration brauchen. Nie .git, .venv, tofu-State oder .env-Dateien.
- name: Repo-Auszug von ops abgleichen
ansible.posix.synchronize:
src: "{{ infra_repo_src }}/"
dest: "{{ infra_repo_dir }}/"
delete: true
rsync_opts:
- "--exclude=__pycache__"
- "--exclude=.pytest_cache"
- "--exclude=*.egg-info"
- "--exclude=.env"
- "--exclude=secrets/"
- "--include=/core/***"
- "--include=/config/***"
- "--include=/catalog/***"
- "--include=/skills/***"
- "--include=/sites/***"
- "--include=/deploy/***"
- "--exclude=*"
- name: Einstellungen in .env
ansible.builtin.lineinfile:
path: "{{ infra_state_dir }}/.env"
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value }}"
create: true
mode: "0600"
loop: "{{ infra_settings | dict2items }}"
# Gibt nur Namen aus, nie Werte
- name: Fehlende Geheimnisse im Container erzeugen
ansible.builtin.shell: |
set -eu
umask 077
f={{ infra_state_dir }}/.env
for k in {{ infra_secrets | join(' ') }}; do
grep -q "^$k=" "$f" || { echo "$k=$(openssl rand -hex 24)" >> "$f"; echo "neu: $k"; }
done
chmod 0600 "$f"
args:
executable: /bin/bash
register: secrets_gen
changed_when: "'neu:' in secrets_gen.stdout"
- name: Router-Token für Alertmanager (= KNASTER_API_TOKEN, lesbar für nobody im Container)
ansible.builtin.shell: |
set -eu
umask 077
cd {{ infra_state_dir }}
v=$(sed -n 's/^KNASTER_API_TOKEN=//p' .env)
t=secrets/router_token
if [ ! -f "$t" ] || [ "$(cat "$t")" != "$v" ]; then printf '%s' "$v" > "$t"; echo geändert; fi
chown 65534:65534 "$t"
chmod 0400 "$t"
args:
executable: /bin/bash
register: router_token
changed_when: "'geändert' in router_token.stdout"
no_log: true
- name: Proxmox-Lese-Token für den pve-exporter
ansible.builtin.copy:
src: "{{ infra_pve_exporter_env }}"
dest: "{{ infra_state_dir }}/secrets/pve-exporter.env"
mode: "0600"
no_log: true
- name: file_sd-Ziele
ansible.builtin.include_tasks: targets.yml
- name: Stack bauen und starten
ansible.builtin.command: >
docker compose -p {{ infra_compose_project }} --env-file {{ infra_state_dir }}/.env
-f {{ infra_compose_file }} up -d --build --remove-orphans
register: compose_up
changed_when: compose_up.stderr is search('Container \S+\s+(Created|Recreated|Started)')
- name: Prometheus und Alertmanager laden geänderte Konfiguration (bind-mount, kein Neustart nötig)
ansible.builtin.command: "curl -fsS -m 10 -X POST http://127.0.0.1:{{ item }}/-/reload"
loop: [9090, 9093]
register: reload
retries: 15
delay: 4
until: reload.rc == 0
changed_when: false
- name: Grafana-Konto kiosk (Viewer) anlegen, Passwort aus .env
ansible.builtin.shell: |
set -eu
cd {{ infra_state_dir }}
A=$(sed -n 's/^GRAFANA_ADMIN_PASSWORD=//p' .env)
K=$(sed -n 's/^GRAFANA_KIOSK_PASSWORD=//p' .env)
umask 077
cfg=$(mktemp)
trap 'rm -f "$cfg"' EXIT
printf 'user = "admin:%s"\n' "$A" > "$cfg" # Passwort nicht in der Prozessliste
g() { curl -sS -K "$cfg" -H 'Content-Type: application/json' "$@"; }
for i in $(seq 60); do curl -fsS -m 3 http://127.0.0.1:3000/api/health >/dev/null 2>&1 && break; sleep 2; done
code=$(g -o /dev/null -w '%{http_code}' 'http://127.0.0.1:3000/api/users/lookup?loginOrEmail=kiosk')
if [ "$code" = 404 ]; then
printf '{"name":"Kiosk","login":"kiosk","password":"%s"}' "$K" \
| g -f -X POST --data @- http://127.0.0.1:3000/api/admin/users >/dev/null
echo angelegt
elif [ "$code" != 200 ]; then
echo "Grafana-API antwortet $code" >&2
exit 1
fi
id=$(g -f 'http://127.0.0.1:3000/api/users/lookup?loginOrEmail=kiosk' | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
g -f -X PATCH --data '{"role":"Viewer"}' "http://127.0.0.1:3000/api/org/users/$id" >/dev/null
args:
executable: /bin/bash
register: kiosk
changed_when: "'angelegt' in kiosk.stdout"
no_log: true