Add GitOps runtime image drift alert

This commit is contained in:
2026-05-31 13:17:45 +02:00
parent 69ad9d1d3c
commit 16a266cd79
4 changed files with 68 additions and 0 deletions
+10
View File
@@ -17,6 +17,16 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
## Historische Meilensteine ## Historische Meilensteine
### 2026-05-31 - Komodo Deploy-Drift strukturell abgesichert
Nach dem Renovate-Block wurde die Ursache fuer den kurzzeitigen `nextcloud-postgres`-Drift nachgezogen: Komodo hatte den `nextcloud`-Stack beim Postgres-17.10-Deploy gestartet, aber `docker compose up -d` scheiterte zunaechst an einem Docker-Recreate-Namenskonflikt (`nextcloud-postgres` Ersatzcontainer). Der Workspace war dadurch bereits auf dem neuen Commit, waehrend der laufende DB-Container noch das alte Image nutzte. Der spaetere gezielte `docker compose up -d` aus dem aktualisierten Workspace hat den Zwischenzustand sauber aufgeloest; es blieben keine exited/dead Containerreste.
- Komodo-Update-Historie bestaetigt: ein fehlgeschlagener `DeployStack` fuer `nextcloud` mit Compose-Up-Konflikt, danach ein erfolgreicher `DeployStack`.
- Aktueller Runtime-Stand nach Pruefung: `nextcloud`, `nextcloud-postgres`, `nextcloud-redis`, `postgresql17`, `mealie-postgres`, `gitea`, `bentopdf`, `ddns-updater` und Komodo-Self-Stack laufen ohne `unhealthy`-Status; die erwarteten Images stimmen mit den Compose-Dateien ueberein.
- `services/posture-check/export-prometheus-textfile.sh` exportiert jetzt `homelab_gitops_runtime_image_match{name,project,service}` fuer laufende Compose-Container. Die Metrik vergleicht das Image aus `docker compose config --format json` gegen `docker inspect .Config.Image` des laufenden Containers und faengt damit genau den Zustand "Workspace/Compose neu, Runtime alt" ab.
- Neue Prometheus-Regel `HomelabGitOpsRuntimeImageDrift`: feuert als Warning, wenn ein laufender Compose-Container laenger als 10 Minuten nicht dem Compose-Image entspricht.
- Smoke: Exporter-Test in `/tmp/kallilab-textfile-test/homelab.prom` lieferte fuer alle erkannten Compose-Container `homelab_gitops_runtime_image_match = 1`; `promtool check rules` meldete `SUCCESS: 17 rules found`.
### 2026-05-31 - Renovate PRs #1 bis #5 gemerged und deployed ### 2026-05-31 - Renovate PRs #1 bis #5 gemerged und deployed
Die ersten fuenf Renovate-PRs wurden einzeln in `master` uebernommen, mit Policy-Check und Live-Smoke nach den Datenhalter-Aenderungen. Major-Branches wurden bewusst nicht gemerged. Die ersten fuenf Renovate-PRs wurden einzeln in `master` uebernommen, mit Policy-Check und Live-Smoke nach den Datenhalter-Aenderungen. Major-Branches wurden bewusst nicht gemerged.
+2
View File
@@ -71,6 +71,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite | | `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite |
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo |
| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand, Authelia-Repo<->Host-Drift und ntfy-Alarmierung | | `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand, Authelia-Repo<->Host-Drift und ntfy-Alarmierung |
| `services/posture-check/export-prometheus-textfile.sh` | Host-seitiger Textfile-Exporter fuer Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken |
| `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme | | `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme |
| `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` | | `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` |
| `services/authelia-diff.sh` | Vergleicht `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei; wird vom Posture-Check als Check `authelia_config_drift` aufgerufen | | `services/authelia-diff.sh` | Vergleicht `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei; wird vom Posture-Check als Check `authelia_config_drift` aufgerufen |
@@ -237,6 +238,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|---|---|---| |---|---|---|
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Unraid Host, nicht Borg-UI Inline-Hook | erzeugt aktuelle Dumps unter `/mnt/user/backups/borg/dumps/latest` | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Unraid Host, nicht Borg-UI Inline-Hook | erzeugt aktuelle Dumps unter `/mnt/user/backups/borg/dumps/latest` |
| `services/posture-check/posture-check.sh` | Unraid Host | schreibt `/mnt/user/services/posture-check/last.json` und alarmiert via ntfy bei Warning/Critical | | `services/posture-check/posture-check.sh` | Unraid Host | schreibt `/mnt/user/services/posture-check/last.json` und alarmiert via ntfy bei Warning/Critical |
| `services/posture-check/export-prometheus-textfile.sh` | Unraid Host, Cron/Textfile-Collector | schreibt Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken fuer Prometheus |
| `services/posture-check/docker-critical-events.sh` | Unraid Host | beobachtet Docker `die`/`oom`/`kill` Events und alarmiert via `homelab-alerts` | | `services/posture-check/docker-critical-events.sh` | Unraid Host | beobachtet Docker `die`/`oom`/`kill` Events und alarmiert via `homelab-alerts` |
Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei Analyse niemals Secret-Inhalte ausgeben. Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei Analyse niemals Secret-Inhalte ausgeben.
+9
View File
@@ -140,6 +140,15 @@ groups:
summary: "Critical container is down: {{ $labels.name }}" summary: "Critical container is down: {{ $labels.name }}"
description: "The host textfile exporter reports that critical container {{ $labels.name }} is not running." description: "The host textfile exporter reports that critical container {{ $labels.name }} is not running."
- alert: HomelabGitOpsRuntimeImageDrift
expr: homelab_gitops_runtime_image_match == 0
for: 10m
labels:
severity: warning
annotations:
summary: "Runtime image drift: {{ $labels.name }}"
description: "Container {{ $labels.name }} is not running the image declared by its Compose config in project {{ $labels.project }}."
- name: homelab-meta - name: homelab-meta
rules: rules:
- alert: HomelabPrometheusTargetDown - alert: HomelabPrometheusTargetDown
@@ -15,6 +15,51 @@ trap cleanup EXIT
now="$(date +%s)" now="$(date +%s)"
emit_gitops_runtime_image_metrics() {
cat <<'EOF'
# HELP homelab_gitops_runtime_image_match Whether a running Compose container uses the image currently declared by its Compose config.
# TYPE homelab_gitops_runtime_image_match gauge
EOF
if ! command -v jq >/dev/null 2>&1; then
return
fi
docker ps \
--filter label=com.docker.compose.project \
--format '{{.Names}}\t{{.Label "com.docker.compose.project"}}\t{{.Label "com.docker.compose.service"}}\t{{.Label "com.docker.compose.project.config_files"}}\t{{.Label "com.docker.compose.project.environment_file"}}' |
while IFS="$(printf '\t')" read -r container project service config_files env_file; do
[ -n "$container" ] || continue
[ -n "$service" ] || continue
[ -n "$config_files" ] || continue
config_file="${config_files%%,*}"
[ -f "$config_file" ] || continue
compose_args=(-f "$config_file")
if [ -n "$env_file" ] && [ -f "$env_file" ]; then
compose_args+=(--env-file "$env_file")
fi
expected="$(
docker compose "${compose_args[@]}" config --format json 2>/dev/null |
jq -r --arg service "$service" '.services[$service].image // empty' 2>/dev/null || true
)"
[ -n "$expected" ] || continue
running="$(docker inspect -f '{{.Config.Image}}' "$container" 2>/dev/null || true)"
[ -n "$running" ] || continue
match="0"
if [ "$running" = "$expected" ]; then
match="1"
fi
printf 'homelab_gitops_runtime_image_match{name="%s",project="%s",service="%s"} %s\n' \
"$container" "$project" "$service" "$match"
done
}
{ {
cat <<'EOF' cat <<'EOF'
# HELP homelab_textfile_exporter_last_run_timestamp_seconds Unix timestamp of the last successful homelab textfile exporter run. # HELP homelab_textfile_exporter_last_run_timestamp_seconds Unix timestamp of the last successful homelab textfile exporter run.
@@ -34,6 +79,8 @@ EOF
printf 'homelab_critical_container_running{name="%s"} %s\n' "$container" "$running" printf 'homelab_critical_container_running{name="%s"} %s\n' "$container" "$running"
done done
emit_gitops_runtime_image_metrics
cat <<'EOF' cat <<'EOF'
# HELP homelab_borg_last_completed_timestamp_seconds Unix timestamp of the most recent completed Borg backup job known to Borg UI. # HELP homelab_borg_last_completed_timestamp_seconds Unix timestamp of the most recent completed Borg backup job known to Borg UI.
# TYPE homelab_borg_last_completed_timestamp_seconds gauge # TYPE homelab_borg_last_completed_timestamp_seconds gauge