Snapshots per rsync

Aus UUGRN
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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

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

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

    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