VPSで遊ぶ -その11 :darとmysqldumpをつかってサーバーの月次フルバックアップと日次差分バックアップを取る(前編:VPS鯖側でダンプ作成の巻)

みなさまお久しぶりです。
どこかのレンサバ屋がデーターをふっ飛ばしたと聞きました。
怖いですねー、鯖屋なんて信用できませんねー、自衛が必要ですねー。

というわけで安鯖・VPS鯖を使う人々(に限らず皆々様)は自分の手元に必ず日次バックアップを持っておきましょう。
&出来れば通電しない環境、物理的に離れた環境にコピー→別の場所に数世代前まで定期的に保存もしておきましょう。HDDなんてタダみたいなもんだし。
私は東京大阪アメリカ西海岸に分散させています。
あと最重要データの数GBはパスワード付き圧縮&暗号化ディスクイメージに保存しそいつをmicrosdに入れて念には念を入れています。

自衛が重要、他人の安全宣言は信じるな。信じられるのは自分だけ。(割とマジ)

あんま関係ないけどHDD不調時はfsck掛ける前にddrescueでディスクイメージのクローンを作ったりしてディスクイメージ側を操作しましょうね…

ユルいユーザー向けバックアップスクリプト公開

データ流出も怖いけどデータ抹消もものすごく怖い。
というわけで今日は私の使っているVPS鯖内ファイルバックアップスクリプトを公開しておきます。

トランザクション処理等バリバリやっている鯖は対象ではありませんが、正しく運用することで全データロスト復旧不可\(^o^)/オワタを避けることは出来ると思います。(だいたい良い感じに戻せる但し完全完璧ではない、トランザクションデータはどっかに消えるかもねという感じ。)

(ついでに/etc以下とかはmercurialを導入して履歴管理もしておきましょう。鯖屋を信じず更新差分をいつでも取れるようにしておくことも重要だと思います。)

まあデーターロスト&バックアップネーヨで終わる会社なんて成り立ってるほうが奇跡ですわ。(ポールシフトで電磁波が云々地球の危機ガーとかそういうのは別ね。そうなったら紙とか石版とか口頭伝承しか残らないわ。)


ご注意:
このスクリプトの運用ミス・将来のプログラム構造の変化などでバックアップスクリプトが動かずデータを飛ばしても私は一切責任を持ちません。
差分バックアップが膨れてディスクフルでサーバーが停止しても知りません。
定期的にバックアップが動いているかの確認・復旧出来るかの確認・ディスク容量が足りているかの確認等はあなたの仕事です。

あくまでも手法の紹介ですので自己責任で。ここに書いてあることも鵜呑みにしないでください。

前提条件

基本的なインストールについては省略します。
また、手元(自宅等)にデータを転送するバックアップ用ユーザーはパスフレーズ無しの公開鍵認証でssh接続しますが、そちらの説明も省略します。(わからない場合は対象外ですので以下のスクリプトは使わないでください。)

  1. 一日一回の牧歌的なバックアップで良い。(リアルタイムにバックアップしたい・トランザクションバリバリな環境は対象外。閲覧主体のPHP-MySQLCMSを安物VPSに突っ込んだ環境を想定。)
  2. Linuxである。(Ubuntu 10.04LTS)
  3. sh(bash)が動く。
  4. コマンドラインPHPが動く。(呼び出し部分をPHPで作った俺俺スクリプトなので。)
  5. dar(ディスクアーカイブ)がインストールしてある。( aptitude install dar )
  6. 使っているDBはmysqlである。(postgresql対応版もあるけど今回は簡単のために省略)
  7. VPSサーバーの環境構築位自分で出来る。
  8. ローカル←→VPSサーバー間で公開鍵認証かつノーパスワードで接続できるバックアップファイル転送専用ユーザーを作成出来る。
  9. 手元(ローカル)または別会社にLinux鯖があってrsyncが出来る。
  10. 一ヶ月分の差分バックアップファイルを保存可能な十分なディスク空き容量がVPS側、ローカル側双方に存在する。

という要はVPSとか専用鯖の環境です。共有レンサバは対象外です。

dar (http://dar.linux.free.fr/) の概要についての説明も省きます。『dar バックアップ』でググって先人の解説ページを見てください。

darの何が嬉しいか簡単にいうと、

  • フルバックアップを1ファイルに圧縮して保存できる。
  • フルバックアップを元に差分バックアップを作成できるので日次では差分のみ作成して負荷を減らせる。
  • バックアップ対象外・圧縮対象外等の細かな制御が可能である。

ってことです。

概要

やってることは単純で図で書くとこんな感じ。

VPS鯖側で毎日(月一回フルバックアップ、毎日差分バックアップ)のバックアップファイルを生成して保存しておき

保存したファイルを手元のサーバー側起動でrsyncで取得する。(手元のサーバー起動なので、手元側が固定IPじゃなくてもOK)

  • crontab起動のPHPスクリプトからdarを使ってVPSサーバー内ファイルのバックアップ
  • mysqldumpを使ってDBのバックアップ
  • crontab起動のrsyncで手元側サーバーとバックアップファイルの同期

てなかんじです。


こんな感じで毎月1日にファイルのフルバックアップ、それ以外の毎日はフルバックアップからの差分バックアップしています。
差分バックアップは変化がなければ殆ど容量を食いません。


スクリプト

以下にスクリプトを貼っていきます。本日はVPSサーバー側。
手元側サーバーにrsyncする部分については中編・後編で。

ディスクのバックアップ自体はルートユーザーに実行させます。
(一般ユーザーの読める領域のみバックアップでいいなら好きにしてください。)

スクリプト1:VPSサーバー側、rootユーザーのcrontab -e

# m h  dom mon dow   command
10 03 * * * /root/bin/mysql-backup.sh >/dev/null 2>&1
40 03 1 * * /root/bin/dar_backup.php -f > /backup/local/dar.log 2>&1
40 03 2-31 * * /root/bin/dar_backup.php -d > /backup/local/dar.log 2>&1

mysql-backup.sh、dar_backup.php がバックアップスクリプトです。/root/bin/に設置しています。

をおこなっています。



スクリプト2:VPSサーバー側、バックアップファイル保存ディレクトリの作成スクリプト
このスクリプトはバックアップ環境構築時の初回のみ使います。

/root/bin/mkbackupdir.sh

#!/bin/sh
mkdir -p /backup/local/dailybk
mkdir -p /backup/local/dar_file
mkdir -p /backup/local/mysql
mkdir -p /backup/local/psql
chmod 600 /backup
chown -R backupusr:users /backup
chmod 600 /backup/local/dailybk
chmod 600 /backup/local/dar_file
chmod 600 /backup/local/mysql
chmod 600 /backup/local/psql

backupusrは、VPSサーバー側のバックアップファイルrsync用の専用ユーザーです。任意のユーザーを作ってください。
バックアップしたファイルはroot権限ユーザーとbackupusrのみが読み取り可能です。

/backupとしていますがこれは昔バックアップ用パーティションを分けていた名残です。特に深い意味はありません。
/backup/local/なのは相互バックアップをするときに /backup/remote/とか /bakup/相手鯖名/ とかの構成にする場合があるからです。



スクリプト3:VPSサーバー側、DB毎のmysqldump利用のダンプファイル生成スクリプト
(2012/06/25 VPS側にも3日分保存する場合の例をコメントアウトして追加&$BACKIDIRを毎回ディレクトリ削除するのをやめて$BACKIDIR内ファイル削除に変更)
/root/bin/mysql-backup.sh (要chmod 700)

#!/bin/bash
PATH=/usr/local/sbin:/usr/bin:/bin
NOW=`date +%Y%m%d`

# バックアップ先ディレクトリ
BACKDIR=/backup/local/mysql  #空白不可注意
DAILY_BACKDIR=/backup/local/dailybk #空白不可注意

# MySQLrootパスワード
ROOTPASS=hogehgoerootpass

# backup(転送用)のユーザー
BACKUPUSR=backupusr

# バックアップ先ディレクトリ内前日ダンプ削除
find $BACKIDIR -type f -name "*.dump.sql.gz"|xargs rm -f

chmod -R 600 $BACKDIR
chmod -R 600 $DAILY_BACKDIR
touch $BACKDIR
touch $DAILY_BACKDIR

# データベース名取得
DBLIST=`ls -p /var/lib/mysql | grep / | tr -d /`

# データベースごとにバックアップ
for dbname in $DBLIST
do
    nice -n 19 mysqldump -uroot -p$ROOTPASS --default-character-set=binary $dbname | nice -n 19 gzip > $BACKDIR/$dbname.dump.sql.gz;
    chmod 600 $BACKDIR/$dbname.dump.sql.gz;
done

#dumpファイルを圧縮して保存、VPS側にも3日分保存する場合は下二行の方を有効に
nice -n 19 tar zcf $DAILY_BACKDIR/mysql_dailybk_mysqldump.tar.gz $BACKDIR
#find $DAILY_BACKDIR -type f -mtime +2 -name "mysql*dailybk*.gz" |xargs rm -f
#nice -n 19 tar zcf $DAILY_BACKDIR/mysql_dailybk_mysqldump_$NOW.tar.gz $BACKDIR

chmod 600 $DAILY_BACKDIR/mysql*dailybk*.gz
chown  $BACKUPUSR:users $DAILY_BACKDIR/mysql*dailybk*.gz
chown -R $BACKUPUSR:users $BACKDIR
chown -R $BACKUPUSR:users $DAILY_BACKDIR

ROOTPASS(mysqlのrootユーザーのパスワード)、BACKUPUSRは適宜変更してください。
mysqlのDB内からDB名を取得せず/var/lib/mysqlディレクトリからDB名を取得してるので乱暴ですが動くのでOK)

