#!/bin/bash # # ============================================================ # # @license This program is free software. # # @category Script for Percona Server # @project Gedow Software # @package Gedow Percona Utils # @author GedowFather http://blog.father.gedow.net/ # @copyright gedow.net All Rights Reserved. # @version v2.2.1 # @since File available since Release 1.0.0 # @depends percona-xtrabackup >= 2.0.3 # @recommended pigz, pbzip2 # # ============================================================= # # Percona Server の Xtrabackup を用いたリストアスクリプトです # # 処理内容は # - リストア実行可否のチェック # - バックアップファイルの解凍 # - リストア準備 # - リストア実行 # - レプリケーションの開始 # # 下記 config を編集 もしくは --help で表示されるオプションを使うことで、 # 設定を上書きして実行することができます。 # # 例) /usr/local/bin/percona_restore.sh \ # -u root -p password \ # -d /fio/mysql/ -l /fio/mysql_log/ -t /fio/mysql_tmp/ \ # -f /var/tmp/mysql_backup/xtrabackup-20121001_012345.tar.gz \ # --master-host 10.0.0.1 --master-user USER --master-pass PASSWORD # # 【ロック】 # 誤実行を防ぐため、既にリストア済み、もしくはレプリケーション開始済みのサーバでは # エラーを吐いて中止されます。その場合、ロックファイルを削除してから(--unlock) # 再実行するか、または強制的に実行します(--force)。 # # 【レプリケーション】 # バックアップにbinlog情報(xtrabackup_binlog_info)が存在した状態で # オプションに --master-host, --master-user, --master-pass を全てつけると # 自動的にレプリケーションを開始します。 # # MASTERのSLAVEなのか、SLAVEのSLAVEなのか、SLAVEと同じMASTERを持つSLAVEなのか # といった配置についてはログ情報の有無により自動的に判断しますが、 # どちらにもなり得る場合は --slave-position により強制できます(設定コメント参照) # # レプリケーションが成功したかは、標準出力の最後の方の # Slave_IO_Running, Slave_SQL_Running がどちらもYesになることで確認できます。 # # 【解凍】 # バックアップファイルの拡張子が .gz なら gzip, .bz2 なら bzip2 を使って解凍しますが、 # pigz, pbzip2 コマンドが存在する場合、自動的にこちらを優先して使用します。 # 並列数は自動的に最大まで利用します。 # PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DATE_FORMAT="+%Y-%m-%d %H:%M:%S" ########################################################### # # 設定 # MYSQL_USER=root MYSQL_PASS=password BACKUP_FILE=/path/to/xtrabackup.tar.gz # バックアップファイル DATA_DIR=/fio/mysql/ # データディレクトリ(datadir) TMP_DIR=/fio/mysql_tmp # tmpディレクトリパス(tmpdir) LOG_DIR=/fio/mysql_log # ログファイルのトップディレクトリパス LOCK_FILE=/var/tmp/percona_restore.lock # リストア処理開始済みを表し、重複実行を抑制するロックファイル FORCE=No # Yesにすると、レプリケーション開始済み・ロック済みなら # リストアを中止するといった条件を無視して実行する UNLOCK=No # Yesにすると、ロックを解除して処理を終了します COMPRESS_TYPES=(gzip bzip2) # 利用可能な圧縮コマンドのリスト COMPRESS_EXTENSIONS=(gz bz2) # 圧縮形式に対応した拡張子のリスト # 上記2配列は必ずセットの値にすること COMPRESS_PARALLELS=(pigz pbzip2) # 対応する圧縮形式で、こちらのコマンドが存在する場合は優先して利用する MASTER_HOST= # MASTERのホスト MASTER_PORT=3306 # MASTERのポート MASTER_USER= # MASTERのレプリケーションユーザ MASTER_PASS= # MASTERのレプリケーションパスワード SLAVE_POSITION=serial # serial or parallel # 取得元サーバでlog-bin,relay-logのどちらが有効だったか、 # またはどちらも有効だったか、によってどこをMASTERとするか決める # # log-binの場合は取得元がMASTERとなり(直列 serial)、 # SLAVEのSLAVEを作る場合も同様に、 # [MASTER(Backup)] -> [SLAVE(Restore)] # relay-logの場合は取得元のMASTERをMASTERとする(並列 parallel) # [MASTER] -> [SLAVE(Backup)] # -> [SLAVE(Restore)] # 両方ある場合はこの設定で強制的に選択する DEBIAN_CONFIG=/etc/mysql/debian.cnf # DebianのMySQL管理者情報ファイル DEBIAN_DUMMY_NAME=debian.cnf.CSV # バックアップ一式に含ませるためデータディレクトリに入れる時の名前 DEVICE_USAGE_PERCENGATE_LIMIT=60 # 既にデバイス利用率がこの%を超えている場合エラーとなる INNODB_LOGFILE_NAME=ib_logfile # move-backでログファイルが移動しないバグ対策用 # innodb_log_group_home_dir は設定せず、DATADIR直下である前提とする # # オプション # exit_usage() { echo "Usage: this.sh [-h|--help] [-u|--user USER] [-p|--pass PASS] " echo " [-f|--file BACKUP_FILE] [-d|--data-dir DATA_DIR]" echo " [-l|--log-dir LOG_DIR] [-t|--tmp-dir TMP_DIR]" echo " [--master-host MASTER_HOST] [--master-port MASTER_PORT]" echo " [--master-user MASTER_USER] [--master-pass MASTER_PASSWORD]" echo " [--slave-position serial|parallel]" echo " [--force] [--unlock]" exit 1 } OPTSHORT="hu:p:f:d:l:t:" OPTLONG="help,user:,pass:,file:,data-dir:,log-dir:,tmp-dir:" OPTLONG+=",master-host:,master-port:,master-user:,master-pass:,slave-position:" OPTLONG+=",force,unlock,use-memory:" GETOPT=`getopt -q -o $OPTSHORT -l $OPTLONG -- "$@"` [ $? != 0 ] && exit_usage eval set -- "$GETOPT" while true do case $1 in -h|--help) exit_usage ;; -u|--user) MYSQL_USER=$2; shift 2;; -p|--pass) MYSQL_PASS=$2; shift 2;; -f|--file) BACKUP_FILE=$2; shift 2;; -d|--data-dir) DATA_DIR=$2; shift 2;; -l|--log-dir) LOG_DIR=$2; shift 2;; -t|--tmp-dir) TMP_DIR=$2; shift 2;; --master-host) MASTER_HOST=$2; shift 2;; --master-port) MASTER_PORT=$2; shift 2;; --master-user) MASTER_USER=$2; shift 2;; --master-pass) MASTER_PASS=$2; shift 2;; --slave-position) SLAVE_POSITION=$2; shift 2;; --force) FORCE=Yes; shift;; --unlock) UNLOCK=Yes; shift;; --) shift; break ;; *) exit_usage ;; esac done # # Validation # # SLAVE_POSITION [ "$SLAVE_POSITION" != "serial" -a "$SLAVE_POSITION" != "parallel" ] && exit_usage # # ファイル拡張子から解凍コマンドの選択、そして並列処理への切替 # # 配列もどき作成 count=0 for key in ${COMPRESS_EXTENSIONS[@]} do eval COMPRESS_TYPE_OF_$key=${COMPRESS_TYPES[$count]} eval COMPRESS_PARALLEL_OF_$key=${COMPRESS_PARALLELS[$count]} count=`expr $count + 1` done # 拡張子から解凍コマンドの確定 COMPRESS_EXTENSION=${BACKUP_FILE##*.} COMPRESS_TYPE=`eval echo "\\$COMPRESS_TYPE_OF_$COMPRESS_EXTENSION"` if [ -z "$COMPRESS_TYPE" ]; then echo "$COMPRESS_EXTENSION is invalid command." echo "Check COMPRESS_EXTENSIONS value." exit 1 fi # 圧縮コマンドの存在確認 type $COMPRESS_TYPE > /dev/null if [ $? -ne 0 ]; then echo "You need '$COMPRESS_TYPE' command." echo "Retry after installing $COMPRESS_TYPE package." exit 1 fi # 対応する並列圧縮コマンドがあれば切り替える COMPRESS_PARALLEL=`eval echo "\\$COMPRESS_PARALLEL_OF_$COMPRESS_EXTENSION"` if [ ! -z "$COMPRESS_PARALLEL" ]; then type $COMPRESS_PARALLEL > /dev/null if [ $? -eq 0 ]; then COMPRESS_TYPE="$COMPRESS_PARALLEL" echo "COMPRESS_TYPE is changed to $COMPRESS_TYPE." echo fi fi # # アーカイブ形式(tar|xbstream)の取得 # archive_path=${BACKUP_FILE%.*} ARCHIVE_TYPE=${archive_path##*.} if [ "$ARCHIVE_TYPE" != "tar" -a "$ARCHIVE_TYPE" != "xbstream" ]; then echo "ARCHIVE_TYPE is invalid value." echo "Backup file name must be .(tar|xbstream).(gz|bzip2)$" exit 1 fi # # 変数 # WORK_DIR=`dirname $BACKUP_FILE`/work MYSQL_OPTION="--user=$MYSQL_USER --password=$MYSQL_PASS" LOG_BIN_DIR=$LOG_DIR/bin # バイナリログの保存ディレクトリ LOG_RELAY_DIR=$LOG_DIR/relay # リレーログの保存ディレクトリ LOG_SYSTEM_DIR=$LOG_DIR/system # エラーログやスロークエリの保存ディレクトリ ########################################################### # # 関数 # # # 第1引数にフィールド名を入力するとSLAVE STATUSの値を返す # get_slave_status () { echo 'SHOW SLAVE STATUS \G' | mysql $MYSQL_OPTION | \ grep "$1:" | sed -e 's/^ *//g' -e 's/: */:/g' | awk -F: '{print $2}' } # # 空きメモリ容量の取得(単位:MB) # get_free_memory () { local MemFree=` cat /proc/meminfo | grep MemFree: | awk '{print $2}'` local Inactive=`cat /proc/meminfo | grep Inactive: | awk '{print $2}'` expr \( $MemFree + $Inactive \) / 1024 } ########################################################### # # 開始表示 # echo `date "$DATE_FORMAT"`" starting restore." ########################################################### # # --unlock によるロック解除処理 # # # ロックファイルの削除だけを行って終了 # if [ "$UNLOCK" = "Yes" ]; then if [ -f $LOCK_FILE ]; then rm $LOCK_FILE fi echo "unlock!! you can now restore." exit 0 fi ########################################################### # # リストアの可否をチェック # # # バックアップファイルが存在するか # if [ ! -f "$BACKUP_FILE" ]; then echo "Could not find $BACKUP_FILE" echo "or no input -f|--file BACKUP_FILE." exit_usage exit 1 fi # # 自動的にレプリケーションを開始する場合、 # 必須の3つの値が入力されていること # (条件説明:1つ以上入力されているのに3つ揃っていない) # if [ -z "$MASTER_HOST" -o -z "$MASTER_USER" -o -z "$MASTER_PASS" ]; then if ! [ -z "$MASTER_HOST" -a -z "$MASTER_USER" -a -z "$MASTER_PASS" ]; then echo "for starting replication, please specify 3 options" echo " --master-host, --master-user, --master-pass" echo exit_usage fi fi # # リストア済み(ロックファイルが存在する)の場合、エラーとして終了する。 # --force をつけると無視される。 # 主に自動SLAVE化の重複実行回避のため。 # if [ "$FORCE" = "No" -a -f "$LOCK_FILE" ]; then echo "On this server, restore have already done." echo "If you want to restore on this server, add --force option or --unlock once." echo "aborted restore." exit 1 fi # # 既にレプリケーションが開始していた場合、運用中と判断して終了する # --force をつけると無視される。 # ps -C mysqld --no-heading run_mysql=$? if [ "$FORCE" = "No" -a $run_mysql -eq 0 ]; then Slave_IO_Running=` get_slave_status Slave_IO_Running` Slave_SQL_Running=`get_slave_status Slave_SQL_Running` if [ "$Slave_IO_Running" = "Yes" -o "$Slave_SQL_Running" = "Yes" ]; then echo "This server is already starting replication." echo "aborted restore." exit 1 fi fi # # データディレクトリのパーティション容量%が設定値を超えているとエラー # for row in `df -B1 -P | grep ^/ | awk '{ print $6 ":" $5 }' | sort -r` do partition=` echo "$row" | awk -F: '{ print $1 }'` percentage=`echo "$row" | awk -F: '{ print $2 }' | sed -e 's/%//'` if expr "$DATA_DIR" : "^$partition" >/dev/null; then break; fi done if [ $percentage -ge $DEVICE_USAGE_PERCENGATE_LIMIT ]; then echo "$partition usage percentage is over ${DEVICE_USAGE_PERCENGATE_LIMIT}%." echo "Please try again to delete unnecessary files." exit 1 fi ########################################################### # # ロックファイルの作成 # touch $LOCK_FILE if [ $? != 0 ]; then echo "Could not create $LOCK_FILE." exit 1 fi ########################################################### # # リストア準備 # # # バックアップファイルの解凍 # echo `date "$DATE_FORMAT"`" uncompress $BACKUP_FILE to $WORK_DIR" [ -d "$WORK_DIR" ] && rm -R $WORK_DIR mkdir -p $WORK_DIR cd $WORK_DIR case $ARCHIVE_TYPE in tar) tar ixf $BACKUP_FILE -C $WORK_DIR --use-compress-program $COMPRESS_TYPE ;; xbstream) $COMPRESS_TYPE -cd $BACKUP_FILE | xbstream -x ;; *) echo "$file_name is unknown file type." echo "you can specify .(tar|xbstream).(gz|bzip2)" exit 1 ;; esac # # prepareの実行 # # 空きメモリの半分を使用する USE_MEMORY=`eval "expr \`get_free_memory\` / 2"`M innobackupex --user=$MYSQL_USER --password=$MYSQL_PASS \ --apply-log --use-memory=$USE_MEMORY \ $WORK_DIR ########################################################### # # リストア実行 # # # MySQLが起動していたら停止し、データディレクトリを退避 # ps -C mysqld --no-heading && /etc/init.d/mysql stop if [ -d "$DATA_DIR" ]; then MV_PATH=`dirname $DATA_DIR`"/_mysql_data~"`date +%Y%m%d_%H%M%S` mv $DATA_DIR $MV_PATH echo echo "moved original datadir to $MV_PATH" fi # # データディレクトリとログディレクトリを作成 # mkdir -p -m 0700 $DATA_DIR $TMP_DIR mkdir -p $LOG_BIN_DIR $LOG_RELAY_DIR $LOG_SYSTEM_DIR chown -R mysql:mysql $DATA_DIR $LOG_DIR $TMP_DIR # # データをデータディレクトリに戻す # innobackupex --move-back $WORK_DIR # INNODBログファイルが移動しないバグ対策 if [ -f "${WORK_DIR}/${INNODB_LOGFILE_NAME}0" ]; then mv ${WORK_DIR}/${INNODB_LOGFILE_NAME}* $DATA_DIR/ fi # 権限調整 chown -R mysql:mysql $DATA_DIR find $DATA_DIR -type f -exec chmod 0660 {} \; find $DATA_DIR -type d -exec chmod 0700 {} \; # # バイナリログ情報がある場合はデータディレクトリにコピーしておく # - xtrabackup_binlog_pos_innodb はCHANGE MASTERには使えないので注意 # if [ -f "$WORK_DIR/xtrabackup_binlog_info" ]; then cp $WORK_DIR/xtrabackup_binlog_info $DATA_DIR/ fi # # Debianの場合 # debian.cnf を取り出し、設定置き場に戻す # debian_dummy_file=$DATA_DIR/mysql/$DEBIAN_DUMMY_NAME if [ -f "$debian_dummy_file" ]; then mv $DEBIAN_CONFIG ${DEBIAN_CONFIG}~orig-`date "+%Y%m%d_%H%M%S"` mv $debian_dummy_file $DEBIAN_CONFIG fi # # MySQLを起動 # InnoDBログファイルができて、DB接続できるまで待機 # /etc/init.d/mysql start echo -n "waiting for success connection (esp until making innodb log files)." count=1 until=60 while [ $count -lt $until ] do output=`mysqladmin --connect_timeout=1 $MYSQL_OPTION ping 2> /dev/null` result=`echo "$output" | grep alive` if [ $? = 0 ]; then break; fi count=`expr $count + 1` echo -n " ." sleep 1 done echo " connecting!!" rm -R $WORK_DIR ########################################################### # # レプリケーション開始 # # # バイナリログ情報ファイル # BINLOG_INFO=${DATA_DIR}/xtrabackup_binlog_info SLAVE_INFO=${DATA_DIR}/xtrabackup_slave_info # # バイナリログ情報の有効無効判断 # ACTIVE_BINLOG_INFO=0 if [ -f $BINLOG_INFO ]; then grep "[0-9]" $BINLOG_INFO > /dev/null 2>&1 [ $? -eq 0 ] && ACTIVE_BINLOG_INFO=1 fi ACTIVE_SLAVE_INFO=0 if [ -f $SLAVE_INFO ]; then grep "[0-9]" $SLAVE_INFO > /dev/null 2>&1 [ $? -eq 0 ] && ACTIVE_SLAVE_INFO=1 fi # # レプリケーションを開始する必要が無ければ終了する # OK_LOG_INFO=0 OK_MASTER=0 [ $ACTIVE_BINLOG_INFO -eq 1 -o $ACTIVE_SLAVE_INFO -eq 1 ] && OK_LOG_INFO=1 [ ! -z "$MASTER_HOST" -a ! -z "$MASTER_USER" -a ! -z "$MASTER_PASS" ] && OK_MASTER=1 if [ $OK_LOG_INFO -eq 0 -o $OK_MASTER -eq 0 ]; then echo echo "see also MASTER STATUS $BINLOG_INFO" echo "see also SLAVE STATUS $SLAVE_INFO" echo echo `date "$DATE_FORMAT"`" finished restore." echo exit 0 fi # # バックアップ条件ごとにMASTERデータ取得方法を変更 # - 両方とも有効だった場合はオプションで強制選択 # (指定が無い場合は取得元の下につく直列型とする) # - log-binのみ有効だった # - relay-logのみ有効だった # if [ $ACTIVE_BINLOG_INFO -eq 1 -a $ACTIVE_SLAVE_INFO -eq 1 ]; then LOG_TYPE=$SLAVE_POSITION elif [ $ACTIVE_BINLOG_INFO -eq 1 ]; then LOG_TYPE=serial else LOG_TYPE=parallel fi # 条件別にクエリ作成 if [ $LOG_TYPE = "serial" ]; then # バックアップを取得したサーバのバイナリログ位置を取得 MASTER_LOG_FILE=`eval "basename \`cat $BINLOG_INFO | awk '{print $1'}\`"` MASTER_LOG_POS=` cat $BINLOG_INFO | awk '{print $2'}` # SLAVE用に合わせて途中までクエリ作成 query="CHANGE MASTER TO MASTER_LOG_FILE='$MASTER_LOG_FILE', MASTER_LOG_POS=$MASTER_LOG_POS" else # SLAVE parallel用 query=`cat $SLAVE_INFO` fi # 完成クエリ query+=", MASTER_HOST='$MASTER_HOST', MASTER_PORT=$MASTER_PORT, MASTER_USER='$MASTER_USER', MASTER_PASSWORD='$MASTER_PASS'; START SLAVE;" # # レプリケーション開始 # result=`echo "$query" | mysql $MYSQL_OPTION` sleep 1 # SLAVEステータスをチェック Slave_IO_Running=` get_slave_status Slave_IO_Running` Slave_SQL_Running=`get_slave_status Slave_SQL_Running` echo echo "START SLAVE ... statuses are" echo " * Slave_IO_Running : $Slave_IO_Running" echo " * Slave_SQL_Running: $Slave_SQL_Running" echo if [ "$Slave_IO_Running" != "Yes" -o "$Slave_SQL_Running" != "Yes" ]; then Last_IO_Error=`get_slave_status Last_IO_Error` echo "Replication failed !! error message is ..." echo " * $Last_IO_Error" exit 1 fi ########################################################### # # 終了表示 # echo echo `date "$DATE_FORMAT"`" finished restore." echo exit 0