Migration

Migration

Die Aufgabe ist folgende: Wir müssen ein Videoarchiv von einem teuren Rechenzentrum in ein günstigeres verlagern. Option eins: Die Festplatten per Post schicken. Allerdings handelt es sich um zehn alte Festplatten im RAID-0-Verbund. Wir müssten das System lokal auf den alten Festplatten wieder aufbauen und es dann auf die neuen übertragen. Option zwei: Die Daten über das Internet übertragen. Das klingt unwahrscheinlich, da es sich um ein Videoarchiv handelt, aber wir geben bekanntlich nicht so schnell auf!

1. Investitionen

Wir kaufen 10 vServer mit minutengenauer Abrechnung. Als Image-Installationsserver verwenden wir Debian (Debian-1200-*-minimal - Debian Bookworm oder Debian-1300-*-minimal - Trixie). Daraus ergibt sich: Server 0 ist die Quelle, Server 10 ist das Ziel, Server 1-9 sind Hilfsknoten.

2. Installation und Vorbereitungen Server 0

Sie benötigen ein Torrent-Erstellungsprogramm, den Torrent-Client selbst und rsync zum Kopieren der Metadaten. Server 0 sollte sich automatisch mit den Servern 1–10 verbinden, ohne dass ein Passwort eingegeben werden muss. Kopieren Sie den Schlüssel auf ALLE Server (1–10).


sudo -s
# Wenn Sie kein Root-Benutzer sind

apt update && apt install -y transmission-cli rsync

ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519

ssh-copy-id root@server1
ssh-copy-id root@server2
# ... und so weiter bis Server 10

# Prüfung
ssh root@server1 "echo OK"


# Wenn debian-minimal installiert ist, gehen wir sofort zum nächsten Punkt über.

# Wenn UFW verwendet wird
ufw allow 51413/tcp
ufw allow 51413/udp

# Wenn die Ports plötzlich über iptables geschlossen werden (dies ist bei Hetzner nicht der Fall, kann aber bei anderen Systemen im Installationsabbild enthalten sein)
iptables -L INPUT -v -n --line-numbers

# Wenn sie geschlossen sind, öffnen Sie sie.
iptables -A INPUT -p tcp --dport 51413 -j ACCEPT
iptables -A INPUT -p udp --dport 51413 -j ACCEPT

# Dies ist eine einfache Möglichkeit, sich keine Gedanken über Netzwerkschnittstellen machen zu müssen.
netfilter-persistent save

# Falls iptables-persistent nicht vorhanden ist, installieren Sie es.
apt install iptables-persistent
              

3. Installation und Vorbereitungen Server 1–9 (Worker/Peers)

Diese Server fungieren als Zwischenknoten. Sie laden gleichzeitig Dateien voneinander herunter und verteilen sie sofort weiter, wodurch die Last auf Server 0 reduziert wird.


sudo -s
# Wenn Sie kein Root-Benutzer sind

apt update && apt install -y transmission-cli rsync



# Wenn debian-minimal installiert ist, gehen wir sofort zum nächsten Punkt über.

# Wenn UFW verwendet wird
ufw allow 51413/tcp
ufw allow 51413/udp

# Wenn die Ports plötzlich über iptables geschlossen werden (dies ist bei Hetzner nicht der Fall, kann aber bei anderen Systemen im Installationsabbild enthalten sein)
iptables -L INPUT -v -n --line-numbers

# Wenn sie geschlossen sind, öffnen Sie sie.
iptables -A INPUT -p tcp --dport 51413 -j ACCEPT
iptables -A INPUT -p udp --dport 51413 -j ACCEPT

# Dies ist eine einfache Möglichkeit, sich keine Gedanken über Netzwerkschnittstellen machen zu müssen.
netfilter-persistent save

# Falls iptables-persistent nicht vorhanden ist, installieren Sie es.
apt install iptables-persistent
              

4. Installation und Vorbereitungen Server 10 (Endpunkt-/Zielserver)

Dieser Server ist das endgültige Ziel. Er sammelt alle Dateiteile im maximalen Multithreading-Modus aus dem gesamten Netzwerk der Worker.


sudo -s
# Wenn Sie kein Root-Benutzer sind

apt update && apt install -y transmission-cli rsync


# Wenn debian-minimal installiert ist, gehen wir sofort zum nächsten Punkt über.

ufw allow 51413/tcp && ufw allow 51413/udp

# Wenn die Ports plötzlich über iptables geschlossen werden (dies ist bei Hetzner nicht der Fall, kann aber bei anderen Systemen im Installationsabbild enthalten sein)
iptables -L INPUT -v -n --line-numbers

# Wenn sie geschlossen sind, öffnen Sie sie.
iptables -A INPUT -p tcp --dport 51413 -j ACCEPT
iptables -A INPUT -p udp --dport 51413 -j ACCEPT

# Dies ist eine einfache Möglichkeit, sich keine Gedanken über Netzwerkschnittstellen machen zu müssen.
netfilter-persistent save

# Falls iptables-persistent nicht vorhanden ist, installieren Sie es.
apt install iptables-persistent
              

5. Skript mit Protokollierung migration_script.sh (Server 0)

Wenn Sie das Skript ausführen, wird im Stammverzeichnis des Ordners /data/ (oder wo immer Sie es angeben) eine Datei namens migration_pipeline.log erstellt.


#!/bin/bash
set -e

# ==========================================
# CENTRAL CONFIGURATION
# ==========================================
# Verzeichnis mit Dateien auf Server 0
SOURCE_DIR="/data/oracle_migration_export_dir"
STAGE_DIR="/data/torrent_zone"
TORRENT_FILE="${STAGE_DIR}/migration.torrent"
REMOTE_USER="root"
REMOTE_META_DIR="/data/torrent_meta"
REMOTE_CONFIG_DIR="/data/torrent_config"
REMOTE_WORKER_DL_DIR="/data/torrent_download"   # Server 1-9
REMOTE_ENDPOINT_DL_DIR="/data/final_target"      # Server 10

# Allgemeine Protokolldatei auf Server 0
PIPELINE_LOG="/data/migration_pipeline.log"

WORKER_SERVERS=("server1" "server2" "server3" "server4" "server5" "server6" "server7" "server8" "server9")
ENDPOINT_SERVER="server10"

# Überwachungs-Intervall (in Sekunden)
POLL_INTERVAL=15
# ==========================================

# Zeitzählfunktion
format_duration() {
    local seconds=$1
    echo "$((seconds / 60)) min. $((seconds % 60)) sek."
}

# Es wird geprüft, ob das Quellverzeichnis existiert.
if [ ! -d "$SOURCE_DIR" ]; then
    echo "[ERROR] Das Quellverzeichnis $SOURCE_DIR existiert nicht!" | tee -a "$PIPELINE_LOG"
    exit 1
fi

# Wir erstellen eine Liste aller Dateien in einem Verzeichnis (nur Dateien, ohne Unterverzeichnisse).
IFS=$'\n' FILES=($(find "$SOURCE_DIR" -maxdepth 1 -type f))
unset IFS