ここでは

  1. mysqldumpをぐるぐる回しながらデータベース毎にダンプファイルを作成して
  2. /backup/local/mysql/以下に最新ダンプをDB毎に保存して
  3. ついでに$DAILY_BACKDIR/mysql_dailybk_mysqldump.tar.gz に固めて保存し
  4. 読み取り権限と所有者の変更をしています。

また次回バッチ起動時に前回バッチファイルのダンプは/backup/local/mysql/をまるごとrm -rf しています。


スクリプト4:VPSサーバー側、darのディスクアーカイブ生成スクリプト

/root/bin/dar_backup.php (要chmod 700)

#!/usr/bin/php -q
<?php
if (!isset($argv[1])){
  echo "引数がありません -f or -d \n";
  exit;
}
$ptn = $argv[1];
//backupファイルの自VPS内設置場所
define("log_path", "/backup/local/darbk.log");
define("bk_base", "/backup/local/dar_file");

/**
 * #VPSサーバー側からのrsyncは行わず、ローカル側からのrsyncに起動方法を変更したため廃止
 * define("sync_use",false) ;
 * define("sync_to","remote-back@example.com:/backup/remote/mysvname/") ;
 */
define("nowtime", time());
// dar の起動オプション、-Z以下は負荷削減のため二重に圧縮しないファイル。:http://dar.linux.free.fr/doc/man/dar.html
$option = <<<___END___
-v
-D
-m 256
-y9
-Z "*.gz"
-Z "*.Z"
-Z "*.bz2"
-Z "*.tgz"
-Z "*.lzh"
-Z "*.zip"
-Z "*.jpg"
-Z "*.JPG"
-Z "*.jpeg"
-Z "*.JPEG"
-Z "*.png"
-Z "*.PNG"
-Z "*.gif"
-Z "*.GIF"
-Z "*.mpg"
-Z "*.MPG"
-Z "*.mpeg"
-Z "*.MPEG"
-Z "*.mp3"
-Z "*.rm"
-Z "*.jar"
-Z "*.exe"
-Z "*.EXE"
-Z "*.msi"
-Z "*.MSI"
-Z "*.divx"
-Z "*.flv"
-Z "*.mov"
-Z "*.wma"
-Z "*.iso"
-Z "*.img"
-Z "*.ISO"
-Z "*.IMG"
-Z "*.swf"
-Z "*.slc"
-Z "*.ts"
-Z "*.class"
___END___;

