[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/