Snapshots per rsync
Dieses Script wird verwendet, um regelmäßige Snapshots kompletter Subdirectories zu archivieren. Dabei verwendet es im Kern die beiden Tools rsync und GNU copy (unter Linux bekannt als cp).
Funktionsweise[Bearbeiten]
Beim ersten Aufruf wird eine Urkopie (snap_0)erstellt (Zeile 67-71), diese wird per rsync vom Quellverzeichnis aus geschrieben (Zeile 77). War der rsync erfolgreich, wird das Verzeichnis work_* in snap_* umbenannt (Zeile 77-85).
Bei allen weiteren Aufrufen werden dann lediglich Deltas erzeugt, indem vom jüngsten snap_*-Verzeichnis per GNU copy zunächst eine Hardlink-Kopie erzeugt wird . Dabei wird lediglich die Verzeichnisstruktur real kopiert, alle normalen Dateien werden als Hardlink erstellt und nehmen daher keinen weiteren Platz in Anspruch. work_1 enthält nun eine exakte Kopie von snap_0 (Zeile 63-66)
Es folgt ein Abgleich per rsync mit dem Quellverzeichnis (Zeile 77). Dabei werden alle Dateien gelöscht und neu geschrieben, die sich geändert haben. Hat eine Datei mehr als eine Referenz, so wird beim Löschen lediglich diese Referenz entfernt, d.h. in den vorhergehenden Snapshots bleibt diese Datei erhalten. Ist der rsync erfolgreich, wird work_1 in snap_1 umbenannt (Zeile 77-80).
Schlägt ein rsync-Aufruf fehl, so bleibt die unvollständige oder fehlerhafte Kopie als work_* erhalten, das darauffolgende Delta arbeitet immer auf dem zuletzt erfolgreichen snap_*. Gründe für einen fehlgeschlagenen rsync sind beispielsweise Dateien, die zwischen dem Generieren der Dateiliste und dem Kopieren verschwinden (vanished).
Denkbar wäre, die Zeile 77 zu ersetzen:
77 if /usr/local/bin/rsync -aHW --delete --delete-before "${DIR}/" "${WORKDIR}"; then
nach
77 RSYNCCMD="/usr/local/bin/rsync -aHW --delete --delete-before ${DIR}/ ${WORKDIR}" 78 if ${RSYNCCMD} || ${RSYNCCMD}; then
Dadurch wird der rsync im Fehlerfall 2x ausgeführt. Der zweite rsync sollte damit auch deutlich schneller durchlaufen und damit die Zeit zwischen der Erstellung des Katalogs und dem tatsächlichen Kopieren der Dateien drastisch verkürzen, was die Fehlerwahrscheinlichkeit idR senkt.
Schlägt rsync immer fehl, kann dieses jedoch toleriert werden, so kann die Zeile 77 einfach durch || true ergänzt werden, damit der if-Block ausgeführt wird.
Im Beispiel wurden die Verzeichnisse snap_1 genannt, im realen betrieb werden hier timestamps verwendet, siehe Zeile 28-32.
Anwendung[Bearbeiten]
Anders als bei einer ausgewachsenen Versionsverwaltung müssen die Daten nicht explizit in einem Repository angemeldet werden, die Dateien "wissen" nichts von snapshots. Dieses Verfahren eignet sich beispielsweise dafür, statische Binärdaten wie etwa Multimedia-Dateien etc. zu versionieren.
Bearbeitet man beispielsweise ein Bild und speichert es unter gleichem Namen wieder ab, geht die alte Version im Quellverzeichnis erstmal verloren. Im snapshot-Verzeichnis jedoch bleibt die alte Version erhalten.
Verzeichnisse, die überwiegend wachsende Dateien enthalten, wie etwa Logfiles, sind nur begrenzt sinnvoll für die snapshots, da diese Dateien bei JEDEM Durchlauf komplett neu kopiert werden. Dabei entstehen zwar funktionierende Snapshots, jedoch werden auch geringe Änderungen mit dem vollen Speicherplatz archiviert, keine Deltas.
Script[Bearbeiten]
1 #! /bin/sh 2 # 3 # Verzeichnis-Snapshots erstellen mit gnu-copy(hardlinks) und rsync 4 # 5 # 6 # 7 # Aufruf: 8 # $0 /path/to/dir/ 9 # 10 # --> /data/snapshot/path_to_dir/<timestamp>/... 11 12 13 SNAPSHOTHOME=/data/snapshot 14 15 # Symlinks etc auflösen, benötigen den absoluten pfad 16 abspath() 17 { 18 (cd $1 && pwd -P) 2>/dev/null 19 } 20 21 # Zielverzeichnis ermitteln 22 snapdir() 23 { 24 echo -n "${SNAPSHOTHOME}/" 25 echo "$1"| tr '/ ' '__' 26 } 27 28 # Zeitstempel ermitteln 29 timestamp() 30 { 31 date +%Y%m%d%H%M%S 32 } 33 34 # jüngsten (vollständigen) snapshot in $SNAPDIR ermitteln 35 getlatest() 36 { 37 ls -1dr "$SNAPDIR"/snap_20*/ | head -n 1 2>/dev/null 38 } 39 40 if [ $# -ge 1 ]; then 41 while [ -n "$1" ]; do 42 DIR="$(abspath "$1")" 43 if [ -d "${DIR}" ]; then 44 SNAPDIR=$(snapdir "${DIR}") 45 echo "$0: Bearbeite ${DIR} ... " 46 47 # Anlegen, falls nicht vorhanden 48 if [ ! -d "${SNAPDIR}" ]; then 49 mkdir -p "${SNAPDIR}" 2>/dev/null 50 fi 51 52 # Weitermachen, falls jetzt vorhanden 53 if [ -d "${SNAPDIR}" ]; then 54 echo "$0: Snapshot von $DIR nach $SNAPDIR" 55 TS=$(timestamp) 56 LATESTSNAP="$(getlatest)" 57 WORKDIR="${SNAPDIR}/work_${TS}" 58 FINISHDIR="${SNAPDIR}/snap_${TS}" 59 60 # jüngsten snapshot ermitteln 61 # falls vorhanden mit GNU-copy als hardlink-Kopie anlegen 62 # oder leeres Verzeichnis erstellen 63 if [ -n "${LATESTSNAP}" ]; then 64 # Alter snapshot besteht, gcp-hardlink erzeugen 65 echo "$0: Hardlink-Kopie ${LATESTSNAP} --> ${WORKDIR} erzeugen (gcp)" 66 /usr/local/bin/gcp -al "${LATESTSNAP}" "${WORKDIR}" 67 else 68 # Kein Alter snapshot vorhanden, neu (leer) anlegen 69 echo "$0: Leeres ${WORKDIR} anlegen ..." 70 mkdir "${WORKDIR}" 71 fi 72 73 # Quellverzeichnis ins Arbeitsverzeichnis rsync'en 74 # DIR -> WORKDIR 75 if [ -d "${WORKDIR}" ]; then 76 echo "$0: rsync ${DIR}/ -> ${WORKDIR} ..." 77 if /usr/local/bin/rsync -aHW --delete --delete-before "${DIR}/" "${WORKDIR}"; then 78 if mv "${WORKDIR}" "${FINISHDIR}" ; then 79 echo "$0: ${DIR} --> ${FINISHDIR} abgeschlossen. OK." 80 else 81 echo "$0: Fehler beim Abschließen: "${WORKDIR}" --> ${FINISHDIR}" >&2 82 fi 83 else 84 echo "$0; Fehler bei rsync ${DIR}/ --> ${WORKDIR}" >&2 85 fi 86 else 87 echo "$0: ${WORKDIR} nicht gefunden, konnte nicht erstellt werden" >&2 88 fi 89 else 90 echo "$0: $SNAPDIR nicht gefunden, konnte nicht angelegt werden oder kein Verzeichnis" >&2 91 fi 92 else 93 echo "$0: $1 nicht gefunden" >&2 94 fi 95 shift 96 done 97 else 98 echo "$0: Fehlende Parameter" >&2 99 echo "Aufruf:" >&2 100 echo "$0 /path/to/dir [/path2/to/dir] ..." >&2 101 exit 1 102 fi 103 104