Бекап баз данных – есть ли он?

    image

    Нет смысла говорить о том, насколько это актуальный вопрос. Сегодня мы расскажем, как у нас организовано резервное копирование баз данных mysql.
    И одно их самых важных – это проверка, а сделался ли бекап? А успешно ли прошел дамп? А были ли ошибки? А знаю ли я о них?

    По-моему, сегодня вряд ли можно найти того, кто не делает бекап. Но вот проверять наличие ошибок при выполнении бекапа, пожалуй, попробую рассказать вам как это делаем мы.

    Сразу оговорюсь, что, возможно, в принципе реализации использованы стандартные схемы, но, возможно, вы найдете для себя, что-то новое, что сможете внедрить у себя.

    Опишем переменные и пути:

    PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/mysql/bin
    DATE=`date +%Y-%m-%d_%Hh%Mm`                            # Datestamp e.g 2002-09-21
    DOW=`date +%A`                                                  # Day of the week e.g. Monday
    DNOW=`date +%u`                                         # Day number of the week 1 to 7 where 1 represents Monday
    DOM=`date +%d`                                                  # Date of the Month e.g. 27
    M=`date +%B`                                                    # Month e.g January
    W=`date +%V`                                                    # Week Number e.g 37
    VER=2.5                                                                 # Version Number
    LOGFILE=$BACKUPDIR/$DBHOST-`date +%N`.log               # Logfile Name
    LOGERR=$BACKUPDIR/ERRORS_$DBHOST-`date +%N`.log         # Logfile Name
    BACKUPFILES=""
    OPT="--quote-names --opt --routines --single-transaction --events"      # OPT string for use with mysqldump ( see man mysqldump )
    DBEXCLUDE+=" information_schema performance_schema"
    LOCATION="$(cd -P -- "$(dirname -- "$0")" && pwd -P)/.."
    


    Для всех серверов скрипт один, но куда же без файла настроек. Зачастую есть необходимость не только в глобальных настройках, но и в специфических для отдельного проекта. Мы решили это разнести на несколько файлов.

    mysql-backup.conf.dist — файл хранит глобальные настройки, общие для всех проектов

    mysql-backup.conf – файл для специфических настроек для проекта

    mysql-backup.local.conf – файл для настроек локального бекапа

    Проверим, если есть конфиг — будем делать бекап на этом сервере, и прочитаем наши конфиги:

    if [ -f "$LOCATION/etc/mysql-backup.conf.dist" ]; then
        . "$LOCATION/etc/mysql-backup.conf.dist"
        if [ -f "$LOCATION/etc/mysql-backup.conf" ]; then
            . "$LOCATION/etc/mysql-backup.conf"
        fi
        if [ -f "$LOCATION/etc/mysql-backup.local.conf" ]; then
            . "$LOCATION/etc/mysql-backup.local.conf"
        fi
    else
        echo "mysql-backup.conf.dist not found"
        exit 0
    fi
    


    Далее пойдём читать опции конфига и устанавливать значения

    # Add --compress mysqldump option to $OPT
    if [ "$COMMCOMP" = "yes" ];
            then
                    OPT="$OPT --compress"
            fi
    
     # Add --compress mysqldump option to $OPT
    if [ "$MAX_ALLOWED_PACKET" ];
            then
                    OPT="$OPT --max_allowed_packet=$MAX_ALLOWED_PACKET"
            fi 
    if [ ! "$BACKUP_DAYS" ];
            then
                    BACKUP_DAYS=7
            fi 
    if [ ! "$BACKUP_MONTH" ];
            then
                    BACKUP_MONTH=4
            fi
    if [ ! "$DO_SQL_DUMP" ];
            then
                    DO_SQL_DUMP="yes"
            fi
     if [ ! "$DO_HOT_BACKUP" ];
           then
                    DO_HOT_BACKUP="no"
            fi
    


    Думаю тут не нужно подробно описывать, т.к. названия переменных должны сами всё говорить за себя.

    Проверим и создадим директории для наших бекапов

    # Create required directories
    if [ ! -e "/var/lib/mysql.backup" ]             # Check Backup Directory exists.
            then
                mkdir -p "/var/lib/mysql.backup"
    fi
    if [ ! -e "$BACKUPDIR" ]                # Check Backup Directory exists.
            then
            mkdir -p "$BACKUPDIR"
    fi
    if [ ! -e "$BACKUPDIR/daily" ]          # Check Daily Directory exists.
            then
            mkdir -p "$BACKUPDIR/daily"
    fi
    if [ ! -e "$BACKUPDIR/weekly" ]         # Check Weekly Directory exists.
            then
            mkdir -p "$BACKUPDIR/weekly"
    fi
    if [ ! -e "$BACKUPDIR/monthly" ]        # Check Monthly Directory exists.
            then
            mkdir -p "$BACKUPDIR/monthly"
    fi
    if [ "$LATEST" = "yes" ]
    then
            if [ ! -e "$BACKUPDIR/latest" ] # Check Latest Directory exists.
            then
                    mkdir -p "$BACKUPDIR/latest"
            fi
    eval rm -fv "$BACKUPDIR/latest/*"
    fi
    


    Один из важных моментов, не только сделать бекап, но и проверить ошибки в процессе его выполнения.

    # IO redirection for logging.
    touch $LOGFILE
    exec 6>&1           # Link file descriptor #6 with stdout.
                        # Saves stdout.
    exec > $LOGFILE     # stdout replaced with file $LOGFILE.
    touch $LOGERR
    exec 7>&2           # Link file descriptor #7 with stderr.
                        # Saves stderr.
    exec 2> $LOGERR     # stderr replaced with file $LOGERR.
    echo $LOCATION
    


    Опишем наши фукции. Т.к. для разных проектов нужен разный бекап, кому инкреметный, у кого пользователи ночью спят — достаточно дампа ночного.

    Обычный mysqldump

    # Database dump function
    dbdump () {
        if [ "$SEPTABLE" = "yes" ]; then
            TABLENAMES="`mysql --user=$USERNAME --password=$PASSWORD --host=$DBHOST --batch --skip-column-names -e "show tables" $1| sed 's/ /%/g'`"
            for TABLENAME in $TABLENAMES ; do
               OUTFILENAME=`echo $2 | sed "s~$3~$3.$TABLENAME~"`
               mysqldump --user=$USERNAME --password=$PASSWORD --host=$DBHOST $OPT $1 $TABLENAME > $OUTFILENAME
            done
            mysqldump --user=$USERNAME --password=$PASSWORD --host=$DBHOST $OPT --no-data $1 > $2
        else
            mysqldump --user=$USERNAME --password=$PASSWORD --host=$DBHOST $OPT $1 > $2
        fi
        return 0
    }
    


    С использованием mydumper

    # Database dump function
    dbdump_mydumper () {
        MYOPT=""
            # Add --ignore-table options to $MYOPT 
            if [ -n "$TABLEEXCLUDE" ]; then
                for table in $TABLEEXCLUDE ; do
                    MYOPT="${MYOPT}${table}|"
                done
            fi
            if [ -n "$DBEXCLUDE" ]; then
                for table in $DBEXCLUDE ; do
                    MYOPT="${MYOPT}${table}|"
                done
            fi
            if [ -n "$MYOPT" ]; then
                MYOPT="--regex '^(?!(${MYOPT}mysql.noptable))'"
            fi
        eval mydumper --user $USERNAME --password $PASSWORD -c -l 300 --kill-long-queries -s 500000 $MYOPT -o $1
        if [ $? -gt 0 ]; then
              echo "Error in mydumper backup stage" >&2
              mysql -u root -p`cat /root/.mysql` -e "SHOW FULL PROCESSLIST" | sort -n -k 6 >&2
        fi
        /usr/bin/find "$BACKUPDIR/daily" -name "_mydumper*" -type d -mtime +$BACKUP_DAYS -print0 | xargs -0 rm -rf
        /usr/bin/find "$BACKUPDIR/weekly" -name "_mydumper*" -type d -mtime +35 -print0 | xargs -0 rm -rf
        /usr/bin/find "$BACKUPDIR/monthly" -name "_mydumper*" -type d -mtime +100 -print0 | xargs -0 rm -rf
        return 0
    }
    


    Обычное копирование данных, естественно c блокировкой таблиц

    dbdump_h () {
        echo First rsync started at `date`
        rsync -avH --delete --numeric-ids /var/lib/mysql/ /var/lib/mysql.backup
        echo First rsync finished at `date`
        echo DB locked, second rsync started at `date`
        echo "FLUSH TABLES WITH READ LOCK" | mysql --user=$USERNAME --password=$PASSWORD --host=$DBHOST
        rsync -avH --delete --numeric-ids /var/lib/mysql/ /var/lib/mysql.backup
        echo "UNLOCK TABLES" | mysql --user=$USERNAME --password=$PASSWORD --host=$DBHOST
        echo DB unlocked, second rsync finished at `date`
    return 0
    }
    


    И куда же без него, всем нам известный инкрементный бекап

    dbdump_h_xtra () {
        if [ ! -f /usr/bin/innobackupex ]; then
            yum -y install xtrabackup.x86_64
        fi
        if [ ! -f /var/lib/mysql-xtra/xtrabackup_checkpoints ]; then
            echo Full backup stage started at `date`
            #ionice -c3
            /usr/bin/innobackupex --defaults-file=/etc/my.cnf --password=`cat /root/.mysql` --no-timestamp  --throttle=40 --rsync /var/lib/mysql-xtra 2>&1
            if [ $? -gt 0 ]; then
              echo "Error in full backup stage" >&2
            fi
            #ionice -c3
            /usr/bin/innobackupex --apply-log --redo-only --defaults-file=/etc/my.cnf --password=`cat /root/.mysql` --no-timestamp  --throttle=40 /var/lib/mysql-xtra 2>&1
            if [ $? -gt 0 ]; then
              echo "Error in apply log redo stage" >&2
            fi
            echo Full backup stage finished at `date` code $?
        else
            echo INC backup stage started at `date` code $?
            rm -rf /var/lib/mysql-xtra-inc
            #ionice -c3
            /usr/bin/innobackupex --defaults-file=/etc/my.cnf --password=`cat /root/.mysql` --no-timestamp  --throttle=40  --rsync --incremental /var/lib/mysql-xtra-inc --incremental-basedir=/var/lib/mysql-xtra 2>&1
            if [ $? -gt 0 ]; then
              echo "Error in inc stage" >&2
            fi
            #ionice -c3
            /usr/bin/innobackupex --defaults-file=/etc/my.cnf --password=`cat /root/.mysql` --no-timestamp  --throttle=40 --apply-log /var/lib/mysql-xtra --incremental-dir=/var/lib/mysql-xtra-inc 2>&1
            if [ $? -gt 0 ]; then
              echo "Error in inc apply stage" >&2
            fi
            /usr/bin/rsync -vaH --delete -f "+ */" -f "+ */**" -f "- *" /var/lib/mysql-xtra-inc/ /var/lib/mysql-xtra
            rm -rf /var/lib/mysql-xtra-inc
            echo INC backup stage finished at `date` code $?
        fi
    return 0
    


    Пожалуй и фукцию компресии вынесем отдельно. Для чего? — Феншуй. Неизвестно настроение клиента, и какую он захочет компрессию. Дадим ему право выбрать желаемый тип.

    # Compression function plus latest copy
    SUFFIX=""
    compression () {
        if [ "$SEPTABLE" = "yes" ]; then
            TBDIR=`/usr/bin/dirname $2`
            TFNAME=`/bin/basename $2`
            if [ "$COMP" = "gzip" ]; then
                TPWD=`pwd`
                cd $TBDIR
                tar -czvf "$1.tgz" ${TFNAME}*.sql 2>&1
                cd $TPWD
                SUFFIX=".tgz"
            elif [ "$COMP" = "bzip2" ]; then
                TPWD=`pwd`
                cd $TBDIR
                tar -cjvf "$1.tbz2" ${TFNAME}*.sql 2>&1
                cd $TPWD
                SUFFIX=".tbz2"
            fi
            rm -f ${2}*.sql
        else
            if [ "$COMP" = "gzip" ]; then
                gzip -f "$1"
                echo
                echo Backup Information for "$1"
                gzip -l "$1.gz"
                SUFFIX=".gz"
            elif [ "$COMP" = "bzip2" ]; then
            echo Compression information for "$1.bz2"
            bzip2 -f -v $1 2>&1
            SUFFIX=".bz2"
        else
            echo "No compression option set, check advanced settings"
        fi
    fi
        if [ "$LATEST" = "yes" ]; then
            cp $1$SUFFIX "$BACKUPDIR/latest/"
        fi
        return 0
    }
    
    # Compression function plus latest copy
    SUFFIX=""
    compression_h () {
        if [ "$COMP" = "gzip" ]; then
            TPWD=`pwd`
            cd /var/lib/mysql.backup
            tar -czvf "$1.tgz" . 2>&1
            cd $TPWD
            SUFFIX=".tgz"
        elif [ "$COMP" = "bzip2" ]; then
            TPWD=`pwd`
            cd /var/lib/mysql.backup
            tar -cjvf "$1.tbz2" . 2>&1
            cd $TPWD
            SUFFIX=".tbz2"
        else
            echo "No compression option set, check advanced settings"
        fi
        if [ "$LATEST" = "yes" ]; then
            cp $1$SUFFIX_H "$BACKUPDIR/latest/"
        fi
        return 0
    }
    


    Опишем ротацию. Опять же потребности в сроке хранения бывают разные.

    ## rotates monthly backups, set 'keep' to the last n backups to keep
    rotateMonthly () {
    mdbdir="$1"
    ## set to the number of monthly backups to keep
    keep=$BACKUP_MONTH
    (cd ${mdbdir}
        totalFilesCount=`/bin/ls -1 | wc -l`
        if [ ${totalFilesCount} -gt ${keep} ]; then
            purgeFilesCount=`expr ${totalFilesCount} - ${keep}`
            purgeFilesList=`/bin/ls -1tr | head -${purgeFilesCount}`
            echo ""
            echo "Rotating monthly: Purging in ${mdbdir}"
            rm -fv ${purgeFilesList} | sed -e 's/^//g'
        fi
    )
    }
    


    Бывает необходимо перед началом запуска бекапа выполнить команду. Приостановить сервис, показать пользователям сообщение о работах. Сообщить поисковикам и дампе огромной базы. Предоставим такую возможность.

    # Run command before we begin
    if [ "$PREBACKUP" ]
            then
            echo ======================================================================
            echo "Prebackup command output."
            echo
            eval $PREBACKUP
            echo
            echo ======================================================================
            echo
    fi
    


    Порой не все таблицы нужно бекапить.

    # Add --ignore-table options to $OPT
    if [ -n "$TABLEEXCLUDE" ]; then
            for table in $TABLEEXCLUDE ; do
                    OPT="${OPT} --ignore-table=${table}"
            done
    fi
    


    Думаю, эта часть в описании не нуждается

    if [ "$SEPDIR" = "yes" ]; then # Check if CREATE DATABSE should be included in Dump
            if [ "$CREATE_DATABASE" = "no" ]; then
                    OPT="$OPT --no-create-db"
            else
                    OPT="$OPT --databases"
            fi
    else
            OPT="$OPT --databases"
    fi
    # Hostname for LOG information
    if [ "$DBHOST" = "localhost" ]; then
            HOST=`hostname`
            if [ "$SOCKET" ]; then
                    OPT="$OPT --socket=$SOCKET"
            fi
    else
            HOST=$DBHOST
    fi
    


    Прочитаем все базы и удалим из списка для бекапа базы исключения

    # If backing up all DBs on the server
    if [ "$DBNAMES" = "all" ]; then
            DBNAMES="`mysql --user=$USERNAME --password=$PASSWORD --host=$DBHOST --batch --skip-column-names -e "show databases"| sed 's/ /%/g'`"
            # If DBs are excluded
            for exclude in $DBEXCLUDE
            do
                    DBNAMES=`echo $DBNAMES | sed "s/\b$exclude\b//g"`
            done
            MDBNAMES=$DBNAMES
    fi
    


    Пора приступить самому бекапу

    echo ======================================================================
    echo AutoMySQLBackup VER $VER
    echo http://sourceforge.net/projects/automysqlbackup/
    echo
    echo Backup of Database Server - $HOST
    echo ======================================================================
    


    Нам нужны дампы? Приступим к дампам

    if [ "$DO_SQL_DUMP" = "yes" ]; then
    echo Backup Start Time `date`
    echo ======================================================================
    


    Также по феншую удобно разделять временные типы бекапа, ну что ж сегодня первое число? Сделаем месячный дамп.

     # Monthly Full Backup of all Databases
           if [ $DOM = "01" ]; then
                    for MDB in $MDBNAMES
                    do
                             # Prepare $DB for using
                            MDB="`echo $MDB | sed 's/%/ /g'`"
                            if [ ! -e "$BACKUPDIR/monthly/$MDB" ]           # Check Monthly DB Directory exists.
                            then
                                    mkdir -p "$BACKUPDIR/monthly/$MDB"
                            fi
                            echo Monthly Backup of $MDB...
                                    dbdump "$MDB" "$BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql" "$BACKUPDIR/monthly/$MDB/${MDB}"
                                    compression "$BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql" "$BACKUPDIR/monthly/$MDB/${MDB}"
                                    BACKUPFILES="$BACKUPFILES $BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql$SUFFIX"
                            echo ----------------------------------------------------------------------
                            TTT=`expr 33 \* $BACKUP_MONTH`
                            /usr/bin/find "$BACKUPDIR/monthly/$MDB" -name "*.sql.*" -mtime +$TTT -type f -delete
                    done
    


    Не первое число месяца? – приступаем к повседневно-ежедневному бекапу. Преполагаю, что построчное описание не требуется.

    else
                    for DB in $DBNAMES
                    do
                    # Prepare $DB for using
                    DB="`echo $DB | sed 's/%/ /g'`"
                    # Create Seperate directory for each DB
                    if [ ! -e "$BACKUPDIR/daily/$DB" ]              # Check Daily DB Directory exists.
                    then
                            mkdir -p "$BACKUPDIR/daily/$DB"
                    fi
                    if [ $BACKUP_DAYS -le 7 ]; then
                        if [ ! -e "$BACKUPDIR/weekly/$DB" ]         # Check Weekly DB Directory exists.
                        then
                            mkdir -p "$BACKUPDIR/weekly/$DB"
                        fi
                        # Weekly Backup
                    fi
                    if [ $DNOW = $DOWEEKLY -a $BACKUP_DAYS -le 7 ]; then
                        echo Weekly Backup of Database \( $DB \)
                        echo Rotating 5 weeks Backups...
                        if [ "$W" -le 05 ];then
                                    REMW=`expr 48 + $W`
                            elif [ "$W" -lt 15 ];then
                                    REMW=0`expr $W - 5`
                            else
                                    REMW=`expr $W - 5`
                        fi
                        eval rm -fv "$BACKUPDIR/weekly/$DB/${DB}_week.$REMW.*"
     echo
                            dbdump "$DB" "$BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql" "$BACKUPDIR/weekly/$DB/${DB}"
                            compression "$BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql" "$BACKUPDIR/weekly/$DB/${DB}"
                            BACKUPFILES="$BACKUPFILES $BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql$SUFFIX"
                        echo ----------------------------------------------------------------------
                    # Daily Backup
                    else
                        echo Daily Backup of Database \( $DB \)
                        echo Rotating last weeks Backup...
                        /usr/bin/find "$BACKUPDIR/daily/$DB" -name "*.sql.*" -mtime +$BACKUP_DAYS -delete
                        echo
                            dbdump "$DB" "$BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql" "$BACKUPDIR/daily/$DB/${DB}"
                            compression "$BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql" "$BACKUPDIR/daily/$DB/${DB}"
                            BACKUPFILES="$BACKUPFILES $BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql$SUFFIX"
                        echo ----------------------------------------------------------------------
                    fi
                    done
            fi
    echo Backup End `date`
    echo ======================================================================
    fi
    


    Нам нужен HOT BACKUP? Приступим:

    #### HOT BACKUP
    if [ "$DO_HOT_BACKUP" = "yes" ]; then
        echo HOT Backup Start `date`
        echo ======================================================================
        # Monthly HOT Full Backup of all Databases
        if [ $DOM = "01" ]; then
            echo Monthly full Backup of \( $MDBNAMES \)...
            dbdump_h "$MDBNAMES" "$BACKUPDIR/monthly/$DATE.$M.all-databases.sql"
    ###        compression_h "$BACKUPDIR/monthly/$DATE.$M.all-databases.sql"
            BACKUPFILES="$BACKUPFILES $BACKUPDIR/monthly/$DATE.$M.all-databases.sql$SUFFIX"
            echo ----------------------------------------------------------------------
    ###     TTT=`expr 33 \* $BACKUP_MONTH`
    ###     /usr/bin/find "$BACKUPDIR/monthly/" -name "*.all-databases.sql.*" -mtime +$TTT -type f -delete
        else
    # Weekly Backup
        if [ $DNOW = $DOWEEKLY -a $BACKUP_DAYS -le 7 ]; then
            echo Weekly Backup of Databases \( $DBNAMES \)
            echo
            echo Rotating 5 weeks Backups...
            if [ "$W" -le 05 ];then
                    REMW=`expr 48 + $W`
            elif [ "$W" -lt 15 ];then
                    REMW=0`expr $W - 5`
            else
                    REMW=`expr $W - 5`
            fi
            eval rm -fv "$BACKUPDIR/weekly/week.$REMW.*"
            echo
            dbdump_h "$DBNAMES" "$BACKUPDIR/weekly/week.$W.$DATE.sql"
    ###        compression_h "$BACKUPDIR/weekly/week.$W.$DATE.sql"
            BACKUPFILES="$BACKUPFILES $BACKUPDIR/weekly/week.$W.$DATE.sql$SUFFIX"
            echo ----------------------------------------------------------------------
    # Daily Backup
    else
            echo Daily Backup of Databases \( $DBNAMES \)
            echo
            echo Rotating last weeks Backup...
            /usr/bin/find "$BACKUPDIR/daily/$DB" -name "*.sql.*" -mtime +$BACKUP_DAYS -delete
            echo
            dbdump_h "$DBNAMES" "$BACKUPDIR/daily/$DATE.$DOW.sql"
    ###        compression_h "$BACKUPDIR/daily/$DATE.$DOW.sql"
            BACKUPFILES="$BACKUPFILES $BACKUPDIR/daily/$DATE.$DOW.sql$SUFFIX"
            echo ----------------------------------------------------------------------
        fi
        fi
        echo Backup End Time `date`
        echo ======================================================================
    fi
    


    HOT XTRA BACKUP? — сделаем его:

    #### HOT XTRA BACKUP
    if [ "$HOT_XTRA_BACKUP" = "yes" ]; then
        dbdump_h_xtra
    fi
    

    И последний вариант — mydumper:

    #### DO_MYDUMPER_BACKUP
    if [ "$DO_MYDUMPER_BACKUP" = "yes" ]; then
        # Monthly Full Backup of all Databases
        BACKUPDIRM=$BACKUPDIR/daily/
        if [ $DOM = "01" ]; then
           BACKUPDIRM=$BACKUPDIR/monthly/
        else
          if [ $DNOW = $DOWEEKLY -a $BACKUP_DAYS -le 7 ]; then
            BACKUPDIRM=$BACKUPDIR/weekly/
          fi
        fi
        BACKUPDIRM=${BACKUPDIRM}_mydumper-`date +%F_%R`
        dbdump_mydumper "$BACKUPDIRM"
        OPT="$OPT --no-data"
        for DB in $DBNAMES
        do
            # Prepare $DB for using
            DB="`echo $DB | sed 's/%/ /g'`"
            dbdump "$DB" "$BACKUPDIRM/$DB-schema.sql"
            compression "$BACKUPDIRM/$DB-schema.sql"
        done
    fi
    


    Посчитаем сколько занимают дампы, место имеет привычку заканчиваться.

    echo Total disk space used for backup storage..
    echo Size - Location
    echo `du -hs "$BACKUPDIR"`
    echo
    


    Порой бывает нужно выполнить команду, после бекапа, например вновь открыть сайт. Предоставим такую возможность:

    # Run command when we're done
    if [ "$POSTBACKUP" ]
            then
            echo ======================================================================
            echo "Postbackup command output."
            echo
            eval $POSTBACKUP
            echo
            echo ======================================================================
    fi
    


    Приступим к анализу лога бекапа

    #Clean up IO redirection
    exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.
    exec 1>&7 7>&-      # Restore stdout and close file descriptor #7.
    


    Да, мы знаем, спасибо. Удалим это из нашего лога.

    sed -i '/Using a password on the command line interface can be insecure/d' $LOGERR
    


    Далее у нас несколько вариантов: прислать лог работы скрипта, прислать лог и файлы дампа, показать вывод в консоль (отладка, куда же без неё), прислать на почту лог и если есть ошибки.

    Пожалуй в дефолте мы выбираем последний вариант. Но опишем все.

    if [ "$MAILCONTENT" = "files" ]
    then
            if [ -s "$LOGERR" ]
            then
                    # Include error log if is larger than zero.
                    BACKUPFILES="$BACKUPFILES $LOGERR"
                    ERRORNOTE="WARNING: Error Reported - "
            fi
            #Get backup size
            ATTSIZE=`du -c $BACKUPFILES | grep "[[:digit:][:space:]]total$" |sed s/\s*total//`
            if [ $MAXATTSIZE -ge $ATTSIZE ]
            then
                    BACKUPFILES=`echo "$BACKUPFILES" | sed -e "s# # -a #g"` #enable multiple attachments
                    mutt -s "$ERRORNOTE MySQL Backup Log and SQL Files for $HOST - $DATE" $BACKUPFILES $MAILADDR < $LOGFILE         #send via mutt
            else
                    cat "$LOGFILE" | mail -s "WARNING! - MySQL Backup exceeds set maximum attachment size on $HOST - $DATE" $MAILADDR
            fi
    elif [ "$MAILCONTENT" = "log" ]
    then
            cat "$LOGFILE" | mail -s "MySQL Backup Log for $HOST - $DATE" $MAILADDR
            if [ -s "$LOGERR" ]
                    then
                            cat "$LOGERR" | mail -s "ERRORS REPORTED: MySQL Backup error Log for $HOST - $DATE" $MAILADDR
            fi
    elif [ "$MAILCONTENT" = "quiet" ]
    then
            if [ -s "$LOGERR" ]
                    then
                            cat "$LOGERR" | mail -s "ERRORS REPORTED: MySQL Backup error Log for $HOST - $DATE" $MAILADDR
                            cat "$LOGFILE" | mail -s "MySQL Backup Log for $HOST - $DATE" $MAILADDR
            fi
    else
            if [ -s "$LOGERR" ]
                    then
                            cat "$LOGFILE"
                            echo
                            echo "###### WARNING ######"
                            echo "Errors reported during AutoMySQLBackup execution.. Backup failed"
                            echo "Error log below.."
                            cat "$LOGERR"
            else
                    cat "$LOGFILE"
            fi
    fi
    


    Почистим за собой:

    if [ -s "$LOGERR" ]
            then
                    STATUS=1
            else
                    STATUS=0
    fi
    # Clean up Logfile
    eval rm -f "$LOGFILE"
    eval rm -f "$LOGERR"
    exit $STATUS
    


    Теперь приведу файл конфиг для опций:

    # Username to access the MySQL server e.g. dbuser
    USERNAME=root
    # Username to access the MySQL server e.g. password
    if [ -f "/root/.mysql" ]; then
        PASSWORD=`cat /root/.mysql`
    else
        exit 0
    fi
    # Host name (or IP address) of MySQL server e.g localhost
    DBHOST=localhost
    # List of DBNAMES for Daily/Weekly Backup e.g. "DB1 DB2 DB3"
    DBNAMES="all"
    # Backup directory location e.g /backups
    BACKUPDIR="/var/backups/mysql"
    # Mail setup
    # What would you like to be mailed to you?
    # - log   : send only log file
    # - files : send log file and sql files as attachments (see docs)
    # - stdout : will simply output the log to the screen if run manually.
    # - quiet : Only send logs if an error occurs to the MAILADDR.
    MAILCONTENT="quiet"
    # Set the maximum allowed email size in k. (4000 = approx 5MB email [see docs])
    MAXATTSIZE="4000"
    # Email Address to send mail to? (user@domain.com)
    MAILADDR="root"
    # ============================================================
    # === ADVANCED OPTIONS ( Read the doc's below for details )===
    #=============================================================
    # List of DBBNAMES for Monthly Backups.
    MDBNAMES="mysql $DBNAMES"
    # List of DBNAMES to EXLUCDE if DBNAMES are set to all (must be in " quotes)
    DBEXCLUDE="information_schema performance_schema "
    # List of tables to exclude from the backup (in form db.table)
    TABLEEXCLUDE=""
    # Include CREATE DATABASE in backup?
    CREATE_DATABASE=no
    # Separate backup directory and file for each DB? (yes or no)
    SEPDIR=yes
    # Which day do you want weekly backups? (1 to 7 where 1 is Monday)
    DOWEEKLY=0
    # How many days keep backup
    BACKUP_DAYS=3
    # Choose Compression type. (gzip or bzip2)
    COMP=gzip
    # Compress communications between backup server and MySQL server?
    COMMCOMP=no
    # Additionally keep a copy of the most recent backup in a seperate directory.
    LATEST=no
    #  The maximum size of the buffer for client/server communication. e.g. 16MB (maximum is 1GB)
    MAX_ALLOWED_PACKET=1GB
    #  For connections to localhost. Sometimes the Unix socket file must be specified.
    OS=`uname`
    if [ "$OS" = "FreeBSD" ]; then
        SOCKET=/tmp/mysql.sock
    else
        SOCKET=/var/lib/mysql/mysql.sock
    fi
    # Command to run before backups (uncomment to use)
    #PREBACKUP="/etc/mysql-backup-pre"
    # Command run after backups (uncomment to use)
    #POSTBACKUP="/etc/mysql-backup-post"
    # (если не описано, по-умолчанию так и делать)
    DO_SQL_DUMP=yes
    # separate table to file ?
    SEPTABLE=no
    # (если не описано, по-умолчанию не делать)
    DO_HOT_BACKUP=no
    HOT_XTRA_BACKUP=no
    DO_MYDUMPER_BACKUP=no
    


    Мы не ставили целью «открыть Америку», но тема бекапов не перестаёт быть актуальной. И зачастую встречается так, что добавив строку в скрипт дамп базы, забываем проверить наличие ошибок в процессе дампа. Надеюсь в эта статья напомнит вам о необходимости очередной раз проверить наличие бекапов. А ты проверил свой бекап?

    image

    Автор: Игорь Медынский

    Southbridge

    152,72

    Обеспечиваем стабильную работу серверов

    Поделиться публикацией
    Комментарии 37
      +6
      Центосы админите, а скрипты пишете в Винде? Поправьте символы конца строки.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Скрипт копировался из консоли.
          Ткните носом, пожалуйста, где символы конца строки нужно поправить, не вижу.
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Да, Вас я понял.
              Правлю уже.
            –1
            Админить центосы из под винды — преступление? )
              +3
              Нет конечно. А передергивать нехорошо.
            +1
            Не знаю кто минусанул автору. Думаю данный пост очень хороший пример организации резервного копирования.
            из разряда — прочитал-повторил-профит.
            Спасибо за статью. На базе этого «скелета» можно делать свои варианты автоматизации бэкапа.
              0
              mysqldump прошлый век, взгляните на www.percona.com/software/percona-xtrabackup
              П.С. да, этим инструментом сразу не получишь .sql dump, но зато на больших объемах данных, нагрузки на IO в разы меньше. Да и по времени бекап делается быстрее.
                0
                Это решение мы тоже используем.
                Описали пока именно старый и проверенный метод, используемый много лет.
                  0
                  Напишите мануал как восстановить такой бекап «с нуля». Я нагуглил много чего, но по факту так и не смог восстановить. Базу не видно после рестарта. Наверно упускаю что-то очевидное.
                    0
                    Что имеется в виду под восстановлением «с нуля»?
                    Расскажите подробнее, пожалуйста.
                      0
                      На новом сервере.
                        +1
                        Напишем отдельный мануал по восстановлению бэкапа на новом сервере.
                      0
                      Бекапим:
                      /usr/bin/innobackupex-1.5.1 --use-memory=1G --user=USER --password=PASS /db_backup/mysql --no-timestamp
                      /usr/bin/innobackupex-1.5.1 --use-memory=1G --user=USER --password=PASS --apply-log /db_backup/mysql

                      Если Вам нужно вытащить sql то вот:
                      mysql_install_db --basedir=/usr/local/mysql --datadir=/tmp/data
                      mysqld --basedir=/usr --datadir=/tmp/data/ -P 3307 &
                      echo $! > mysql.pid
                      mysql -uroot -P 3307
                      mysqlcheck -P 3307 --user root --all-databases
                      mysqldump -uroot -P 3307 an_app > dump.sql
                      kill `cat mysql.pid`

                      Eсли нужно сразу восстановить все базы, без генерации sql, то после apply-log, перемещаете директорию в /var/lib/ (либо в директорию которая прописана в конфиге mysql — datadir)
                        +1
                        Вот статья
                        Надеюсь, поможет.
                        Сообщите, пожалуйста, результат.
                        0
                        Посмотрите внимательно код — там есть поддержка xtrabackup.
                        +7
                        Не проще ли было бы дать ссылку на GitHub-репозиторий с этими скриптами? Например, я хочу воспользоваться вашим решением. Мне копировать и собирать скрипты в ручную из поста? Не самое удобное решение, мягко говоря.
                          0
                          Извините, в репо этого скрипта нет.
                          Как только разместим — сообщу.
                            0
                            А воз и ныне там, почему не разместили на GitHub или хотябы в архив и на файлобменник?
                              0
                              Готовим скрипты. Сразу все накопленное разместим.
                              Сообщу отдельной новостью как только будет все готово.
                                0
                                Не разместили еще?
                          +2
                          Такие решения обычно простым копированием себе в проект не портировать. Это не готовый набор скриптов которые взял и поставил. Поэтому как ни крути, придется кусками копировать к себе с полным разбором, а что делают приведенные строки.
                            +1
                            Оригинальный скрипт можно скачать тут sourceforge.net/projects/automysqlbackup
                            +9
                            Зашёл сказать, что первая картинка замечательна.
                              +2
                              Зашел только из-за первой картинки.

                              По теме: очень похожее решение использовали на моей предыдущей работе(в nixys.ru). Даже названия переменных иногда совпадают). Возможно, всем сисадминам-аутсорсерам стоит объединить усилия в одной компании? :)
                                0
                                Возможно, это будет полезно)
                                Только непонятно пока как правильно начать взаимодействие.
                                Придти с улицы: «Здрассте, давайте работать вместе», как-то неприятно выглядит, я считаю)
                              +1
                              Интересно, почему все (ну хорошо, большинство) мучаются с бекапами MySQL на продакшене? Таблицы лочат и занимаются прочей противоестественной деятельностью, ведущей к деградации производительности на сервере.

                              Ведь есть вполне нормальный способ: поднимаем связку master-slave, и уже со slave делаем бекапы. Все довольны: и производительность не страдает, и в процессе бекапа с сервером можно делать что угодно и как угодно и на худой случай всегда имеем «горячую копию» в виде этого самого слейва…
                                0
                                К сожалению, не всегда есть возможность поднять репликацию.
                                Когда она есть — то, конечно, используется slave для бэкапа.
                                  0
                                  Вы не поверите, но иной раз попадаются клиенты, которые могут заказать екоммерс за пару млн, а когда дело доходит до архитектуры и серверах, они начинают выпучивать глаза и говорить — «а зачем нам 3 сервера?»
                                    +1
                                    А это уже дело исполнителя — рассказать клиенту, что он в итоге получит и сколько ему встанет отсутствие 3го сервера. Обычно такие люди очень неплохо бабло считают. А вот ИТшники очень редко умеют говорить что-то кроме «надо больше серверов» :)
                                      0
                                      Для слэйва и слабенькой виртуалки достаточно. А на нормальном продакшене не реально дампами пользоваться.
                                      +1
                                      Требования разные бывают. Причин, по которым нельзя задерживать сервер БД для бэкапа совсем мало, и у многих таких причин нет. Ночью, как правило, сервер простаивает и задержка в работе БД никого не побеспокоит. Тем более, лок делается только на запись, а чтение выполняется как обычно.

                                      А держать слейв ради бэкапов — это хоть и мелкая, но деградация производительности мастера, дополнительные затраты на еще один сервер и его обслуживание, плюс обеспечение не только того, чтобы бэкапы делались правильно, но и того, чтобы мастер-слейв корректно реплицировался и не разваливался. Для всех (ну хорошо, большинства :) такая морока не оправдана.
                                      +4
                                      Когда дело доходит до бэкапа своих баз, каждый изобретает свой велосипед.
                                        +3
                                        Давно ищу комплексное решение для бэкапа БД на linux сервере. Хочется, чтобы все удобно настраивалось и работало из коробки.
                                        Резервное копирование с помощью вороха самописных bash-скриптов не вызывает у меня доверия.
                                          +1
                                          >tar -czvf "$1.tgz". 2>&1
                                          >tar -cjvf "$1.tbz2". 2>&1

                                          tar давно умеет понимать тип сжатия по расширению, с помощью -a
                                          tar -cavf "$1.tgz". 2>&1
                                          tar -cavf "$1.tbz2". 2>&1

                                          итд

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое