18 - Kibana Betriebsmonitoring Setup

Schritt-fuer-Schritt Anleitung zur Konfiguration des Betriebsmonitoring-Dashboards

Version 1.0 | Stand: 2026-02-27 | Klassifikation: INTERN

Inhaltsverzeichnis

1. Ueberblick

Das Betriebsmonitoring-Dashboard bietet eine CheckMK-aehnliche Uebersicht ueber:

Architektur: Logs fliessen von den Quellsystemen ueber Cribl Stream/Edge in den Elasticsearch-Index cribl-ops. Kibana visualisiert diese Daten.

Dashboards:

DashboardIDBeschreibung
Navigationnavigation-menuHauptmenue mit Links
Betriebsmonitoringops-monitoring-v1System- & Dienste-Uebersicht (25 Panels)
Docker Log Classificationicedataemphasise-docker-log-dashboardLog-Klassifikation

2. Voraussetzungen

KomponenteVersionContainerPort
Elasticsearch8.12.2cribl-elasticsearch9200
Kibana8.12.2cribl-kibana5601
Cribl Stream-via Docker Compose-
MinIO-cribl-minio-

Alle Container laufen im selben Docker-Netzwerk und kommunizieren intern.

3. Datenstruktur im Index

Der Index cribl-ops enthaelt zwei Hauptdatenquellen:

3.1 Sophos XG Firewall Logs (~8.9 Mio. Dokumente)

Empfangen via Syslog, verarbeitet durch die Cribl Pipeline pipeline_sophos_fw.

FeldTypBeschreibungBeispielwert
log_typekeywordHauptkategorieFirewall, System Health, Event, Content Filtering
log_componentkeywordUnterkategorieFirewall Rule, CPU, Memory, Disk, Interface, DHCP Server
log_subtypekeywordAktionAllowed, Denied, Usage
actionkeywordNormalisierte AktionAllowed, Denied, Drop
src / destkeywordQuell-/Ziel-IP (normalisiert)10.10.0.111, 192.168.178.1
protocolkeywordNetzwerkprotokollTCP, UDP, ICMP
fw_rule_namekeywordName der FW-RegelGeneral out from all LAN
device_namekeywordGeraetenameSchwalbe.paulis.net
source_typekeywordCribl Source Typesophos_firewall

3.2 System Health Felder (Sophos)

KomponenteFelder (Originaltyp: text)Beispiel
CPUsystem, user, idle2.33%, 4.14%, 93.52%
Memorytotal_memory, free, used, unit16779149312, 11381645312, byte
DiskConfiguration, Reports, Signature, Temp9.00%, 18.00%, 6.00%
Interfaceinterface, receivedkbits, transmittedkbitsPort2, 562.49, 137.11
Wichtig: Die System-Health-Werte werden von Sophos als Text geliefert (z.B. "93.52%"). Fuer Visualisierungen muessen diese via Runtime Fields in numerische Werte konvertiert werden.

3.3 NUC-HA Docker/Journald Logs (~2.3 Mio. Dokumente)

Empfangen via Cribl Edge (Journald-Quelle), klassifiziert durch ollama_classifier Pipeline.

FeldTypBeschreibung
CONTAINER_NAMEkeywordDocker Container Name
IMAGE_NAMEkeywordDocker Image
appnamekeywordApplikationsname (journald)
classificationkeywordsiem / operational
classification_methodkeywordrule_based / ollama
severityNamekeywordinfo, notice, error, warning
host.namekeywordHostname (NUC-HA fuer Docker-Logs)

3.4 Gemeinsame Felder

FeldBeschreibung
@timestampZeitstempel (date) - Grundlage aller Zeitreihen
severitySyslog Severity (numerisch: 0-7)
severityNameSeverity als Text: info, notice, warning, error
cribl_pipeArray der durchlaufenen Cribl Pipelines
messageVolltextmeldung
_rawOriginal-Rohdaten

4. Runtime Fields anlegen

Da Sophos System-Health-Werte als Text gespeichert werden, muessen Runtime Fields angelegt werden, die zur Abfragezeit die Werte in numerische Typen konvertieren.

1Runtime Fields via Elasticsearch API anlegen

Folgender API-Call fuegt alle benoetigten Runtime Fields zum Index cribl-ops hinzu:

curl -X PUT "http://10.10.0.100:9200/cribl-ops/_mapping" \
  -H 'Content-Type: application/json' \
  -d '{
  "runtime": {
    "cpu_idle_pct": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"idle.keyword\") && doc[\"idle.keyword\"].size() > 0) { String v = doc[\"idle.keyword\"].value; if (v.endsWith(\"%\")) { v = v.substring(0, v.length()-1); } try { emit(Double.parseDouble(v)); } catch(Exception e) {} }"
      }
    },
    "cpu_user_pct": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"user.keyword\") && doc[\"user.keyword\"].size() > 0) { String v = doc[\"user.keyword\"].value; if (v.endsWith(\"%\")) { v = v.substring(0, v.length()-1); } try { emit(Double.parseDouble(v)); } catch(Exception e) {} }"
      }
    },
    "cpu_system_pct": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"system.keyword\") && doc[\"system.keyword\"].size() > 0) { String v = doc[\"system.keyword\"].value; if (v.endsWith(\"%\")) { v = v.substring(0, v.length()-1); } try { emit(Double.parseDouble(v)); } catch(Exception e) {} }"
      }
    },
    "mem_total_bytes": {
      "type": "long",
      "script": {
        "source": "if (doc.containsKey(\"total_memory.keyword\") && doc[\"total_memory.keyword\"].size() > 0) { try { emit(Long.parseLong(doc[\"total_memory.keyword\"].value)); } catch(Exception e) {} }"
      }
    },
    "mem_used_bytes": {
      "type": "long",
      "script": {
        "source": "if (doc.containsKey(\"used.keyword\") && doc[\"used.keyword\"].size() > 0) { try { emit(Long.parseLong(doc[\"used.keyword\"].value)); } catch(Exception e) {} }"
      }
    },
    "mem_free_bytes": {
      "type": "long",
      "script": {
        "source": "if (doc.containsKey(\"free.keyword\") && doc[\"free.keyword\"].size() > 0) { try { emit(Long.parseLong(doc[\"free.keyword\"].value)); } catch(Exception e) {} }"
      }
    },
    "net_rx_kbits": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"receivedkbits.keyword\") && doc[\"receivedkbits.keyword\"].size() > 0) { try { emit(Double.parseDouble(doc[\"receivedkbits.keyword\"].value)); } catch(Exception e) {} }"
      }
    },
    "net_tx_kbits": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"transmittedkbits.keyword\") && doc[\"transmittedkbits.keyword\"].size() > 0) { try { emit(Double.parseDouble(doc[\"transmittedkbits.keyword\"].value)); } catch(Exception e) {} }"
      }
    },
    "disk_config_pct": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"Configuration.keyword\") && doc[\"Configuration.keyword\"].size() > 0) { String v = doc[\"Configuration.keyword\"].value; if (v.endsWith(\"%\")) { v = v.substring(0, v.length()-1); } try { emit(Double.parseDouble(v)); } catch(Exception e) {} }"
      }
    },
    "disk_reports_pct": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"Reports.keyword\") && doc[\"Reports.keyword\"].size() > 0) { String v = doc[\"Reports.keyword\"].value; if (v.endsWith(\"%\")) { v = v.substring(0, v.length()-1); } try { emit(Double.parseDouble(v)); } catch(Exception e) {} }"
      }
    }
  }
}'

Die Antwort sollte {"acknowledged": true} sein.

Wie Runtime Fields funktionieren: Im Gegensatz zu gespeicherten Feldern werden Runtime Fields zur Abfragezeit berechnet. Das bedeutet: kein Reindexing noetig, aber etwas mehr CPU-Last bei Abfragen. Fuer die wenigen System-Health-Dokumente (~18.000) ist das vernachlaessigbar.

Uebersicht der Runtime Fields

Runtime FieldTypQuellfeldBeschreibung
cpu_user_pctdoubleuserCPU User % (ohne %-Zeichen)
cpu_system_pctdoublesystemCPU System %
cpu_idle_pctdoubleidleCPU Idle %
mem_total_byteslongtotal_memoryGesamter RAM in Bytes
mem_used_byteslongusedBelegter RAM in Bytes
mem_free_byteslongfreeFreier RAM in Bytes
net_rx_kbitsdoublereceivedkbitsEmpfangene kbit/s
net_tx_kbitsdoubletransmittedkbitsGesendete kbit/s
disk_config_pctdoubleConfigurationDisk Config %
disk_reports_pctdoubleReportsDisk Reports %

5. Index Pattern konfigurieren

2Index Pattern pruefen/erstellen

Das Index Pattern cribl-ops* sollte bereits existieren (ID: 99f5381a-3d59-4cec-8ce4-77ee8a90595d).

Pruefen via API:

curl -s 'http://10.10.0.100:5601/api/saved_objects/_find?type=index-pattern' \
  -H 'kbn-xsrf: true' | python3 -m json.tool

Falls nicht vorhanden, erstellen:

curl -X POST "http://10.10.0.100:5601/api/saved_objects/index-pattern/99f5381a-3d59-4cec-8ce4-77ee8a90595d" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{
  "attributes": {
    "title": "cribl-ops*",
    "timeFieldName": "@timestamp",
    "name": "Cribl Operations Logs"
  }
}'

3Index Pattern aktualisieren (Felder refreshen)

Nach Anlage der Runtime Fields muessen die Felder im Index Pattern aktualisiert werden:

  1. Kibana oeffnen: Stack Management → Data Views
  2. Auf Cribl Operations Logs klicken
  3. Oben rechts auf das Refresh-Symbol klicken (Felder neu laden)
  4. Die neuen Runtime Fields (cpu_user_pct, etc.) sollten nun in der Feldliste erscheinen

6. Dashboard-Aufbau

Das Dashboard besteht aus 25 Panels in 5 Sektionen. Jeder Panel-Typ wird im Folgenden erklaert, damit eigene Panels erstellt oder bestehende angepasst werden koennen.

6.1 KPI-Metrik-Panels

Grosse Zahlenwerte als Uebersicht (z.B. "Events Gesamt", "Errors", "FW Denied").

Manuell in Kibana erstellen:

  1. Dashboard oeffnen → EditCreate visualization
  2. Visualization Type: Metric
  3. Metric: Count (Aggregation ueber alle Dokumente)
  4. Optional: Filter hinzufuegen (z.B. severityName.keyword: error fuer Error-Zaehler)
  5. Farbe anpassen: Panel Settings → Color

API-Struktur (Lens Metric):

{
  "visualizationType": "lnsMetric",
  "state": {
    "datasourceStates": {
      "formBased": {
        "layers": {
          "l1": {
            "columns": {
              "c1": {
                "label": "Errors",          // Angezeigter Name
                "operationType": "count",    // Aggregation: count, sum, avg, min, max
                "sourceField": "___records___"
              }
            }
          }
        }
      }
    },
    "visualization": {
      "metricAccessor": "c1",
      "color": "#E7664C"                    // Farbe als Hex
    },
    "filters": [{                           // Optional: Vorfilterung
      "query": { "match_phrase": { "severityName.keyword": "error" }}
    }]
  }
}

6.2 Zeitreihen-Charts (XY)

Fuer CPU, Memory, Netzwerk-Durchsatz und andere zeitbasierte Metriken.

Manuell erstellen:

  1. Create visualization → Typ: Line oder Area
  2. X-Achse: @timestamp (Date Histogram, Auto-Interval)
  3. Y-Achse: Average von cpu_user_pct (Runtime Field)
  4. Weitere Metriken als zusaetzliche Y-Achsen-Werte hinzufuegen
  5. Filter setzen: log_component.keyword: CPU
  6. Y-Achse begrenzen: 0-100 fuer Prozent-Werte

Wichtige Einstellungen:

EinstellungWertZweck
seriesTypearea_stacked / line / bar_stackedDiagrammtyp
fittingFunctionLinear / NoneLuecken interpolieren
yLeftExtent{lowerBound: 0, upperBound: 100}Y-Achse fixieren
splitAccessorFeld-IDNach Kategorie aufteilen (z.B. Severity)
colorMappingAssignmentsFeste Farben pro Wert zuweisen

Beispiel: CPU-Chart mit fixierten Farben

// colorMapping-Beispiel: Feste Farben pro Serie
"colorMapping": {
  "assignments": [
    {
      "rule": {"type": "matchExactly", "values": ["CPU User %"]},
      "color": {"type": "colorCode", "colorCode": "#6092C0"}
    },
    {
      "rule": {"type": "matchExactly", "values": ["CPU System %"]},
      "color": {"type": "colorCode", "colorCode": "#E7664C"}
    }
  ],
  "specialAssignments": [
    {"rule": {"type": "other"}, "color": {"type": "loop"}}
  ]
}

6.3 Balkendiagramme (Top-N)

Horizontale Balken fuer "Top Denied Sources", "Container Errors", "Top Hosts".

Manuell erstellen:

  1. Create visualization → Bar horizontal
  2. X-Achse: Terms-Aggregation auf src.keyword (Top 15)
  3. Y-Achse: Count
  4. Filter: action.keyword: Denied
  5. Value Labels: "Show" fuer bessere Lesbarkeit

API-Schluesselfelder:

// Terms-Spalte fuer Top-N
{
  "label": "Source IP",
  "operationType": "terms",
  "sourceField": "src.keyword",
  "params": {
    "size": 15,              // Anzahl Top-Eintraege
    "orderDirection": "desc", // Absteigend sortiert
    "exclude": ["-"],         // Werte ausschliessen
    "include": []             // Oder nur bestimmte einschliessen
  }
}

6.4 Donut/Pie-Charts

Fuer Verteilungen (Protokolle, Log-Komponenten).

Manuell erstellen:

  1. Create visualization → Donut
  2. Slice By: Terms auf protocol.keyword
  3. Size: Count
  4. "Other" Bucket aktivieren fuer Zusammenfassung kleiner Werte

API:

"visualizationType": "lnsPie",
"visualization": {
  "shape": "donut",    // oder "pie" fuer Vollkreis
  "layers": [{
    "primaryGroups": ["proto"],  // Slice-Feld
    "metrics": ["cnt"],          // Groesse
    "numberDisplay": "percent"   // Prozent anzeigen
  }]
}

6.5 Heatmap

Ideal fuer Container × Severity Matrix.

Manuell erstellen:

  1. Create visualization → Heatmap
  2. X-Achse: Terms auf severityName.keyword
  3. Y-Achse: Terms auf CONTAINER_NAME.keyword
  4. Value: Count
  5. Palette: "Status" fuer intuitive Rot/Gelb/Gruen-Faerbung

API:

"visualizationType": "lnsHeatmap",
"visualization": {
  "xAccessor": "sev",       // Severity (X)
  "yAccessor": "cname",     // Container (Y)
  "valueAccessor": "cnt",   // Wert (Farbe)
  "palette": {"type": "palette", "name": "status"},
  "gridConfig": {"isCellLabelVisible": true}
}

6.6 Datentabellen

Fuer detaillierte Auflistungen (Event-Typen, Firewall Rules).

Manuell erstellen:

  1. Create visualization → Table
  2. Rows: Terms auf log_type.keyword, dann log_component.keyword
  3. Metrics: Count
  4. Paging aktivieren (10 Zeilen pro Seite)

API:

"visualizationType": "lnsDatatable",
"visualization": {
  "columns": [
    {"columnId": "lt", "isTransposed": false},
    {"columnId": "cnt", "isTransposed": false}
  ],
  "paging": {"size": 10, "enabled": true}
}

7. Dashboard via API erstellen

Dashboards koennen ueber die Kibana Saved Objects API erstellt werden:

4Dashboard erstellen via POST

curl -X POST "http://10.10.0.100:5601/api/saved_objects/dashboard/DASHBOARD-ID" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{
  "attributes": {
    "title": "Mein Dashboard",
    "description": "Beschreibung",
    "timeRestore": true,
    "timeTo": "now",
    "timeFrom": "now-24h",
    "refreshInterval": {"pause": false, "value": 60000},
    "kibanaSavedObjectMeta": {
      "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
    },
    "panelsJSON": "[...]",     // JSON-String mit Panel-Array
    "optionsJSON": "{\"useMargins\":true,\"syncColors\":true}",
    "version": 1
  }
}'

5Bestehendes Dashboard exportieren (als Vorlage)

# Export eines bestehenden Dashboards
curl -s "http://10.10.0.100:5601/api/saved_objects/dashboard/ops-monitoring-v1" \
  -H "kbn-xsrf: true" | python3 -m json.tool > dashboard-export.json

# PanelsJSON extrahieren und formatiert anzeigen
cat dashboard-export.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
panels = json.loads(data['attributes']['panelsJSON'])
print(json.dumps(panels, indent=2))
" > panels-formatted.json

6Dashboard aktualisieren

Zum Aktualisieren eines bestehenden Dashboards DELETE + POST verwenden:

# Loeschen
curl -X DELETE "http://10.10.0.100:5601/api/saved_objects/dashboard/ops-monitoring-v1" \
  -H "kbn-xsrf: true"

# Neu erstellen mit geaenderten Panels
curl -X POST "http://10.10.0.100:5601/api/saved_objects/dashboard/ops-monitoring-v1" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d @updated-dashboard.json
Tipp: Am einfachsten ist es, das Dashboard in Kibana manuell zu bearbeiten (Edit-Modus), dann per API zu exportieren und die JSON-Struktur als Vorlage fuer weitere Dashboards zu nutzen.

Ein spezielles Dashboard dient als Hauptmenue mit Links zu allen anderen Dashboards:

Markdown-Format fuer Dashboard-Links:

| Dashboard | Beschreibung | Link |
|-----------|-------------|------|
| **Betriebsmonitoring** | System Health & Dienste | [Oeffnen](http://10.10.0.100:5601/app/dashboards#/view/ops-monitoring-v1) |
| **Docker Logs** | Log-Klassifikation | [Oeffnen](http://10.10.0.100:5601/app/dashboards#/view/icedataemphasise-docker-log-dashboard) |

9. Eigene Anpassungen vornehmen

9.1 Neues Panel hinzufuegen

  1. Dashboard oeffnen → Edit
  2. Create visualization klicken
  3. Datenquelle: Cribl Operations Logs waehlen
  4. Felder aus der linken Sidebar per Drag&Drop ziehen
  5. Save and return

9.2 Filter anpassen

Jedes Panel kann eigene Filter haben. In der API-Struktur unter state.filters:

// Einfacher Term-Filter
{"query": {"match_phrase": {"log_component.keyword": "CPU"}}}

// Exists-Filter (Feld muss vorhanden sein)
{"query": {"exists": {"field": "CONTAINER_NAME"}}}

// Mehrere Werte (should/or)
{"query": {"bool": {"should": [
  {"match_phrase": {"action.keyword": "Denied"}},
  {"match_phrase": {"action.keyword": "Drop"}}
]}}}

9.3 Neues Runtime Field hinzufuegen

Fuer weitere numerische Felder, die als Text gespeichert sind:

curl -X PUT "http://10.10.0.100:9200/cribl-ops/_mapping" \
  -H 'Content-Type: application/json' \
  -d '{
  "runtime": {
    "mein_neues_feld": {
      "type": "double",
      "script": {
        "source": "if (doc.containsKey(\"quellfeld.keyword\") && doc[\"quellfeld.keyword\"].size() > 0) { try { emit(Double.parseDouble(doc[\"quellfeld.keyword\"].value)); } catch(Exception e) {} }"
      }
    }
  }
}'

Danach in Kibana unter Data Views die Felder refreshen.

9.4 Farbpalette aendern

Die Farbzuweisungen in colorMapping verwenden Hex-Codes:

FarbeCodeVerwendung
Rot#E7664CError, Denied, kritisch
Gelb#D6BF57Warning
Gruen#54B399OK, Allowed, Info
Blau#6092C0Neutral, Notice
Pink#D36086Drop, kritisch
Gold#B9A888SIEM, speziell

10. Feld-Referenz

Haeufig verwendete Filter-Kombinationen

ZweckFilter
Nur Firewall-Logslog_type.keyword: Firewall
Nur System Healthlog_type.keyword: System Health
Nur CPU-Datenlog_component.keyword: CPU
Nur Memory-Datenlog_component.keyword: Memory
Nur Disk-Datenlog_component.keyword: Disk
Nur Interface-Datenlog_component.keyword: Interface
Nur Denied Trafficaction.keyword: Denied
Nur Docker-Container-LogsCONTAINER_NAME exists
Nur ErrorsseverityName.keyword: error
Nur SIEM-klassifizierte Logsclassification.keyword: siem
Bestimmter ContainerCONTAINER_NAME.keyword: homeassistant

Verfuegbare Container im Index

ContainerKategorieDocs (ca.)
homeassistantSmart Home2.1M
oa-tikaOpen Archiver153K
oa-postgresOpen Archiver DB13K
tax-ai-stack-paperless-1Steuer-Pipeline3.5K
hassio_supervisorHA Supervisor2.9K
tax-ai-stack-db-1Steuer-Pipeline DB1.2K
addon_a0d7b954_noderedNode-RED387
cribl-kibanaMonitoring Stack129
addon_core_matter_serverMatter/Thread107
addon_core_mosquittoMQTT Broker56