Zum Inhalt

Monitoring & Alerting (P7)

Ziel: Live-Betrieb mit minimal überwachten Risiken. Diese Datei ist Ops-Runbook für die GCP-Console-Setup-Schritte. Alle Alerts sollen pro Tenant-Projekt eingerichtet werden (Single-Tenant-Architektur).

1. Daily Firestore Backup

  • Job: scheduledFirestoreBackup (Cloud Function europe-west3)
  • Schedule: 0 2 * * * UTC (override via system_settings/backup_settings.schedule)
  • Bucket: gs://${PROJECT_ID}-firestore-backup (override über system_settings/backup_settings.bucketOverride)

One-time Setup pro Projekt

PROJECT_ID="<tenant-projekt>"

# Bucket anlegen (EU multi-region)
gsutil mb -l eu -p "$PROJECT_ID" "gs://${PROJECT_ID}-firestore-backup"

# Lifecycle: Backups älter als 35 Tage automatisch löschen
cat > /tmp/lifecycle.json <<'JSON'
{
  "lifecycle": {
    "rule": [
      { "action": { "type": "Delete" }, "condition": { "age": 35 } }
    ]
  }
}
JSON
gsutil lifecycle set /tmp/lifecycle.json "gs://${PROJECT_ID}-firestore-backup"

# IAM für den Default-Compute-SA
SA="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')-compute@developer.gserviceaccount.com"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="serviceAccount:${SA}" \
  --role="roles/datastore.importExportAdmin"

gsutil iam ch "serviceAccount:${SA}:roles/storage.admin" \
  "gs://${PROJECT_ID}-firestore-backup"

Restore

# Letztes Backup-Verzeichnis ermitteln
gsutil ls "gs://${PROJECT_ID}-firestore-backup/exports/" | tail -1

# Import (Achtung: schreibt in dieselbe DB!)
gcloud firestore import \
  "gs://${PROJECT_ID}-firestore-backup/exports/2024-12-31/2024-12-31T02:00:00_12345/"

1.2 Full System Backup (fullBackup Job)

Neu seit Mai 2026: Vollständiges tägliches Backup aller Systemartefakte.

  • Job: fullBackup (System-Job, Cloud Scheduler)
  • Schedule: 0 2 * * * UTC (täglich 02:00 Uhr)
  • Bucket: gs://${PROJECT_ID}-firestore-backup
  • Artefakte:
  • Firestore-Export (alle Collections via Management API)
  • Firebase Auth-User (JSON-Export mit PII)
  • Firestore Rules (core/firestore.rules)
  • Storage Rules (core/storage.rules)
  • Firestore Indexes (core/firestore.indexes.json)
  • CORS-Konfiguration (core/cors.prod.json, core/cors.dev.json)
  • Hosting-Headers (core/hosting_headers_core.json)
  • Remote Config (falls genutzt)

Cloud Scheduler Job registrieren

Der Job wird über das Seed-Skript in Firestore angelegt, aber der Cloud Scheduler Job muss separat registriert werden:

cd core/functions

# Einmalig pro Projekt: Cloud Scheduler Jobs für System-Jobs anlegen
node -e "
process.env.GCLOUD_PROJECT = '${PROJECT_ID}';
const schedulerUtils = require('./src/utils/scheduler_utils');

async function main() {
  await schedulerUtils.createOrUpdateSchedulerJob(
    'fullBackup',
    { type: 'cron', cronExpression: '0 2 * * *' },
    'system'
  );
  console.log('✅ fullBackup Scheduler Job angelegt');
}
main().catch(e => console.error(e)).finally(() => process.exit(0));
"

Oder über die ERP-UI: - Job Management → fullBackup auswählen - Schedule bearbeiten (ohne Änderung speichern) → triggert createOrUpdateSchedulerJob

Backup-Struktur

gs://${PROJECT_ID}-firestore-backup/
  manual/
    20260609_140000/
      firestore/         # Firestore-Export
      auth/users_20260609_140000.json
      config/
        firestore.rules
        storage.rules
        firestore.indexes.json
        cors.prod.json
        hosting_headers_core.json
  exports/               # Legacy scheduledFirestoreBackup

