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 durchxz
komprimiert und einls -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.