TOTAL_FILES=${#FILES[@]}
if [ "$TOTAL_FILES" -eq 0 ]; then
    echo "[INFO] Im Verzeichnis $SOURCE_DIR befinden sich keine Dateien, die übertragen werden müssen." | tee -a "$PIPELINE_LOG"
    exit 0
fi

echo "=========================================================="
echo " Für die serielle Übertragung gefundene Dateien: $TOTAL_FILES"
echo " Das Ausführungsprotokoll wird in folgendes Verzeichnis geschrieben: $PIPELINE_LOG"
echo "=========================================================="

echo "=== START DER MIGRATIONSPIPELINE: $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$PIPELINE_LOG"

CURRENT_INDEX=0

for CURRENT_FILE in "${FILES[@]}"; do
    CURRENT_INDEX=$((CURRENT_INDEX + 1))
    FILE_NAME=$(basename "$CURRENT_FILE")

    # Korrigieren Sie die Startzeit für die aktuelle Datei.
    START_TIME_SEC=$(date +%s)
    START_DATE_STR=$(date '+%Y-%m-%d %H:%M:%S')

    echo "----------------------------------------------------------"
    echo " [Datei $CURRENT_INDEX von $TOTAL_FILES]: $FILE_NAME"
    echo "----------------------------------------------------------"
    echo "[$(date '+%H:%M:%S')] Die Verarbeitung der Datei $FILE_NAME wird gestartet..." >> "$PIPELINE_LOG"

    # Die Torrent-Zeitzone auf Server 0 wird vor dem Herunterladen einer neuen Datei gelöscht.
    rm -rf "$STAGE_DIR"
    mkdir -p "$STAGE_DIR"

    echo "[STAGE 1] Erstelle die .torrent Datei..."
    transmission-create -p -o "$TORRENT_FILE" "$CURRENT_FILE"

    echo "[STAGE 1] Starte Seeding auf Server 0..."
    ORIGIN_BASE_DIR=$(dirname "$CURRENT_FILE")
    nohup transmission-cli -g "$STAGE_DIR/.config" -w "$ORIGIN_BASE_DIR" "$TORRENT_FILE" > "${STAGE_DIR}/seeder.log" 2>&1 &
    MASTER_SEED_PID=$!

    echo "[STAGE 2] Verteile Metadaten an alle Server..."
    ALL_REMOTES=("${WORKER_SERVERS[@]}" "$ENDPOINT_SERVER")
    for SERVER in "${ALL_REMOTES[@]}"; do
        ssh -o StrictHostKeyChecking=no "${REMOTE_USER}@${SERVER}" "mkdir -p ${REMOTE_META_DIR} ${REMOTE_CONFIG_DIR} ${REMOTE_WORKER_DL_DIR} ${REMOTE_ENDPOINT_DL_DIR}"
        rsync -avz "$TORRENT_FILE" "${REMOTE_USER}@${SERVER}:${REMOTE_META_DIR}/"
    done

    echo "[STAGE 3] Initialisiere Prozesse auf Servern 1-9..."
    for WORKER in "${WORKER_SERVERS[@]}"; do
        ssh "${REMOTE_USER}@${WORKER}" "
            nohup transmission-cli -g ${REMOTE_CONFIG_DIR} -w ${REMOTE_WORKER_DL_DIR} ${REMOTE_META_DIR}/migration.torrent > /var/log/torrent_worker.log 2>&1 &
        "
    done

    echo "[STAGE 4] Initialisiere Endpoint auf Server 10..."
    ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "rm -f /var/log/torrent_endpoint.log"
    ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "
        nohup transmission-cli -g ${REMOTE_CONFIG_DIR} -w ${REMOTE_ENDPOINT_DL_DIR} ${REMOTE_META_DIR}/migration.torrent > /var/log/torrent_endpoint.log 2>&1 &
    "

    echo "[STAGE 5] Überwachung der Übertragung..."
    TRANSFER_SUCCESS=true

    while true; do
        if ! ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "pgrep -f 'transmission-cli.*migration.torrent'" > /dev/null 2>&1; then
            echo ""
            if ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "grep -q -E 'Progress: 100%|State: Seeding|Progress: 100.0%' /var/log/torrent_endpoint.log" > /dev/null 2>&1; then
                echo "[SUCCESS] Die Datei $FILE_NAME wurde erfolgreich auf Server 10 heruntergeladen."
                break
            else
                echo "[ERROR] Fehler beim Übertragen der Datei $FILE_NAME!"
                TRANSFER_SUCCESS=false
                break
            fi
        fi

        PROGRESS=$(ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "grep -o 'Progress: [0-8kMGTPEZY]\{1,3\}[0-9.]*%' /var/log/torrent_endpoint.log | tail -n 1" || true)
        if [ -z "$PROGRESS" ]; then
            PROGRESS="Initialisiere..."
        fi
        echo -ne "[STATUS] Übertragung $FILE_NAME -> Server 10: ${PROGRESS}\r"
        sleep "$POLL_INTERVAL"
    done

    # Die Verteilung der aktuellen Datei auf Server 0 wird beendet.
    kill "$MASTER_SEED_PID" || true

    # Berechnen die Zeitabläufe
    END_TIME_SEC=$(date +%s)
    DURATION_SEC=$((END_TIME_SEC - START_TIME_SEC))
    DURATION_FORMATTED=$(format_duration "$DURATION_SEC")

    if [ "$TRANSFER_SUCCESS" = true ]; then
        # Bei Erfolg in das Protokoll schreiben
        echo "Datei: $FILE_NAME | Start: $START_DATE_STR | Zeitaufwand: $DURATION_FORMATTED | Status: [SUCCESS]" >> "$PIPELINE_LOG"
    else
        # Protokollierung bei Absturz und Notfallbeendigung des Skripts
        echo "Datei: $FILE_NAME | Startdatum: $START_DATE_STR | Verstrichene Zeit bis zum Fehler: $DURATION_FORMATTED | Status: [ERROR]" >> "$PIPELINE_LOG"
        exit 1
    fi

    echo "----------------------------------------------------------"
    echo " WORKER-AUFBAU: Entfernen einer Datei von den Servern 1-9"
    echo "----------------------------------------------------------"
    for WORKER in "${WORKER_SERVERS[@]}"; do
        echo "[Bereinigung] Datei und Cache auf ${WORKER} werden gelöscht..."
        ssh "${REMOTE_USER}@${WORKER}" "
            pkill -f 'transmission-cli.*migration.torrent' || true
            rm -f \"${REMOTE_WORKER_DL_DIR}/${FILE_NAME}\"
            rm -rf ${REMOTE_WORKER_DL_DIR}/{*,.*} 2>/dev/null || true
            rm -rf ${REMOTE_META_DIR}/* ${REMOTE_CONFIG_DIR}/*
        "
    done

    # Beenden den Prozess und bereinigen die Metadaten auf Server 10 (die Datei selbst BLEIBT bestehen).
    ssh "${REMOTE_USER}@${ENDPOINT_SERVER}" "
        pkill -f 'transmission-cli.*migration.torrent' || true
        rm -rf ${REMOTE_META_DIR}/* ${REMOTE_CONFIG_DIR}/*
    "

    echo "[INFO] Datei $FILE_NAME wurde auf den Servern 1-9 gelöscht. Speicherplatz ist verfügbar."
done

# Abschließende Bereinigung des temporären Torrent-Ordners auf Server 0
rm -rf "$STAGE_DIR"

echo "=== PIPELINE FERTIGGESTELLT: $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$PIPELINE_LOG"

echo "=========================================================="
echo " END-TO-END PIPELINE ERFOLGREICH BEENDET!"
echo "=========================================================="

Das Skript ausführen:

  • Es wird empfohlen, das Skript in einer Screen- oder tmux-Sitzung auszuführen, damit der Übertragungsprozess (der eine lange Zeit dauern kann) nicht unterbrochen wird, falls die Verbindung zu Server 0 versehentlich abbricht.
  • tmux new -s migration
  • ./migration_script.sh