Restore (manuell)

Siehe BACKUP & RESTORE COMMAND SYSTEM in .github/copilot-instructions.md:

# Alle Backups auflisten
backup:list

# Spezifisches Backup wiederherstellen
restore <backup-id>

# Nur Firestore
restore:firestore gs://.../manual/20260609_140000/firestore

# Nur Config-Dateien aus Git
restore:config <git-ref>

1.3 GCS Object Versioning

Pflicht seit Juni 2026: Aktiviert für alle Firebase Storage Buckets.

Object Versioning schützt vor versehentlichem Überschreiben oder Löschen von Dateien im Firebase Storage Bucket.

Aktivierung

Via Google Cloud Console:

  1. Cloud Storage Browser öffnen
  2. Bucket ${PROJECT_ID}.firebasestorage.app (oder .appspot.com) auswählen
  3. Tab ProtectionObject versioning
  4. Enable versioning aktivieren
  5. Empfohlene Lifecycle-Regeln aktivieren:
  6. ☑️ Checkbox "Empfohlene Lebenszyklusregeln zum Verwalten der Versionskosten hinzufügen"
  7. Max. Anzahl von Versionen pro Objekt: 3
  8. Nicht aktuelle Versionen löschen nach: 30 Tage

Via gsutil CLI:

# Versioning aktivieren
gsutil versioning set on gs://${PROJECT_ID}.firebasestorage.app

# Status prüfen
gsutil versioning get gs://${PROJECT_ID}.firebasestorage.app
# Erwartete Ausgabe: Enabled

# Lifecycle-Regel für noncurrent Versions setzen
cat > /tmp/storage-lifecycle.json <<'JSON'
{
  "lifecycle": {
    "rule": [
      {
        "action": { "type": "Delete" },
        "condition": {
          "numNewerVersions": 3
        }
      },
      {
        "action": { "type": "Delete" },
        "condition": {
          "daysSinceNoncurrent": 30,
          "isLive": false
        }
      }
    ]
  }
}
JSON
gsutil lifecycle set /tmp/storage-lifecycle.json gs://${PROJECT_ID}.firebasestorage.app

Wiederherstellung einer älteren Version

# Alle Versionen eines Objekts auflisten
gsutil ls -a gs://${PROJECT_ID}.firebasestorage.app/path/to/file.pdf

# Spezifische Version wiederherstellen
gsutil cp gs://${PROJECT_ID}.firebasestorage.app/path/to/file.pdf#<generation> \
  gs://${PROJECT_ID}.firebasestorage.app/path/to/file.pdf

Kosten

  • Noncurrent Versions werden nach 7 Tagen automatisch in Nearline/Coldline Storage verschoben (günstigere Klasse)
  • Nach 30 Tagen oder bei > 3 Versionen: automatische Löschung
  • Geschätzte Mehrkosten: < 5 €/Monat bei typischer Nutzung

1.4 Backup Health Monitoring

Neu seit Mai 2026: Automatische tägliche Prüfung aller Backup-Mechanismen.

  • Job: backupHealthCheck (System-Job, Cloud Scheduler)
  • Schedule: 0 4 * * * UTC (täglich 04:00 Uhr, 2h nach fullBackup)
  • Status-Dokument: _system/backup_health_state (Firestore)

Geprüfte Komponenten

Komponente Status Grün Status Gelb Status Rot
fullBackup Job Letzter Lauf < 26h + success Letzter Lauf 26–50h > 50h oder failed
Firestore PITR Aktiviert API-Fehler Nicht aktiviert
GCS Object Versioning Aktiviert Nicht aktiviert n/a

Aggregierter Status: - 🔴 Rot: Mindestens ein Check = red - 🟡 Gelb: Kein red, mindestens ein yellow - 🟢 Grün: Alle checks = green

Backup-Ampel im ERP-Dashboard

SuperAdmins sehen im Dashboard eine Backup-Status-Ampel neben der System-Health-Ampel:

  • Grün: Alle Backup-Mechanismen funktionieren
  • Gelb: GCS Versioning nicht aktiviert (nicht kritisch, aber empfohlen)
  • Rot: fullBackup läuft nicht oder PITR deaktiviert (kritisch!)
  • Grau (Unbekannt): Job noch nie gelaufen (nur beim ersten Setup)

