Asial Blog

Recruit! Asialで一緒に働きませんか?

LVMのスナップショット機能を使ってXenイメージのバックアップ

カテゴリ :
バックエンド(インフラ)
タグ :
Tech
Linux
LVM
バックアップ
テストも重要
今回は、社内サーバに関するバックアップの一環で、LVMのスナップショットを使用してXenイメージのバックアップをとってみました。

LVM(Logical Volume Manager)とは?


名前の通り、論理ディスクを管理する機能です。
通常のパーティションだと、サイズを動的に変更したり、複数のディスクにまたがって一つのパーティションにするなど出来ませんが、そこにLVMを噛ませることにより、柔軟に管理出来るようになります。

LVM概要


LVMは既存のパーティション上に乗って仮想化する機構なので、下記の3種類のレイヤがあります。
・PV(Physical Volume)
 物理的に書き込む領域です。(HDDのパーティションなど)
 領域をLVMのPVとして初期化した際に、PE(Physical Extents)という単位で分割され、この単位で論理領域の確保が出来るようになります。

・VG(Volume Group)
 PVをまとめたもので、LVMとして使用出来る領域になります。
 物理領域(PV)をまとめて一つの デバイス にしたものと思ってください。

・LV(Logical Volume)
 実際に使用する領域。
 VGにパーティションを切ってしたものとでも思ってください。

細かい説明はググってください。

スナップショットとは?


ある時点に記録されている状態を取っておいたもの。
スナップショットを作成した瞬間の情報を保持することが出来ます。

Xenのイメージなど、常時使用中で何か書き込みがあるファイルの場合、cpなどで普通にコピーするとコピー中もデータが書き換わり不整合が起てしまうので、スナップショットを作成してコピーすることにより、破損を最小限(突然電源を落とした程度)にとどめてバックアップ出来ます。
ただし、Copy on write で実装されている関係で、スナップショットを作成した瞬間から書き込みコストが増大しますので、恒久的なバックアップには使えません。(使えないこともないですが遅くなります)
そのため、今回はスナップショットからデータを吸い出したあとに消去する手法にしました。

スナップショットの作成


スナップショットの作成は下記のようにします。
  1. lvcreate -s -L 2G -n "snap_20120719" "/dev/xenimages/xen-image-disk0"
簡単ですね。

-s スナップショットとして作成
-L スナップショット領域のサイズ(元ボリューム側で変更されたサイズがこのサイズを超えるまで使用可能。
  超えるとスナップショットのデータが壊れる可能性あり)
-n スナップショットの名前
元ボリュームのパス

となります。

実際の作業


基本的な流れは、下記のようになります。
・スナップショット作成
・マウント
・rsyncで内容をコピー
・アンマウント
・スナップショット削除