$option = preg_replace("/\r/iu", " ", $option);
$option = preg_replace("/\n/iu", " ", $option);
define("option_param", $option);
// バックアップ対象のディレクトリ(/直下のディレクトリ名のみ指定可能)、除外ディレクトリは"p"=>array("dir1","dir2",… ),で指定。
$bk_arr = array(
  // array("t"=>"backup"),
  array("t" => "bin"),
  array("t" => "boot"),
  array("t" => "dev"),
  array("t" => "etc"),
  array("t" => "home"),
  array("t" => "lib"),
  array("t" => "lost+found"),
  array("t" => "media"),
  array("t" => "misc"),
  array("t" => "mnt"), 
  // array("t"=>"net"),
  array("t" => "opt"), 
  // array("t"=>"proc"),
  array("t" => "root"),
  array("t" => "sbin"),
  array("t" => "selinux"),
  array("t" => "srv"), 
  // array("t"=>"sys"),
  array("t" => "tmp"),
  array("t" => "usr", "p" => array("local/delegate")), // ここでは/usr/local/delegeteは除外している
  array("t" => "var", "p" => array("cache", "spool/squid", "log", "named/chroot/proc")), // ここでは /var/cache, /var/spool/squid, /var/log等を除外している。ディスク空き容量の関係から/var/logはrsyncしてるので除外
  );

if ("-f" === $ptn || "-d" === $ptn){
  $msg = date("Y-m-d H:i:s", nowtime) . " Backup START\n";
  echo $msg ;

  switch ($ptn){
    case "-f":// 月次用フルバックアップ
      $pre_cmd = get_cmd_list("full", null, true);
      $cmd_list = get_cmd_list("full", $bk_arr);
      break;
    case "-d":// 日次用差分バックアップ
      $pre_cmd = get_cmd_list("diff", null, true);
      $cmd_list = get_cmd_list("diff", $bk_arr);
      break;
    default:
      $cmd_list = array();
      break;
  }
  foreach($pre_cmd as $k => $v){
    echo $v . "\n";
    system($v); //32日以上昔のdarファイルの削除
  }

  foreach($cmd_list as $k => $v){
    echo $v . "\n";
    system($v); //darバックアップの順次実行
  }
  $msg = date("Y-m-d H:i:s", time()) . " Backup END \n";
  echo $msg ;
}

/**
 * #VPSサーバー側からのrsyncは行わず、ローカル側からのrsyncに起動方法を変更したため廃止
 * if(sync_use){
 * if("-f"===$ptn||"-d"===$ptn||"-r"===$ptn){
 * $msg =  date("Y-m-d H:i:s",time()). " Rsync START \n";
 * echo $msg ;
 * $cmd = "rsync -auvz -e ssh --delete ".bk_base." ".sync_to." " ;
 * system($cmd);
 * $msg =  date("Y-m-d H:i:s",time()). " Rsync END \n";
 * echo $msg ;
 * }
 * }
 */
// 一連のコマンドの動的生成
function get_cmd_list($ptn, $bk_arr, $pre_flg = false)
{
  $cmd_arr = array();
  $diffstr = "";

  if ($pre_flg){ // 32日以上昔のdarファイルの削除コマンド生成
    $cmd = "for i in `/usr/bin/find " . bk_base . ' -name "*full*.dar" -mtime +32 ` ; do /bin/rm -f $i ; done ; ' ;
    $cmd_arr[] = $cmd;
    $cmd = "for i in `/usr/bin/find " . bk_base . ' -name "*diff*.dar" -mtime +32 ` ; do /bin/rm -f $i ; done ; ' ;
    $cmd_arr[] = $cmd;
  }else{ // darバックアップの順次実行コマンド生成
    foreach($bk_arr as $k => $v){
      $dname = $v["t"];
      $fullpath_h = bk_base . "/" . $dname . "-full-";
      $bkpath_h = bk_base . "/" . $dname . "-" . $ptn . "-";
      $bkpath = $bkpath_h . date("YmdHi");
      $tgt = " -R /{$dname} ";
      $no_tgt = "";
      if ("diff" === $ptn){
        $cmd = "/bin/ls -tr " . $fullpath_h . '*.dar|tail -n1 ' ;
        echo $cmd . "\n" ;
        $lsf = system($cmd);
        if (!$lsf){ 
          // echo " -----basefile none_a\n" ;
          continue;
        }
        $lsf = preg_replace("/^(.+)\.\d+\.dar$/iu", "\\1", $lsf);

        $diffstr = " -A " . $lsf ;
      }

      if (isset($v["p"])){
        foreach($v["p"] as $k2 => $v2){
          $no_tgt .= " -P " . $v2 ;
        }
      }
      $tgt .= $no_tgt ;
      $cmd_arr[] = " nice -n 10 dar " . option_param . " -c {$bkpath} {$tgt} {$diffstr} ";
    }
  }

  return $cmd_arr;
}
exit;

