Docker-Gitea-Postgres Part 2: Postgres Backup

Teil 2 einer Reihe über Gitea & Postgres im Docker-Container: Postgres Backup

Hintergrund dieser Reihe ist das notwendige Update einer Docker-Gitea-Instanz und in diesem Rahmen auch das Major-Version-Upgrade der zugehörigen Postgres-Datenbank.

Postgres

Backup allgemein

Um aus einer laufenden Postgres-Instanz (nahezu) alle Daten zu sichern, gibt es den Befehl pg_dumpall. Da das entstehende Backup reiner Text ist, wird es gleich in einem Rutsch mit xz komprimiert, damit es kleiner wird.

postgres@db:~ # pg_dumpall | xz >pg-backup.xz
[...]

Umgekehrt kann man auch einen Dump in eine (leere) Datenbank wieder einspielen :

postgres@db:~ # xzcat pg-backup.xz | psql -U <USERNAME> -d <DATABASE>

Backup im Docker

Da in den meisten Docker-Umgebungen nicht nur eine Postgres-Datenbank laufen wird, habe ich mir im Laufe der Zeit ein Script geschrieben, welches bei Übergabe des Container-Namens sich ein Backup auf den Host (konkret: ins aktuelle Verzeichnis) zieht:

root@docker:~ # .../postgres-backup.sh gitea_db
-rw-r--r-- 1 root root   115320 Apr 17 09:59 gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz

Im Backup-Dateinamen sind nacheinander Container-Name, Zeitstempel, Image-Name und Posgres-Version hinterlegt. So kann man auch z.B. im Rahmen eines Upgrades ;) nachvollziehen, mit bzw. auf Basis welcher Version das Backup angelegt wurde.

Das Script macht folgende Schritte:

  • (Zeile 3-13) Basierend auf dem übergebenen Namen wird die Container-ID ermittelt
  • (Zeile 14-19) Beginnt der zu Grunde liegende Image-Name nicht mit "postgres" wird das Script abgebrochen - es ist vermutlich kein Postgres-Container
  • (Zeile 21-29) Der Postgres-Username und -Version werden aus den Container-Umgebungsvariablen extrahiert
  • (Zeile 31-36) Der Backup-Dateinamen wird generiert und sichergestellt, dass es nicht bereits existiert
  • (Zeile 37-39) pg_dumpall wird aufgerufen, das Ergebnis durch xz komprimiert und ein ls -l auf die Datei angezeigt
#!/bin/ash

PGCONTAINER=$1
PGCONTID=$(docker ps -qf "name=^${PGCONTAINER}$")

if [ -z "$PGCONTID" ]; then
    echo "Container '${PGCONTAINER}' not found"
    exit 1
elif [ $(echo ${PGCONTID}| wc -m) -ne 13 ]; then
    echo "Unknown container id: ${PGCONTID}"
    exit 1
fi

DIMAGE=$(docker inspect ${PGCONTID} | jq '.[].Config.Image' | tr -d '"')
PGIMAGE=$(basename $DIMAGE | grep '\(^\|/\)postgres:')
if [ -z "${PGIMAGE}" ]; then
    echo "Not a Posgres image '${DIMAGE}'"
    exit 1
fi

export $(docker inspect ${PGCONTID} | jq '.[].Config.Env' | grep '"POSTGRES_USER\|"PG_VERSION' | tr -d '"' | sed -e 's/,$//')

if [ -z "${POSTGRES_USER}" ]; then
    echo "No Posgres user found"
    exit 1
elif [ -z "${PG_VERSION}" ]; then
    echo "No Postgres version number found"
    exit 1
fi

BACKUPFILE=${PGCONTAINER}_$(date +%F_%T)_${PGIMAGE}_${PG_VERSION}
if [ -f "${BACKUPFILE}.xz" ]; then
    echo "Backupfile exists"
    exit 1
fi

docker exec -it -w /tmp ${PGCONTID} sh -c "pg_dumpall -U ${POSTGRES_USER}" | xz >${BACKUPFILE}.xz

ls -l ${BACKUPFILE}.xz

Restore im Docker

Ich habe es nicht geschafft, die Backup-Datei vom Host aus direkt an einen psql in einen Container per Pipe zu übergeben. Falls hier jemand einen Tipp hat, gerne per Mail an mich ;)

Um also die Daten in den neu angelegten (oder umgezogenen oder...) Container zu bringen, kopiert man die Backup-Datei in den Container:

root@docker:~ # docker cp $BACKUPFILE gitea_db:/tmp/

Der zweite Schritt besteht dann darin, im Container das Backup wieder an Postgres zu verfüttern:

root@docker:~ # docker exec -it gitea_db bash
root@bash-5.1# psql -U gitea -d gitea < /tmp/gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz
root@bash-5.1# rm /tmp/gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz

Es macht natürlich keinen Sinn, das Backup im Container liegen zu lassen, es ist schließlich nur eine Kopie, daher wird es auch gleich wieder gelöscht.

Anmerkungen:

  • Meine Docker-Installationen laufen im Allgemeinen auf einem btrfs-Dateisystem. Daher ist Kopieren und Löschen ein Vorgang, der keinerlei Speicher benötigt. Läuft Docker allerdings mit irgendeiner Art von Images, dann ist dieser HD-/SSD-Speicherplatz bis zum nächsten Container-Anlegen und anschließendem "purge" verloren! Hier geht's nur um ca. 100kB, also wirklich nicht viel. Bei einer Multi-GiByte-Datenbank sieht das dann vielleicht anders aus!
  • Natürlich könnte man die Backup-Datei in ein temporäres Docker-Volume kopieren, dieses in den neuen Datenbank-Container einhängen und von dort das Restore machen. Der Aufwand war/ist mir aber zu groß.
  • Wer sofort seinen Speicher zurück möchte, der kann natürlich nach dem Restore den Container zerstören und neu anlegen. Dann ist der Speicher nach der nächsten Maintainance auch wieder frei.