Tap auf Ampel: öffnet Detail-Modal mit Status aller drei Checks.

Cloud Scheduler Job registrieren

Analog zu fullBackup:

cd core/functions

node -e "
process.env.GCLOUD_PROJECT = '${PROJECT_ID}';
const schedulerUtils = require('./src/utils/scheduler_utils');

async function main() {
  await schedulerUtils.createOrUpdateSchedulerJob(
    'backupHealthCheck',
    { type: 'cron', cronExpression: '0 4 * * *' },
    'system'
  );
  console.log('✅ backupHealthCheck Scheduler Job angelegt');
}
main().catch(e => console.error(e)).finally(() => process.exit(0));
"

Firestore Rules

// _system/backup_health_state: nur SuperAdmins können lesen
match /_system/backup_health_state {
  allow read: if isSuperAdmin();
  allow write: if false; // Nur Cloud Functions via Admin SDK
}

2. Cloud Monitoring – Alert-Policies

Alle Alerts: Notification-Channel security-alerts@<kunde> (E-Mail) + optional PagerDuty/Slack. In der GCP-Console unter Monitoring → Alerting → Create Policy.

2.1 Function Error Rate > 5 %

  • Resource type: cloud_run_revision (Functions v2 laufen auf Cloud Run)
  • Metric: run.googleapis.com/request_count mit Filter response_code_class != "2xx"
  • Aggregation: aligner=ALIGN_RATE, reducer=REDUCE_SUM per Service
  • Condition: Verhältnis (5xx + 4xx ohne 401/403) zu Gesamt > 0.05 für 5 min
  • Auto-close: 30 min

2.2 Auth-Failure-Spike > 50 / min

  • Resource: Logs-based metric auf _rateLimits-Schreibzugriffe oder Cloud Logging Filter: resource.type="cloud_function" AND jsonPayload.event="auth_failure"
  • Threshold: count > 50 in 1 min
  • Hinweis: Quelle ist AuditLogger.logFailure({ action: "auth_*" }).

2.3 RateLimit-Sättigung

  • Logs-based metric: Filter jsonPayload.event="rate_limit_exceeded" (von rate_limiter.js).
  • Alert: > 100 Hits / 5 min für eine Action → Hinweis auf Bot/Brute-Force.

2.4 AuditLog – CRITICAL Events

  • Logs-based metric: Firestore-Collection auditLogs, Field severity == "CRITICAL". Praktischer ist eine Logs-based Metric direkt aus den Function-Logs (secureLogger.error schreibt strukturiert).
  • Alert: count > 0 im 5-min-Fenster → sofortige Mail.

2.5 SMTP-Send-Failures

  • Filter: resource.type="cloud_function" AND jsonPayload.fn IN ("sendCustomerInvitationEmail","sendPriceListEmail") AND severity>=ERROR
  • Threshold: > 5 Fehlschläge / 10 min
  • Hinweis: typische Ursachen: Strato-Quota, falsches Secret, App-Passwort abgelaufen.

3. Operational Logging-Conventions

  • secureLogger.error/warn/info schreibt strukturierte Felder; PII wird via secureLogger-Redactor entfernt.
  • Custom-Felder, die in Alerts genutzt werden:
  • event – z. B. rate_limit_exceeded, auth_failure
  • fn – Funktionsname (für SMTP-Filter)
  • connectorId, customerId – Routing in Dashboards
  • Niemals raw Tokens/Passwörter loggen. Code-Review wird durch ESLint-Regel no-pii-logging automatisiert.

4. Pre-Go-Live-Check

Check Verantwortlich
Bucket + Lifecycle in jedem Tenant-Projekt vorhanden DevOps
scheduledFirestoreBackup deployed & 1× manuell getriggert DevOps
5 Monitoring-Alerts aktiv mit Notification-Channel DevOps
Restore-Probe in Staging dokumentiert DevOps
system_settings/backup_settings.enabled=true (oder Default) Operations