?>

$option のオプションパラメータはhttp://dar.linux.free.fr/doc/man/dar.html や man darや他の方のdar解説サイトをご確認ください。

  • y9は圧縮レベル
  • Zで圧縮不要ファイルを圧縮対象にしない。

みたいなのを1行毎に記述します。

$bk_arr の配列部分でバックアップ対象の/直下のディレクトリ名と、バックアップ除外対象のディレクトリ名を指定します。
例えば

array("t"=>"usr","p"=>array("local/delegate")),

の部分では/usrをバックアップ対象に含めるが、/usr/local/delegeteはバックアップ対象から除外しています。

get_cmd_list()
の部分ではdarファイルの作成コマンドと、32日以上昔のdarファイルの削除コマンドを生成しています。

生成するようにしています。

32日以上昔のdarファイルを削除すると、6/1にフルバックアップを取った後の 5/28の差分バックアップとかははっきり言ってゴミですがこのあたりは好きに調整してください。以前はVPS内部側保存期間も96日にしていたのですがディスク容量の関係で断念しました。(VPS側内部では31日保持し、手元に転送するときに工夫して手元では3ヶ月残す構成にするのも良いかと思います。)

中でゴニョゴニョなんかいじくり回していますがここで$optionと$bk_arrのパラメータから

 nice -n 10 dar -v -D -m 256 -y9 -Z "*.gz" -Z "*.Z" -Z "*.bz2" -Z "*.tgz" -Z "*.lzh" -Z "*.zip" -Z "*.jpg" -Z "*.JPG" -Z "*.jpeg" -Z "*.JPEG" -Z "*.png" -Z "*.PNG" -Z "*.gif" -Z "*.GIF" -Z "*.mpg" -Z "*.MPG" -Z "*.mpeg" -Z "*.MPEG" -Z "*.mp3" -Z "*.rm" -Z "*.jar" -Z "*.exe" -Z "*.EXE" -Z "*.msi" -Z "*.MSI" -Z "*.divx" -Z "*.flv" -Z "*.mov" -Z "*.wma" -Z "*.iso" -Z "*.img" -Z "*.ISO" -Z "*.IMG" -Z "*.swf" -Z "*.slc" -Z "*.class" -c /backup/local/dar_file/var-diff-201206230037  -R /var  -P cache -P spool/squid -P log -P named/chroot/proc  -A /backup/local/dar_file/var-full-201206010340

の様な呪文を作っています。科学となんとかかんとかで物語は始まる(とある科学のインなんとかさん


上記のスクリプトを動かしてみる

動かすとこんな感じでずらずらとバックアップファイルが生成されます。
mysqlのバックアップはディスク容量節約のために最新分のみが保存されています。
(手元側鯖への転送時に日付ごとディレクトリを作成してファイルを保管しています。)


/backup以下のtree

ディスク容量15GBのVPSで/backup以下は1.6GB位です。
(日次差分量によって変わってきます。これは殆ど更新の無いサーバー)



続きは中編:手元(ローカル)鯖側編へ

(勘の良い人はこれだけでなんとかなると思います。後編は実際に動かしてrsyncするお話&バックアップからの復元例のお話です。)


ではでは!