毎回手動で実行するのは馬鹿馬鹿しいので、cronで実行出来るようにスクリプト化しました。
ついでに古いものの自動削除も。

  1. #!/bin/bash
  2. export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  3. function backup {
  4.     orgvol="$1";
  5.     # スナップショットパス定義
  6.     snapvol="$orgvol-snap_`date '+%Y%m%d%H%M%S'`"
  7.     if [ -b "$snapvol" ];then
  8.         echo "$snapvol が存在します"
  9.         return 9;
  10.     fi
  11.     if [ ! -b "$orgvol" ];then
  12.         echo "$orgvol が存在しません"
  13.         return 9;
  14.     fi
  15.     
  16.     ret=0
  17. ## LVM自体バックアップ版
  18. ## 無駄が多いのでボツ
  19. #    # スナップショットのエリアをddする
  20. #    echo "バックアップ中..."
  21. #    if ! dd if="$snapvol" | gzip > "$backupdir/`basename $snapvol`.gz";then
  22. #        echo "ddの実行に失敗"
  23. #        ret=2
  24. #    fi
  25.     # バックアップ先ディレクトリ作成
  26.     snapbackupdir="$backupdir/snap_`basename $snapvol`"
  27.     test ! -d "$snapbackupdir" && mkdir "$snapbackupdir";
  28.     if [ ! -d "$snapbackupdir" ];then
  29.         echo "バックアップディレクトリの作成に失敗"
  30.         return 3
  31.     fi
  32.     # マウント先作成
  33.     echo "tmpmount作成"
  34.     test ! -d "tmpmount" && mkdir "tmpmount"
  35.     if [ ! -d "tmpmount" ];then
  36.         echo "マウント先ディレクトリの作成に失敗"
  37.         return 4
  38.     fi
  39.     # LVMスナップショットを取る
  40.     echo "スナップショット作成 `basename $snapvol`" "$orgvol"
  41.     if ! lvcreate -s -L 2G -n "`basename $snapvol`" "$orgvol";then
  42.         echo "スナップショットの作成に失敗"
  43.         return 1;
  44.     fi
  45.     # スナップショットをマウントしてrsync
  46.     echo "mount"
  47.     if ! mount -o ro,nouuid "$snapvol" "tmpmount";then
  48.         echo "マウントに失敗"
  49.         ret="5"
  50.     fi
  51.     if [ $ret -eq 0 ];then
  52.         echo "rsync中..."
  53.         if ! rsync -auv --progress "tmpmount/" "$snapbackupdir/";then
  54.             echo "rsync実行に失敗"
  55.             ret=6
  56.         fi
  57.     fi
  58.     # umount
  59.     echo "umount"
  60.     if ! umount "tmpmount";then
  61.         echo "umountに失敗"
  62.         ret=7
  63.     fi
  64.     # rmdir
  65.     echo "rmdir"
  66.     if ! rmdir "tmpmount";then
  67.         echo "tmpmount削除に失敗"
  68.         ret=8
  69.     fi
  70.     # スナップショットを削除
  71.     echo "スナップショット削除"
  72.     if ! lvremove -f "$snapvol";then
  73.         echo "スナップショット削除に失敗"
  74.         ret=4
  75.     fi
  76.     
  77.     return $ret;
  78. }
  79. function rotate {
  80.     # 1日分以降を削除
  81.     backupdir="$1"
  82.     for dir in $backupdir/snap_*; do 
  83.         # unixtimeに変換
  84.         utime="`echo $dir | perl -MTime::Local -pe 's/.*(201\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/($6,$5,$4,$3,$2-1,$1)/; $_=(timelocal eval $_)."\n"'; `"
  85.         time7="$((`date +%s` - 86400))"
  86.         if [ "$utime" -gt 1240432473 ];then # 念の為
  87.             if [ "$utime" -lt "$time7" ];then
  88.                 rm -rfv "$dir"
  89.             fi
  90.         fi
  91.     done
  92. }
  93. # バックアップするデバイス スペース区切りで複数指定可
  94. lvmnames="/dev/xenimages/xen-image-disk0"
  95. # バックアップ先
  96. backupdir="/mnt/backup"
  97. rotate $backupdir;
  98. # 全てのボリュームに対して実行
  99. for orgvol in $lvmnames;do
  100.     backup "$orgvol"
  101.     res="$?"
  102.     if [ "$res" -ne 0 ];then
  103.         echo "code: $res" | /usr/bin/mail -s "[`hostname`] LVM snapshot faild" mailaddress@example.com
  104.     fi
  105. done

単純ですがこんな感じで書いて見ました。
これを毎日1回実行すればスナップショット使用してバックアップ出来ます。
なお、ディレクトのパスとか名前は環境によって違うのであくまでも 参考まで にしてください。
もし実際のサーバで試す場合には、壊れてもいいサーバでテストを入念にしてください。

バックアップしようとして、全部消えるとか洒落にならないですよね。
ちょっとしたバグで、多量のデータが消えると言う悪夢がたまにあるので・・・。

使用感


弊社の場合、対象が400GB程度ある+バックアップ先にGlusterFSを使用しているためバックアップ自体は遅いですが、数時間あれば終わります。
バックアップ中はパフォーマンスは落ちますが、深夜なのでそれほど苦情は出ていないようです。
※詳細なパフォーマンスについては測定もできますが、本番運用中+GlusterFSを使用しているため速度的に不安定で大したデータは取れないので、そのうちに環境できたらやるかもしれません。

また、この方法だとディスクのフルバックアップになるため無駄が多くなります。どうにかして差分バックアップ出来ないか模索中なので、出来たらブログの記事にします。

以上、LVMスナップショットを使ったバックアップの紹介でした。