[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Konsistentes, dateibasiertes MySQL-Backup mit ZFS in FreeBSD-Jails
[Thread Prev] | [Thread Next]
- Subject: Konsistentes, dateibasiertes MySQL-Backup mit ZFS in FreeBSD-Jails
- From: Raphael Eiselstein <rabe@xxxxxxxxx>
- Date: Sat, 21 Dec 2013 01:45:17 +0100
- To: uugrn@xxxxxxxxxxxxxxx
Hallo zusammen, ich habe nach einer moeglichst einifachen Moeglichkeit gesucht, um moeglichst automatisiert eine offline-Kopie einer MySQL-Datenbank zu erstellen. Mit FreeBSD-Jails und ZFS hat man generell alles zusammen, allerdings muss man die Aufgabe hier ein wenig verteilen, damit der FreeBSD-Jailhost selbst nichts ueber MySQL und dessen Strukturen wissen muss. Grundannahmen: * Jedes Jail hat sein eigenes ZFS Volume, d.h. / im Jail zeigt auf ein Dataset. * MySQL unter FreeBSD arbeitet standardmaessig unter /var/db/mysql/, alle *.pid und *.err Dateien liegen ebenfalls hier. * Die Offline-Kopie soll unter /var/db/mysql.snap/ angelegt werden. Das folgende Beispiel bezieht sich auf mysql.stwserv.de (Stadtwiki e.V.) Step 1: Auf dem Jail-Server: Automatisches Erzeugen von ZFS-Snapshots beim Starten oder Stoppen eines Jails per jail(8) durch Konfiguration in jail.conf(5): .--- /etc/jail.conf ------- | swg_mysql { | name = "swg_mysql"; | host.hostname = "mysql.stwserv.de"; | path = "/jails/swg/mysql"; | ip4.addr = "164.177.171.48"; | ip4.addr += "em1|10.253.1.48"; | ip6.addr = "2a03:2500:1:6:30::"; | mount.fstab = "/jails/meta/swg/fstab.swg_mysql"; | exec.consolelog = "/var/log/jail_swg_mysql_console.log"; | exec.prestart = "/root/bin/jail_zfs_snap.sh zroot/jails/swg/mysql prestart"; | exec.poststop = "/root/bin/jail_zfs_snap.sh zroot/jails/swg/mysql poststop"; | } `---------- Relevant hier: exec.prestart und exec.poststop, der Rest sei hier nur der Vollstaendigkeit halber aufgefuehrt. Das Script erwartet genau 2 Parameter: Dataset und "tag": .--- /root/bin/jail_zfs_snap.sh ------- | #! /bin/sh | # | # zfs snapshot before jail start (precmd) / after jail stop (postcmd) | # | # Usage: /root/bin/jail_zfs_snap.sh DATASET TAG | # | | test $# -eq 2 || | { echo "ERROR: Usage: $0 DATASET TAG" >&2; exit 1; } | | DATASET="${1}" | TAG="${2:-"offline"}" | | ZFS_MNT="$(/sbin/zfs get -H mountpoint "${DATASET}")" | test $? -eq 0 || | { echo "ERROR: Cannot get mountpoint of DATASET=${DATASET}" >&2 ; exit 1; } | | | MOUNTPOINT="$( echo "${ZFS_MNT}" | tr '\t' '|' | cut -f 3 -d "|" )" | test -d "${MOUNTPOINT}" || | { echo "ERROR: MOUNTPOINT=${MOUNTPOINT} of DATASET=${DATASET} is not a directory" >&2 ; exit 1; } | | | NOW="$(/bin/date +%Y%m%d%H%M%S)" | SNAPSHOT="${DATASET}@${NOW}_${TAG}" | SNAPSHOT_DIR="${MOUNTPOINT}/.zfs/snapshot/${NOW}_${TAG}" | | /sbin/zfs snapshot "${SNAPSHOT}" | test $? -eq 0 || | { echo "ERROR: Cannot snapshot DATASET=${DATASET} to SNAPSHOT=${SNAPSHOT}" >&2; exit 1; } | | echo "Snapshotted ${SNAPSHOT}, see ${SNAPSHOT_DIR}" | `---------- Stoppe/Starte ich nun dieses Jail, dann passiert folgeneds: .---------- | [root@stwserv2 ~]# jail -r swg_mysql | swg_mysql: removed | Snapshotted zroot/jails/swg/mysql@20131221004705_poststop, see /jails/swg/mysql/.zfs/snapshot/20131221004705_poststop | [root@stwserv2 ~]# jail -c swg_mysql | Snapshotted zroot/jails/swg/mysql@20131221004708_prestart, see /jails/swg/mysql/.zfs/snapshot/20131221004708_prestart | swg_mysql: created `---------- Das Ganze noch in /etc/crontab, damit es taeglich automatisch passiert: .--- /etc/crontab ------- | 15 5 * * * root /usr/sbin/jail -rc swg_mysql `---------- Soweit muss das also auf dem Jail-Server selbst aussehen. Step 2: Im Jail suchen unter /.zfs/snapshot/* nach MySQL-Logdateien (/var/db/mysql/fqdn.err), die in der letzten Zeile die Nachricht enthalten, dass die MySQL-Datenbank (sauber) heruntergefahren wurde. Findet das Script ein solches Logfile, dann wird das Verzeichnis, in dem dieses Logfile gefunden wurde als Quelle fuer die (saubere) offline-Kopie verwendet. Metainformationen zum verwendeten Snapshot werden in /var/db/mysql.snap/SNAPSHOT festgehalten. .--- /root/bin/mysql_get_snapshot.sh ------- | #! /bin/sh | # | # 1. find most recent zfs snapshot with clean mysql shutdown | # 2. copy /var/db/mysql.snap/ from most current clean zfs snapshot | | | MYSQL_HOSTNAME="mysql.stwserv.de" | MYSQL_DATA_DIR="/var/db/mysql" | MYSQL_SNAP_DIR="/var/db/mysql.snap" | | # this message needs to be in the last line of a clean shutdown | MYSQL_LOG_LINE="mysqld_safe mysqld from pid file ${MYSQL_DATA_DIR}/${MYSQL_HOSTNAME}.pid ended" | | MYSQL_ZFS_SNAP_ERROR_FILE="" | for FILE in $(find /.zfs/snapshot/*${MYSQL_DATA_DIR}/ -name ${MYSQL_HOSTNAME}.err | sort -r) ; do | if tail -n 1 "${FILE}" | grep --silent --fixed-strings "${MYSQL_LOG_LINE}" ; then | MYSQL_ZFS_SNAP_ERROR_FILE="${FILE}" | break | fi | done | | test -n "${MYSQL_ZFS_SNAP_ERROR_FILE}" || | { echo "ERROR: No clean shutdown found in /.zfs/snapshot/*${MYSQL_DATA_DIR}/${MYSQL_HOSTNAME}.err" >&2; exit 1; } | | test -f "${MYSQL_ZFS_SNAP_ERROR_FILE}" || | { echo "ERROR: Found MYSQL_ZFS_SNAP_ERROR_FILE=${MYSQL_ZFS_SNAP_ERROR_FILE} is not a file" >&2; exit 1; } | | MYSQL_ZFS_SNAP_DIR="$(dirname "${MYSQL_ZFS_SNAP_ERROR_FILE}")" | | RSYNC_LOG="$(mktemp -t mysql_get_snapshot)" | /usr/local/bin/rsync -avHWx --itemize-changes --stats --delete "${MYSQL_ZFS_SNAP_DIR}/" "${MYSQL_SNAP_DIR}/" >"${RSYNC_LOG}" 2>"${RSYNC_LOG}" | | echo "NOW=$(date)" > "${MYSQL_SNAP_DIR}/SNAPSHOT" | echo "SRC=${MYSQL_ZFS_SNAP_DIR}/" >> "${MYSQL_SNAP_DIR}/SNAPSHOT" | echo "DST=${MYSQL_SNAP_DIR}/" >> "${MYSQL_SNAP_DIR}/SNAPSHOT" | echo "RSYNC:" >> "${MYSQL_SNAP_DIR}/SNAPSHOT" | cat "${RSYNC_LOG}" >> "${MYSQL_SNAP_DIR}/SNAPSHOT" | rm "${RSYNC_LOG}" `---------- Und damit das auch moeglichst automatisiert erfolgt, wird das script in /etc/crontab eingetragen: .--- /etc/crontab ------- | ... | @reboot root /root/bin/mysql_get_snapshot.sh | ... `---------- Zum Abschluss nochmal testen: Jail einmal neu starten: .---------- | [root@stwserv2 ~]# date; jail -r swg_mysql ; jail -c swg_mysql ; date | Sat Dec 21 01:15:04 CET 2013 | swg_mysql: removed | Snapshotted zroot/jails/swg/mysql@20131221011506_poststop, see /jails/swg/mysql/.zfs/snapshot/20131221011506_poststop | Snapshotted zroot/jails/swg/mysql@20131221011506_prestart, see /jails/swg/mysql/.zfs/snapshot/20131221011506_prestart | swg_mysql: created | Sat Dec 21 01:15:11 CET 2013 `---------- ... kurz abwarten und dann: .---------- | [root@stwserv2 ~]# head -n 3 /jails/swg/mysql/var/db/mysql.snap/SNAPSHOT | NOW=Sat Dec 21 01:16:52 CET 2013 | SRC=/.zfs/snapshot/20131221011506_prestart/var/db/mysql/ | DST=/var/db/mysql.snap/ `---------- Was soll das alles? * mit einer MySQL-Downtime von nur wenigen Sekunden (hier: 7 Sekunden) erhalten wir beim Neustart des MySQL-Jails 2 ZFS Snapshots (einer nach dem Beenden, einer vor dem Starten). * Die ZFS-Snapshots enthalten jeweils /var/db/mysql/ in einem offline-Zustand, also nach dem Herunterfahren von MySQL bzw. kurz davor. * MySQL kann auf einer solchen konsistenten Kopie sehr schnell ohne Crash Recovery starten. * Wir haben unter /var/db/mysql.snap/ die aktuellste Offline-Kopie und koennen innerhalb des Jails unmittelbar darauf zugreifen, etwa um hier ein (alternatives) MySQL zu starten, wenn /var/db/mysql/ defekt ist. * Die Offline-Kopie von MySQL kann ueber ein sehr simples dateibasiertes Backup gesichert werden, das Backup muss dabei nichts von MySQL wissen oder auf dessen Belange Ruecksicht nehmen. * Das Verfahren kann fuer alle Services in Jails angewendet werden, die viel In-Memory arbeiten und zB nur offline konsistente Daten im Dateisystem liegen haben (analog zu MySQL mit InnoDB). Wenn obiges Konstrukt fehlschlaegt erkennt man das zB daran, dass http://rhein-neckar-wiki.de/ ueber Datenbankprobleme klagt, das ist die letzten Tage leider ein paar mal passiert ;-) Kritik und Anregungen willkommen. Have fun! Raphael -- Raphael Eiselstein <rabe@xxxxxxxxx> http://rabe.uugrn.org/ xmpp:freibyter@xxxxxx | https://www.xing.com/profile/Raphael_Eiselstein PGP (alt): E7B2 1D66 3AF2 EDC7 9828 6D7A 9CDA 3E7B 10CA 9F2D PGP (neu): 4E63 5307 6F6A 036D 518D 3C4F 75EE EA14 F625 DB4E .........|.........|.........|.........|.........|.........|.........|.. -- UUGRN e.V. http://www.uugrn.org/ http://mailman.uugrn.org/mailman/listinfo/uugrn Wiki: https://wiki.uugrn.org/UUGRN:Mailingliste Archiv: http://lists.uugrn.org/