Zum Hauptinhalt springen

2-Node-Proxmox-Cluster mit QDevice: Firewall-HA ohne Shared Storage

Wim Bonis
Proxmox Virtualisierung ZFS HA Open Source
Autor
Stylite AG
Spezialisten in ZFS storage solutions, security. Docker containerization for enterprise environments.
Inhaltsverzeichnis

Ausgangslage
#

Zwei Proxmox-Nodes, eine Firewall-VM, kein Shared Storage. Die Anforderung: bei Ausfall eines Nodes soll die Firewall-VM automatisch auf dem anderen Node starten. Sekunden-genaue Synchronisation ist nicht nötig – der Datenzustand darf etwas älter sein.

Zwei gestapelte Protectli Mini-PC Nodes, schuhschachtelgroß, Labels 1 und 2
Die eingesetzte Hardware: zwei schuhschachtelgroße Protectli-Nodes, gestapelt. Klick für Vollansicht.

Dafür wurde ein Proxmox-Cluster mit QDevice als drittem Quorum-Vote und ZFS-Replikation im 5-Minuten-Takt aufgebaut. Zusätzlich läuft optional zrepl als zweiter Replikationspfad.

Architektur
#

  • Node 1 + Node 2: identisch konfiguriert, jeweils lokales ZFS (Mirror)
  • QDevice: läuft z.B. auf einem Raspberry Pi (nur Quorum, keine VM-Daten)
  • VM-Replikation: per Proxmox intern (pvesr) alle 5 Minuten
  • Optional: zusätzlicher ZFS-Replica-Stream via zrepl

Das ist kein synchrones Storage-Cluster. Im Worst Case gehen bis zu 5 Minuten an Änderungen verloren (Recovery Point Objective, kurz RPO). Für eine Firewall, deren Regelwerk sich nicht minütlich ändert, reicht das.

Voraussetzungen
#

  • Beide Proxmox-Nodes erreichbar, Uhrzeit/NTP synchron
  • Gleiche Netzsegmente für Cluster/Management/Replication (produktiv: Corosync und Replikation auf getrennten NICs – Corosync ist latenz-empfindlich, Replikation frisst Bandbreite)
  • ZFS auf beiden Nodes eingerichtet (genug Platz für replizierte VM-Disks)
  • QDevice-Host stabil erreichbar (z.B. Raspberry Pi an drittem Standort)
  • Firewall-VM läuft zunächst primär auf Node 1 (Node 2 ist Replikationsziel)
  • E-Mail-Alerts auf den Nodes konfiguriert – sonst bleiben Replikations- und HA-Fehler unbemerkt (alternativ externes Monitoring auf pvecm status, pvesr status, zpool status)

Proxmox-Cluster bilden
#

Auf Node 1 den Cluster erstellen, Node 2 tritt bei:

# Node 1: Cluster erstellen
pvecm create mein-cluster

# Node 2: Cluster beitreten
pvecm add 10.0.0.11

# Quorum prüfen
pvecm status

QDevice anbinden
#

Ein klassisches 2-Node-Cluster hat ein Quorum-Problem: fällt ein Node aus, fehlt die Mehrheit. Das QDevice löst das als dritter Vote.

Die Einrichtung des QNetd-Dienstes auf dem externen Host (z.B. einem Raspberry Pi) ist gut dokumentiert – eine kompakte Anleitung findet sich etwa im Proxmox-Wiki unter QDevice .

Auf den Proxmox-Nodes:

# Auf beiden Proxmox-Nodes
apt install corosync-qdevice

# QDevice vom Cluster aus einrichten (einmalig, z.B. von Node 1)
pvecm qdevice setup 10.0.0.120

Quorum prüfen
#

pvecm status

Erwartete Ausgabe im Normalbetrieb:

Cluster information
-------------------
Name:             prox
Config Version:   5
Transport:        knet
Secure auth:      on

Quorum information
------------------
Date:             Tue Apr 14 13:11:42 2026
Quorum provider:  corosync_votequorum
Nodes:            2
Node ID:          0x00000002
Ring ID:          1.123
Quorate:          Yes

Votequorum information
----------------------
Expected votes:   3
Highest expected: 3
Total votes:      3
Quorum:           2
Flags:            Quorate Qdevice

Membership information
----------------------
    Nodeid      Votes    Qdevice Name
0x00000001          1    A,V,NMW 10.0.0.11
0x00000002          1    A,V,NMW 10.0.0.12 (local)
0x00000000          1            Qdevice

So liest man die wichtigsten Zeilen
#

  • Nodes: 2 – zwei echte Proxmox-Knoten im Cluster
  • Quorate: Yes – der Cluster hat aktuell Quorum und darf normal arbeiten
  • Expected votes: 3 – drei Votes eingeplant (Node 1, Node 2, QDevice)
  • Total votes: 3 – alle drei Votes sind aktuell verfügbar
  • Quorum: 2 – mindestens zwei Votes nötig, damit der Cluster aktiv bleibt
  • Flags: Quorate Qdevice – Quorum ist aktiv, QDevice korrekt eingebunden

ZFS-Storage auf beiden Nodes
#

Lokalen ZFS-Pool auf beiden Nodes anlegen – identischer Aufbau. Wichtig: Pool-Name und Dataset-Struktur müssen auf beiden Nodes exakt gleich sein. Heißt der Pool auf Node 1 pool1, muss er auch auf Node 2 pool1 heißen – sonst findet pvesr das Replikationsziel nicht und bricht mit “storage not available on target node” ab.

Optional pro Node als Mirror spiegeln, damit ein einzelner Datenträger-Ausfall den Node nicht direkt stoppt:

# Lokalen Mirror-Pool anlegen
zpool create pool1 mirror /dev/sda1 /dev/sdb1

# Status prüfen
zpool status
ZFS Pool Status: pool1 als Mirror mit zwei Disks, Health ONLINE, keine Fehler

Der Pool zeigt Health ONLINE mit 0 Errors auf beiden Mirror-Disks.

In Proxmox den Pool als Storage registrieren:

Proxmox Storage-Konfiguration: ZFS-Pool pool1-data1 für Disk Images und Container

Das ist ein lokaler Schutz pro Node (Disk-Redundanz) und ersetzt nicht die Replikation zwischen Node 1 und Node 2.

Replikationsjob anlegen
#

In Proxmox für die Firewall-VM Replikation von Node 1 → Node 2 aktivieren. Intervall: alle 5 Minuten.

Proxmox Replikationsjobs: drei VMs mit 5-Minuten-Schedule auf verschiedene Nodes

Status der Replikation prüfen:

pvesr list
pvesr status

Gesunde Ausgabe:

JobID      Enabled    Target              LastSync             NextSync   Duration  FailCount State
101-0      Yes        local/prox2      2026-04-14_13:50:03  2026-04-14_13:56:00   5.383416          0 OK
103-0      Yes        local/prox2      2026-04-14_13:50:08  2026-04-14_13:55:00   1.910102          0 OK
111-0      Yes        local/prox2      2026-04-14_13:50:01  2026-04-14_13:54:00   2.099421          0 OK

Alle Jobs OK, FailCount 0, letzte Sync wenige Minuten her.

Wichtig zum Verständnis: Die VM-Konfiguration (/etc/pve/qemu-server/101.conf) liegt auf dem Proxmox-Cluster-FS und ist ohnehin auf allen Nodes identisch vorhanden. Repliziert werden nur die VM-Disks. Nach einem Failover startet Node 1 die VM mit aktueller Config, aber Disk-Stand der letzten Replikation.

HA-Verhalten definieren
#

Watchdog / Self-Fencing
#

Proxmox HA braucht einen Watchdog. Verliert ein Node das Quorum, rebootet er sich nach etwa 60 Sekunden über den Watchdog selbst – so kann die VM sicher auf dem anderen Node starten, ohne dass sie doppelt läuft (Split-Brain).

Default ist softdog (Kernel-Software-Watchdog). Ein Hardware-Watchdog (iTCO, IPMI, iLO) ist zuverlässiger, weil er unabhängig vom Kernel auslöst – wer kann, sollte den aktivieren.

VM als HA-Ressource
#

Die VM wird als HA-Ressource definiert. Bei Node-Ausfall startet Proxmox sie auf dem verbleibenden Node:

Proxmox HA Manager: VM-Ressource mit Migrations- und Prioritätseinstellungen

Im Edit-Dialog werden die wichtigsten Parameter gesetzt – Request State started, Max. Restart und Max. Relocate, sowie Failback:

Proxmox HA-Ressource bearbeiten: VM 101, Max. Restart und Max. Relocate jeweils 1, Request State started, Failback aktiv

Der Datenstand entspricht der letzten erfolgreichen Replikation. Der Hinweis “At least three quorum votes are recommended for reliable HA” bestätigt noch einmal, warum das QDevice im 2-Node-Setup Pflicht ist.

Failover in der Praxis
#

Node-Ausfall simulieren
#

Nach dem Ausfall von Node 2 zeigt pvecm status:

Quorum information
------------------
Date:             Tue Apr 14 13:37:04 2026
Quorum provider:  corosync_votequorum
Nodes:            1
Node ID:          0x00000001
Ring ID:          1.127
Quorate:          Yes

Votequorum information
----------------------
Expected votes:   3
Highest expected: 3
Total votes:      2
Quorum:           2
Flags:            Quorate Qdevice

Membership information
----------------------
    Nodeid      Votes    Qdevice Name
0x00000001          1    A,V,NMW 10.21.7.11 (local)
0x00000000          1            Qdevice

Quorate: Yes – obwohl nur noch 1 Node da ist. Node 1 + QDevice ergeben 2 von 3 Votes, das reicht. Ohne QDevice wäre der Cluster blockiert.

Die Replikation schlägt fehl – logisch, Node 2 ist ja weg:

JobID      Enabled    Target              LastSync             NextSync   Duration  FailCount State
101-0      Yes        local/prox2             -                    -    4.48416          1 ...exit code 255
103-0      Yes        local/prox2      2026-04-14_13:35:01         -   1.308893          1 ...exit code 255
111-0      Yes        local/prox2             -                    -   3.039043          1 ...exit code 255

Sobald der Zielnode wieder da ist, laufen die Jobs von allein an.

Recovery
#

Node 2 ist wieder online. Der Cluster zeigt sofort alle 3 Votes:

Votequorum information
----------------------
Expected votes:   3
Highest expected: 3
Total votes:      3
Quorum:           2
Flags:            Quorate Qdevice

Membership information
----------------------
    Nodeid      Votes    Qdevice Name
0x00000001          1    A,V,NMW 10.21.7.11 (local)
0x00000002          1    A,V,NMW 10.21.7.12
0x00000000          1            Qdevice

Alle 3 Votes zurück, Replikation wieder OK, FailCount 0. Kein manueller Eingriff nötig.

Optional: Zusätzliche Kopie mit zrepl
#

pvesr funktioniert zuverlässig. Wer trotzdem einen zweiten, unabhängigen Replikationspfad will, kann zrepl dazuschalten – ein eigenständiger ZFS-Snapshot-Stream, der nichts mit Proxmox zu tun hat.

Installation
#

zrepl ist nicht in den Proxmox-/Debian-Standard-Repos. Über das inoffizielle APT-Repo von C. Schwarz lässt es sich sauber nachziehen:

apt update
apt install -y curl gnupg lsb-release

curl -fsSL https://zrepl.cschwarz.com/apt/apt-key.asc \
  | gpg --dearmor \
  | tee /usr/share/keyrings/zrepl.gpg >/dev/null

ARCH="$(dpkg --print-architecture)"
CODENAME="$(lsb_release -i -s | tr '[:upper:]' '[:lower:]') $(lsb_release -c -s | tr '[:upper:]' '[:lower:]')"

echo "deb [arch=$ARCH signed-by=/usr/share/keyrings/zrepl.gpg] https://zrepl.cschwarz.com/apt/$CODENAME main" \
  | tee /etc/apt/sources.list.d/zrepl.list

apt update
apt install -y zrepl

Auf beiden Nodes installieren.

Beispielkonfiguration auf Node 2
#

/etc/zrepl/zrepl.yml:

global:
  logging:
    - type: syslog
      format: human
      level: warn

jobs:
  - name: src_to_prox1
    type: source
    serve:
      type: tcp
      listen: ":8888"
      clients:
        "10.0.0.11": "prox1"

    filesystems:
      "pool1/data1<": true

    snapshotting:
      type: periodic
      prefix: zrepl_
      interval: 15m

  - name: pull_from_prox1
    type: pull
    connect:
      type: tcp
      address: "10.0.0.11:8888"

    root_fs: pool1/backups/prox1
    interval: 15m

    recv:
      placeholder:
        encryption: off

    pruning:
      keep_sender:
        - type: regex
          regex: ".*"
      keep_receiver:
        - type: regex
          negate: true
          regex: "^zrepl_.*"
        - type: last_n
          count: 20

Auf Node 1 wird das spiegelbildlich konfiguriert (source/pull-Richtung und Zielpfade entsprechend umdrehen).

Replikationsjob prüfen:

zrepl status
zrepl status Terminal-Ausgabe: aktive Pull- und Push-Jobs mit Snapshot-Fortschritt und Completed-Einträgen

Die interaktive Statusansicht zeigt laufende Pull-/Push-Jobs, aktuelle Snapshot-Schritte und den Fortschritt pro Dataset.

Auto-Repair bei Replikationskonflikten
#

In der Praxis läuft zrepl parallel zu Proxmox – und genau da entsteht ein typisches Problem: Sobald eine VM per Proxmox-Bordmitteln migriert wird (manuelle Migration, HA-Failover, pvesr-Umkonfiguration), ändert Proxmox die ZFS-Datasets und Snapshots der VM-Disk. Damit reißt die Snapshot-Kette, auf der zrepl aufbaut, ab. Folge: zrepl findet keinen gemeinsamen Snapshot mehr und die Replikation muss für das betroffene Dataset komplett von vorne beginnen – inklusive vollem Initial-Sync und frischem Speicherverbrauch auf dem Ziel.

Um das nicht jedes Mal von Hand auflösen zu müssen, gibt es ein kleines Repair-Skript. Es erkennt betroffene Datasets, löst ZFS-Holds, räumt das kaputte Ziel-Dataset auf und triggert den Job erneut.

Beispiel-Cronjob auf Node 2:

4,19,34,49 * * * * /root/zrepl-autorepair.py pull_from_prox1 >/dev/null 2>&1
zrepl-autorepair.py – vollständiges Skript, klicken für Details
#!/usr/bin/env python3
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path

CONFIG = Path("/etc/zrepl/zrepl.yml")

ERROR_PATTERNS = [
    "destination already exists",
    "no common snapshot or suitable bookmark",
    "cannot restore to",
    "validate `to` exists: dataset",
    "validate 'to' exists: dataset",
    "dataset does not exist",
    "cannot resolve conflict",
    "the receiver's latest snapshot is not present on sender",
]

RESTORE_DEST_RE = re.compile(
    r"cannot restore to (\S+@\S+): destination already exists",
    re.IGNORECASE,
)


def run(cmd, check=True, capture=True):
    res = subprocess.run(cmd, text=True, capture_output=capture, check=False)
    if check and res.returncode != 0:
        raise RuntimeError(f"{' '.join(cmd)}: {res.stderr.strip()}")
    return res.stdout.strip(), res.stderr.strip(), res.returncode


def get_root_fs(job: str) -> str:
    found = False
    for line in CONFIG.read_text().splitlines():
        s = line.strip()
        if s == f"- name: {job}":
            found = True
            continue
        if found and s.startswith("root_fs:"):
            return s.split(":", 1)[1].strip()
    raise RuntimeError(f"Could not detect root_fs for job {job}")


def walk(obj):
    if isinstance(obj, dict):
        yield obj
        for v in obj.values():
            yield from walk(v)
    elif isinstance(obj, list):
        for v in obj:
            yield from walk(v)


def collect_strings(obj):
    if isinstance(obj, str):
        yield obj
    elif isinstance(obj, dict):
        for v in obj.values():
            yield from collect_strings(v)
    elif isinstance(obj, list):
        for v in obj:
            yield from collect_strings(v)


def extract_restore_target(text: str):
    m = RESTORE_DEST_RE.search(text)
    if m:
        return m.group(1)
    return None


def extract_node_path(node: dict):
    path = node.get("Path")
    if path:
        return path
    info = node.get("Info")
    if isinstance(info, dict) and isinstance(info.get("Name"), str):
        return info["Name"]
    if isinstance(node.get("Filesystem"), str):
        return node["Filesystem"]
    return None


def get_broken_items(job: str):
    stdout, stderr, rc = run(
        ["zrepl", "status", "--mode", "raw"], check=False
    )
    if rc != 0 or not stdout:
        raise RuntimeError(f"Could not read zrepl raw status: {stderr}")

    data = json.loads(stdout)
    jobs = data.get("Jobs", data)

    if job not in jobs:
        return []

    broken = {}

    for node in walk(jobs[job]):
        if not isinstance(node, dict):
            continue

        path = extract_node_path(node)
        if not path:
            continue

        texts = list(collect_strings(node))
        blob = "\n".join(texts)
        blob_lower = blob.lower()

        if any(p in blob_lower for p in ERROR_PATTERNS):
            entry = broken.setdefault(
                path,
                {"fs": path, "restore_snapshot": None, "messages": []},
            )
            entry["messages"].append(blob)

            snap = extract_restore_target(blob)
            if snap:
                entry["restore_snapshot"] = snap

    return [broken[k] for k in sorted(broken)]


def dataset_exists(ds: str) -> bool:
    _, _, rc = run(["zfs", "list", "-H", "-o", "name", ds], check=False)
    return rc == 0


def snapshot_exists(snap: str) -> bool:
    _, _, rc = run(
        ["zfs", "list", "-H", "-o", "name", "-t", "snapshot", snap],
        check=False,
    )
    return rc == 0


def release_holds_on_snapshot(snap: str):
    stdout, _, _ = run(["zfs", "holds", "-H", snap], check=False)
    if not stdout:
        return

    for line in stdout.splitlines():
        parts = line.split()
        if len(parts) >= 2:
            snapname, tag = parts[0], parts[1]
            print(f"    releasing hold: {tag} on {snapname}")
            run(["zfs", "release", tag, snapname], check=False)


def release_holds_recursive(dataset: str):
    stdout, _, _ = run(
        ["zfs", "list", "-H", "-o", "name", "-t", "snapshot", "-r", dataset],
        check=False,
    )
    if not stdout:
        return

    seen = set()
    for snap in stdout.splitlines():
        holds, _, _ = run(["zfs", "holds", "-H", snap], check=False)
        if not holds:
            continue
        for line in holds.splitlines():
            parts = line.split()
            if len(parts) >= 2:
                snapname, tag = parts[0], parts[1]
                key = (snapname, tag)
                if key in seen:
                    continue
                seen.add(key)
                print(f"    releasing hold: {tag} on {snapname}")
                run(["zfs", "release", tag, snapname], check=False)


def destroy_snapshot_first(snapshot: str, dry_run: bool):
    print(f"  -> conflicting snapshot: {snapshot}")

    if not snapshot_exists(snapshot):
        print("    -> snapshot does not exist, skipping")
        return

    if dry_run:
        print(f'    -> dry run: zfs destroy "{snapshot}"')
        return

    _, stderr, rc = run(["zfs", "destroy", snapshot], check=False)
    if rc == 0:
        print("    -> snapshot destroyed")
        return

    if "it's being held" in stderr.lower():
        print("    -> snapshot is held, releasing holds")
        release_holds_on_snapshot(snapshot)
        _, stderr2, rc2 = run(["zfs", "destroy", snapshot], check=False)
        if rc2 == 0:
            print("    -> snapshot destroyed after releasing holds")
            return
        raise RuntimeError(
            f"Failed to destroy snapshot after releasing holds: {stderr2}"
        )

    raise RuntimeError(f"Failed to destroy snapshot: {stderr}")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("-n", "--dry-run", action="store_true")
    ap.add_argument("job")
    args = ap.parse_args()

    root_fs = get_root_fs(args.job)
    items = get_broken_items(args.job)

    print(f"Detected receiver root: {root_fs}")

    if not items:
        print(f"No affected filesystems found for job {args.job}")
        return

    print("\nAffected filesystems:")
    for item in items:
        print(f"  {item['fs']}")

    for item in items:
        fs = item["fs"]
        target = f"{root_fs}/{fs}"
        print(f"\nTarget replica: {target}")

        if not target.startswith(root_fs + "/"):
            print(f"  -> refusing unsafe target path: {target}")
            continue

        if item["restore_snapshot"]:
            destroy_snapshot_first(item["restore_snapshot"], args.dry_run)

        if not dataset_exists(target):
            print("  -> target dataset does not exist, skipping")
            continue

        if args.dry_run:
            print(f'  -> dry run: would release holds under "{target}"')
            print(f'  -> dry run: zfs destroy -r "{target}"')
            continue

        print(f"  -> releasing holds under: {target}")
        release_holds_recursive(target)

        print(f"  -> destroying dataset recursively: {target}")
        _, stderr, rc = run(["zfs", "destroy", "-r", target], check=False)
        if rc != 0:
            raise RuntimeError(
                f"Failed to destroy dataset {target}: {stderr}"
            )

    if not args.dry_run:
        print(f"\nTriggering replication: {args.job}")
        _, stderr, rc = run(
            ["zrepl", "signal", "wakeup", args.job], check=False
        )
        if rc != 0:
            raise RuntimeError(
                f"Failed to wake up job {args.job}: {stderr}"
            )


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)

Grenzen und Erwartungen
#

✅ Was diese Lösung kann
#

  • Node-Ausfall überstehen: VM startet automatisch auf dem anderen Node
  • Quorum ohne dritten Server: QDevice reicht als dritter Vote
  • Einfacher Betrieb: kein SAN, kein Shared Storage
  • Automatische Recovery: nach Node-Rückkehr läuft die Replikation von allein wieder an

⚠️ Was diese Lösung nicht kann
#

  • Synchrone Zustandsübernahme: Datenverlust bis zum letzten Replikationslauf möglich (RPO)
  • Sofortiger Failover: vom Node-Ausfall bis die VM auf dem anderen Node wieder erreichbar ist vergehen in der Praxis ca. 3 Minuten (Watchdog-Reboot + HA-Übernahme + VM-Boot)
  • Split-Brain-Schutz ohne QDevice: ohne den dritten Vote blockiert das Quorum
  • Kein Backup-Ersatz: HA schützt gegen Hardware-Ausfall, nicht gegen gelöschte VMs, Ransomware oder Fehlkonfiguration – die repliziert sich nämlich auch sauber auf den zweiten Node

Für eine Firewall, deren Regelwerk nicht ständig mutiert, passt das. Als Backup-Lösung läuft bei uns zusätzlich Proxmox Backup Server – er hält inkrementelle, deduplizierte Snapshots der VMs unabhängig vom Cluster vor.

Alternativen: PegaProx
#

Wer mehrere Proxmox-Cluster zentral verwalten will, sollte sich PegaProx anschauen. PegaProx bietet unter anderem ein eigenes 2-Node-HA-Feature, das über die native Proxmox-HA hinausgeht. Im Lab-Test hat das bei uns allerdings nur mit Einschränkungen funktioniert – vermutlich, weil PegaProx für das 2-Node-HA Shared Storage erwartet und lokales ZFS mit Replikation nicht vollständig unterstützt wird. Für den hier beschriebenen Ansatz (lokales ZFS + pvesr) bleiben die Proxmox-Bordmittel die bessere Wahl.

Weiterführende Links#

Fazit
#

Zwei Nodes, ein QDevice, ZFS-Replikation. Kein Shared Storage, kein SAN, trotzdem Failover. Die Proxmox-Bordmittel (pvesr, HA-Manager) reichen dafür aus. Wer paranoid ist, schaltet zrepl als zweiten Replikationspfad dazu.


Wim Bonis ist CTO bei Stylite AG und beschäftigt sich schwerpunktmäßig mit Storage, Virtualisierung und Open-Source-Infrastruktur.

Verwandte Artikel

Proxmox vs OpenStack - Ein detaillierter Vergleich
Wim Bonis
Virtualisierung Open Source Proxmox OpenStack
ZFS Snapshots von Open-E JovianDSS per Pull replizieren
Wim Bonis
Storage ZFS Open Source Open-E TrueNAS
TrueNAS 26 – Ransomware-Schutz, Hybrid-Pools und ein neues Versionsschema
Wim Bonis
Storage TrueNAS ZFS Security Open Source
TrueNAS Goldeye 25.10 – Einfachere Deployments und Terabit-Performance
Wim Bonis
Storage TrueNAS ZFS Performance Enterprise AI KI Virtualisierung
Open-E JovianDSS mit Checkmk überwachen
Wim Bonis
Monitoring Storage Open Source Open-E
ZFS Snapshot-Replikation in TrueNAS SCALE: Remote Backup mit Push und Pull
Wim Bonis
Storage ZFS TrueNAS Backup Replikation
NFON Call Monitor – Echtzeit-Anrufüberwachung für NFON-Telefonanlagen
Wim Bonis
Tools Open Source NFON Telefonanlage CTI ProjectFacts
Stylite Free Tools – Datenschutzfreundliche Open-Source-Werkzeuge im Browser
Wim Bonis
Tools Open Source Security
Stoßgebete aus dem Rechenzentrum: Was sich jeder Storage-Admin wirklich wünscht
Wim Bonis
Storage ZFS Security Monitoring