Docker MySQL and MariaDB Backup Script

Aus Laub-Home Wiki

Bei MySQL oder MariaDB Containern legt man die persistenten Datenbank Daten zwar auf einem Docker Volume ab, sichert die Datenbank aber dennoch mit mysqldump bzw. bei MariaDB mit mariadb-dump. Dies verhindert dass die Daten die man sichert inkonsistent sind, da man beim Abzug der Datenbank Dateien sicherstellen müsste, das keine Datenbankoperatoren zur gleichen Zeit stattfinden. Man müsste also den DB Container vorher stoppen. Deshalb ist es Best Practice die im Container laufende MySQL oder MariaDB Datenbank via mysqldump zu sichern. Den Befehl reicht man einfach in den Container rein und lässt den Output, der über stdout ausgegeben wird in eine Datei laufen. Will man es komprimieren, lässt man den Output vorher durch gzip laufen. (mehr Informationen hier)

# MySQL DB
docker exec -e MYSQL_PWD=ROOTPASSWORT \
                DBCONTAINERNAME /usr/bin/mysqldump -u root DATABASENAME \
                | gzip > backup.sql.gz
                
# MariaDB
docker exec -e MYSQL_PWD=ROOTPASSWORT \
                DBCONTAINERNAME /usr/bin/mariadb-dump -u root DATABASENAME \
                | gzip > backup.sql.gz

Will man wissen, welche Container auf dem MySQL oder MariaDB Image aufsetzen, welches man ggf. sichern möchte, erledigt das der folgende Befehl:

docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1

--format '{{.Names}}:{{.Image}}' sorgt dafür das nur der Containername und das verwendete Docker Image, aller laufender Container ausgegeben werden, der grep Befehl filtert die Ausgabe dann nur auf MySQL und MariaDB Images. Das backup-docker-mysql.sh Script bündelt die beiden Befehle und sichert somit alle MySQL/MariaDB Datenbanken, die als Container auf dem Docker Host laufen in den definierten Backup Ordner. Die Datenbank Backups werden mit einem Zeitstempel versehen und können nach X-Tagen (DAYS) automatisch gelöscht werden. Wichtig ist auch, da es sich um ein lokales Backup handelt, sollte man auf jeden Fall die Backup Dateien auf irgendein anderes System oder in die Cloud (Dropbox, Google Drive und Co) sichern. Das Script legt man am besten nach /usr/local/sbin/backup-docker-mysql.sh. Dieses kann dann via cron-job automatisch, regelmäßig gestartet werden.

Script backup-docker-mysql.sh

Das Script kann einfach in /usr/local/sbin/ angelegt werden. Nach dem Anlegen das Execute Recht vergeben und die folgenden Parameter im Script nach belieben definieren:

Parameter Definition Beispiel
BACKUPDIR definiert den Backup Ordner, sollte er nicht existieren, wird er automatisch angelegt
BACKUPDIR=/backup/mysql
DAYS definiert die wie lange ein Backup aufgehoben wird
DAYS=3
TIMESTAMP definiert den Zeitstempel der im Backup Dateiname verwendet wird
TIMESTAMP=$(date +"%Y%m%d%H%M")
CONTAINER definiert die Container, dessen Datenbanken gesichert werden sollen. Bei mehr als einem Container, einfach ein Leerzeichen zwischen die verschiedenen Container Namen setzen. Hier kann auch mittels
docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1
gearbeitet werden, um zum Beispiel alle Container, auf Basis des mysql oder mariadb Image, zu sichern, oder diese mit grep weiter zu filtern. (Siehe Kommentare im Script)
CONTAINER="mysqlcontainer 1 mysqlcontainer2 mysqlcontainer3"
VOLUME=CONTAINER=$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1)
CONTAINER=$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1 | grep -v 'container1\|container2')

Download des Scriptes hier möglich: https://github.com/alaub81/backup_docker_scripts/raw/main/backup-docker-mysql.sh

/usr/local/sbin/backup-docker-mysql.sh

#!/usr/bin/env bash
#########################################################################
#Name: backup-docker-mysql.sh
#Subscription: This Script backups docker mysql or mariadb containers,
#or better dumps their database to a backup directory
##by A. Laub
#andreas[-at-]laub-home.de
#
# More informations:
# https://www.laub-home.de/wiki/Docker_MySQL_and_MariaDB_Backup_Script
#
# Restore:
# docker exec CONTAINERNAME env
# zcat BACKUPFILE.sql.gz | docker exec -i CONTAINERNAME /usr/bin/mysql -u root --password=ROOTPASSWORD DATABASENAME
#
#License:
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License as published by the
#Free Software Foundation, either version 3 of the License, or (at your option)
#any later version.
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#or FITNESS FOR A PARTICULAR PURPOSE.
#########################################################################
#Set the language
export LANG="en_US.UTF-8"
#Load the Pathes
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# set the variables

# Where to store the Backup files?
BACKUPDIR=/backup/mysql

# How many Days should a backup be available?
DAYS=2

# Timestamp definition for the backupfiles (example: $(date +"%Y%m%d%H%M") = 20200124-2034)
TIMESTAMP=$(date +"%Y%m%d%H%M")

# Which Containers do you want to backup?
# Container names separated by space
#CONTAINER="mysqlcontainer1 mysqlcontainer2 mysqlcontainer3"
# you can use "$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1)"
# for all containers which are using mysql or mariadb images
#CONTAINER=$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1)
# you can filter all containers with grep (include only) or grep -v (exclude) or a combination of both
# to do a filter for 2 or more arguments separate them with "\|"
# example: $(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1 | grep -v 'container1\|container2')
#CONTAINER=$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1 | grep -v 'container1\|container2')
CONTAINER=$(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1)


### Do the stuff
echo -e "Start $TIMESTAMP Backup for Databases: \n"
if [ ! -d $BACKUPDIR ]; then
	mkdir -p $BACKUPDIR
fi

for i in $CONTAINER; do
    MYSQL_PWD=$(docker exec $i env | grep MYSQL_ROOT_PASSWORD | cut -d"=" -f2)

	# check for dump method mariadb / mysql
	if docker exec $i test -e /usr/bin/mysqldump; then
	    # Get a list of databases in the container
    	DATABASES=$(docker exec -e MYSQL_PWD=$MYSQL_PWD $i mysql -uroot -s -e "show databases" | grep -Ev "(Database|information_schema|performance_schema|mysql)")
    	# Loop through each database and create a backup
    	for MYSQL_DATABASE in $DATABASES; do
			# Start Backup
	    	echo -e " create MYSQL Backup for Database on Container:\n  * $MYSQL_DATABASE DB on $i";
	    	docker exec -e MYSQL_DATABASE=$MYSQL_DATABASE -e MYSQL_PWD=$MYSQL_PWD \
				$i /usr/bin/mysqldump -u root $MYSQL_DATABASE | gzip > $BACKUPDIR/$i-$MYSQL_DATABASE-$TIMESTAMP.sql.gz
		done		
	elif docker exec $i test -e /usr/bin/mariadb-dump; then
	    # Get a list of databases in the container
    	DATABASES=$(docker exec -e MYSQL_PWD=$MYSQL_PWD $i mariadb -uroot -s -e "show databases" | grep -Ev "(Database|information_schema|performance_schema|mysql)")
    	# Loop through each database and create a backup
    	for MYSQL_DATABASE in $DATABASES; do
			# Start Backup
	    	echo -e " create MariaDB Backup for Database on Container:\n  * $MYSQL_DATABASE DB on $i";
	    	docker exec -e MYSQL_DATABASE=$MYSQL_DATABASE -e MYSQL_PWD=$MYSQL_PWD \
				$i /usr/bin/mariadb-dump -u root $MYSQL_DATABASE | gzip > $BACKUPDIR/$i-$MYSQL_DATABASE-$TIMESTAMP.sql.gz
		done
	else
	    echo " ERROR: cannot find dump command for container $i!"
	fi
	# dont delete last old backups!
	OLD_BACKUPS=$(ls -1 $BACKUPDIR/$i*.gz |wc -l)
	if [ $OLD_BACKUPS -gt $DAYS ]; then
		find $BACKUPDIR -name "$i*.gz" -daystart -mtime +$DAYS -delete
	fi
done
echo -e "\n$TIMESTAMP Backup for Databases completed\n"

zum Schluss das execute Recht setzten:

chmod +x /usr/local/sbin/backup-docker-mysql.sh

Das Script kann nun via

backup-docker-mysql.sh

ausgeführt werden.

Regelmäßiges Backup einrichten (cron-job)

Will man nun das Script regelmäßig, zum Beispiel täglich ausführen, reicht es einen Symlink in das cron.daily Verzeichnis zu legen:

ln -s /usr/local/sbin/backup-docker-mysql.sh /etc/cron.daily/backup-docker-mysql

Restore

Der Restore erfolgt händisch via mysql command. Die Benötigten Variablen (DB Name und Root Passwort) bekommt man einfach mit folgendem Befehl ausgeben:

docker exec CONTAINERNAME env

Mit diesen Informationen kann man dann einfach den folgenden Befehl editieren und ausführen

zcat BACKUPFILE.sql.gz | docker exec -i CONTAINERNAME /usr/bin/mysql -u root --password=ROOTPASSWORD DATABASENAME

nun sollte die Datenbank mit dem SQL Backup gefüttert sein.

GitHub Repository

Das Ganze könnt ihr auch in GitHub als komplettes Repository finden: