Help us understand the problem. What is going on with this article?

KVM のディスクイメージファイルの無停止バックアップ

More than 1 year has passed since last update.

はじめに

オンプレミス環境で仮想マシーンを運用するにあたって、ディスクイメージファイルのバックアップをどのように取るか、という問題があるわけです。
当然、仮想マシーンを停止させてしまえば確実にバックアップを取得できるわけですが、サービス断は避けたい。
仮想化基盤の製品なら無停止でバックアップを取る機能があるのでしょうが、それを無料で手軽にすぐに使えるKVM で行う方法を書きます。

稼働中の仮想マシーンのディスクイメージファイルは常々書き換わっているので、普通にコピーしたのでは正常に動作するディスクイメージファイルのコピーは取れません。
そこで使うのが、LVM のスナップショットです。
LVM スナップショットからディスクイメージファイルの静止点をコピーできます。
今まで、この手法で取得したディスクイメージファイルで起動できなかったことはありません。

私の知る範囲で、この手法でバックアップを取得している人を見たことが無く、口で説明してもKVM のスナップショットと混同されてなかなか伝わらないので記事にすることにしました。

構成

ホストマシーン(物理サーバー) にCentOS7 を最小構成でインストールする。
その時ディスク構成を以下のようにする。
/var/lib/libvirt/images 用のVG とLV の名前は何でも良いが、ここではそれぞれserver-vg, image-lv とする。

dev Physical Volume
(pv)
システム Volume Group
(vg)
Logical Volume
(lv)
mount
sda sda1 Linux
(83)
- - /boot
sda2 Linux LVM
(8e)
centos root
swap
/
swap
sda3 Linux LVM
(8e)
server-vg image-lv /var/lib/libvirt/images
空き

次の項のVG 拡張で以下のように構成変更し、sda4 のサイズがsda3 のサイズの1/4 以上になるようにしたいので
sda3 と空きスペースのサイズの割り振りをOS インストール前に予め決めておく。

dev Physical Volume
(pv)
システム Volume Group
(vg)
Logical Volume
(lv)
mount
sda sda1 Linux
(83)
- - /boot
sda2 Linux LVM
(8e)
centos root
swap
/
swap
sda3 Linux LVM
(8e)
server-vg image-lv /var/lib/libvirt/images
sda4 Linux
(83)
- -

このsda4 をimage-lv のスナップショットの保存領域として使う。

VG 拡張

sda4 の作成。

sudo su -
echo -e "n\np\n\n\nw\n" | fdisk /dev/sda
reboot

sda4 をserver-vg に加える。
server-vg のFree PE / Size がsda4 の容量分増えていれば良い。

sudo vgextend server-vg /dev/sda4
  Physical volume "/dev/sda4" successfully created.
  Volume group "server-vg" successfully extended

sudo vgdisplay
--- Volume group ---
VG Name               server-vg
System ID
Format                lvm2
Metadata Areas        2
Metadata Sequence No  3
VG Access             read/write
VG Status             resizable
MAX LV                0
Cur LV                1
Open LV               1
Max PV                0
Cur PV                2
Act PV                2
VG Size               408.12 GiB
PE Size               4.00 MiB
Total PE              104480
Alloc PE / Size       76800 / 300.00 GiB
Free  PE / Size       27680 / 108.12 GiB
VG UUID               eUT9O4-CiSA-3a27-jfsJ-VNqE-v0ML-5yYLWZ

LVM スナップショットの取得試験

準備として、ディスクイメージファイルを作成したり/var/lib/libvirt/images に適当なファイルを作成しておく。

現状の論理ボリューム一覧を確認する。

sudo lvscan
  ACTIVE            '/dev/centos/root' [30.00 GiB] inherit
  ACTIVE            '/dev/centos/swap' [<8.00 GiB] inherit
  ACTIVE            '/dev/server-vg/image-lv' [300.00 GiB] inherit

仮想マシーンディスクイメージファイル保存ディレクトリのサイズを確認する。

sudo du -h /var/lib/libvirt/images
25G     /var/lib/libvirt/images/

images-snap という名前のLVM のスナップショットを作成して一覧で確認する。
サイズは上で確認したサイズの1/4 以上。

sudo lvcreate -s -L 7G -n images-snap /dev/server-vg/image-lv
  Logical volume "images-snap" created.

sudo lvscan
  ACTIVE            '/dev/centos/root' [30.00 GiB] inherit
  ACTIVE            '/dev/centos/swap' [<8.00 GiB] inherit
  ACTIVE   Original '/dev/server-vg/image-lv' [300.00 GiB] inherit
  ACTIVE   Snapshot '/dev/server-vg/images-snap' [7.00 GiB] inherit

適当にマウント用ディレクトリを作成してLVM スナップショットをマウントして中身を確認する。
この時、元の/var/lib/libvirt/images 以下に変更をかけても/mnt/snap 以下には変化が無いことを確認する。
また、/mnt/snap 以下のファイルをホームディレクトリなどにコピーできることを確認する。

sudo mkdir /mnt/snap
sudo mount -o nouuid -o ro /dev/server-vg/images-snap /mnt/snap
sudo ls /mnt/snap
 XXXX.qcow2

アンマウントしてLVM スナップショットを削除する。

sudo umount /mnt/snap
sudo lvremove -f /dev/server-vg/images-snap
  Logical volume "images-snap" successfully removed

論理ボリューム一覧でLVM スナップショットが削除されたことを確認する。

sudo lvscan
  ACTIVE            '/dev/centos/root' [30.00 GiB] inherit
  ACTIVE            '/dev/centos/swap' [<8.00 GiB] inherit
  ACTIVE            '/dev/server-vg/image-lv' [300.00 GiB] inherit

以上の流れをスクリプトにしてCron に登録すればディスクイメージファイルのバックアップを自動化できる。

バックアップの自動化

例として、LVM のスナップショットを作成して、そこからディスクイメージファイルを取り出してNFS サーバーにコピーするスクリプトを作成する。
おおよその流れは前項の試験内容と同じ。

スクリプトで使用するコマンドをインストール。

sudo yum -y install bc

スクリプトの作成。
環境に合わせて適宜編集する。

img_backup.sh
#!/bin/sh
# 説明   : LVM スナップショットからディスクイメージファイルをNFS 先にコピーする。
# 作成者 : 
# 作成日 : 

date=`/usr/bin/date +"%Y/%m/%d %H:%M:%S"`
log="/var/log/img_backup.log"
vg="server-vg"
lv="image-lv"
snap_dir="/mnt/snap"
nfs_dir="/mnt/nfs"
images="/var/lib/libvirt/images"
nfs_server="10.253.3.20"
nfs_server_dir="/opt/images_backup"


remove_snapshot (){
 /usr/bin/umount ${snap_dir}
 local lvremove=`/usr/sbin/lvremove -f /dev/${vg}/${lv}.snap 2>&1`

 local exists_snapshot=`check_snapshot`

 if [ ${exists_snapshot} -eq 1 ]; then
  /usr/bin/echo "${date} ${lvremove}" >> ${log}
 fi
}


check_snapshot (){
 local lvscan=`/usr/sbin/lvscan | /usr/bin/grep ${lv}.snap | /usr/bin/wc -c`

 if [ ${lvscan} -gt 0 ]; then
  /usr/bin/echo 1
 else
  /usr/bin/echo 0
 fi
}


unmount_nfs (){
 local umount=`/usr/bin/umount ${nfs_dir} 2>&1`

 local mount=`check_mount`

 if [ ${mount} -eq 1 ]; then
  /usr/bin/echo "${date} ${umount}" >> ${log}
 fi
}

check_mount (){
 local mounted=`/usr/bin/mount | /usr/bin/grep ${nfs_server} | /usr/bin/wc -c`

 if [ ${mounted} -gt 0 ]; then
  /usr/bin/echo 1
 else
  /usr/bin/echo 0
 fi
}


#
# イメージファイルがあるか確認
#
exist_qcow2=0
for qcow2 in $( ls ${images} ); do
 if [[ ${qcow2} =~ .+\.qcow2 || ${qcow2} =~ .+\.img ]]; then
  exist_qcow2=1
  break
 fi
done

if [ ${exist_qcow2} -eq 0 ]; then
 /usr/bin/echo "${date} not exist image file." >> ${log}
 exit 1
fi


#
# ディレクトリの確認
#
if [ ! -e ${snap_dir} ]; then
 /bin/mkdir ${snap_dir}
fi

if [ ! -e ${nfs_dir} ]; then
 /bin/mkdir ${nfs_dir}
fi


#
# NFS マウント
#
mount=`check_mount`
if [ ${mount} -eq 0 ]; then

 mount_nfs=`/usr/bin/mount -t nfs -o tcp ${nfs_server}:${nfs_server_dir} ${nfs_dir} 2>&1`
 mount=`check_mount`

 if [ ${mount} -eq 0 ]; then
  /usr/bin/echo "${date} ${mount_nfs}" >> ${log}
  exit 1
 fi
fi


#
# ${images} のサイズの1/4 GB + 1GB をスナップショットのサイズにする。
#
img_size=`/usr/bin/du -b ${images} | /usr/bin/sed -r "s/^([0-9]+).+$/\1/"`
snap_size=`/usr/bin/echo "scale=2; ${img_size} / (4 * 1024 * 1024 * 1024) + 1.00" | /usr/bin/bc`


#
# LVM スナップショットの作成
#
exists_snapshot=`check_snapshot`

if [ ${exists_snapshot} -eq 1 ]; then
 `remove_snapshot`
fi

lvcreate=`/usr/sbin/lvcreate -s -L ${snap_size}GB -n ${lv}.snap /dev/${vg}/${lv} 2>&1`
exists_snapshot=`check_snapshot`

if [ ${exists_snapshot} -eq 0 ]; then
 /usr/bin/echo "${date} ${lvcreate}" >> ${log}
 exit 1
fi

/usr/bin/mount -o nouuid -o ro /dev/${vg}/${lv}.snap ${snap_dir}


#
# ディスクイメージファイルのコピー
# 前回のバックアップよりも更新日時が新しい場合のみ。
#
start_date=`/usr/bin/date +"%Y/%m/%d %H:%M:%S"`
/usr/bin/echo "${start_date} start copy." >> ${log}

for qcow2 in $( ls ${snap_dir} ); do
 if [[ ${qcow2} =~ .+\.qcow2 || ${qcow2} =~ .+\.img ]]; then
  if [ -e ${nfs_dir}/${qcow2} ]; then
   active_img=`/usr/bin/ls -l --time-style="+%s" ${snap_dir}/${qcow2} | /usr/bin/cut -d " " -f 6`
   backup_img=`/usr/bin/ls -l --time-style="+%s" ${nfs_dir}/${qcow2} | /usr/bin/cut -d " " -f 6`

   if [ ${backup_img} -ge ${active_img} ]; then
    skip_date=`/usr/bin/date +"%Y/%m/%d %H:%M:%S"`
    /usr/bin/echo "${skip_date} skip ${qcow2}." >> ${log}
    continue
   fi
  fi

  copy_date=`/usr/bin/date +"%Y/%m/%d %H:%M:%S"`
  /usr/bin/echo "${copy_date} copy ${qcow2}." >> ${log}

  cp ${snap_dir}/${qcow2} ${nfs_dir}/${qcow2}
 fi
done

end_date=`/usr/bin/date +"%Y/%m/%d %H:%M:%S"`
/usr/bin/echo "${end_date} end copy." >> ${log}


#
# XML 定義ファイルのコピー
#
for xml in $( ls /etc/libvirt/qemu ); do
 if [[ ${xml} =~ .+\.xml ]]; then
  /bin/cp /etc/libvirt/qemu/${xml} ${nfs_dir}/${xml}
 fi
done


#
# NFS アンマウントとLVM スナップショットの削除
#
`unmount_nfs`
`remove_snapshot`

作成したスクリプトを適当なディレクトリに保存してCron に登録する。
コピーにかかる時間を考慮して頻度を調節する。

/etc/cron.d/img_backup.cron
1 * * * * root sh /usr/local/bin/img_backup.sh

/var/log にログを出力するようにしているのでlogrotate を定義する。

/etc/logrotate.d/img_backup
/var/log/img_backup.log {
 monthly
 missingok
 rotate 12
 compress
}

応用

仮想マシーンディスクイメージファイル保存領域としてSSD やNVMe を使用する場合の例。
OS インストール時は以下のようなディスク構成にする。
この時、sda の空き領域の容量が増設デバイスの空き領域の容量の1/4 以上になるようにする。

dev Physical Volume
(pv)
システム Volume Group
(vg)
Logical Volume
(lv)
mount
sda sda1 Linux
(83)
- - /boot
sda2 Linux LVM
(8e)
centos root
swap
/
swap
空き
nvme0n1 空き

以下のような構成になるように設定していく。

dev Physical Volume
(pv)
システム Volume Group
(vg)
Logical Volume
(lv)
mount
sda sda1 Linux
(83)
- - /boot
sda2 Linux LVM
(8e)
centos root
swap
/
swap
sda3 Linux
(83)
server-vg - -
nvme0n1 nvme0n1p1 Linux LVM
(8e)
image-lv /var/lib/libvirt/images

増設デバイスにパーティションを作成する。

sudo su -
echo -e "n\np\n1\n\n\nt\n8e\nw\n" | fdisk /dev/nvme0n1

増設デバイスにPV とVG とLV を作成しxfs でフォーマットする。

pvcreate /dev/nvme0n1p1
  Physical volume "/dev/nvme0n1p1" successfully created.

vgcreate server-vg /dev/nvme0n1p1
  Volume group "server-vg" successfully created

lvcreate -n image-lv -l 100%FREE server-vg
  Logical volume "image-lv" created.

mkfs.xfs /dev/server-vg/image-lv
meta-data=/dev/server-vg/image-lv isize=512    agcount=4, agsize=97675520 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=390702080, imaxpct=5
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=190772, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

/var/lib/libvirt/images にマウントしてfstab に追記する。

mount /dev/server-vg/image-lv /var/lib/libvirt/images
echo "/dev/server-vg/image-lv /var/lib/libvirt/images xfs defaults 0 0" >> /etc/fstab

sda3 を作成してserver-vg に加える。

echo -e "n\np\n3\n\n\nw\n" | fdisk /dev/sda
reboot

sudo vgextend server-vg /dev/sda3

これで前項のスクリプトでバックアップを取得できる環境になった。

まとめ

複数台の物理サーバーを置けるならGlusterfs で冗長化するのがスマートであるが、汎用サーバー1台しか置けない環境や、あまり難しいことをしたくない環境ならこの方法で仮想マシーンのバックアップを取るのが費用もかからず便利である。
また、LVM のスナップショットは様々なファイルのバックアップに使えるので、物理サーバーを構築するときはHDD を全部使わずに空き領域を残しておくと後からLVM のスナップショットを利用したバックアップの仕組みを作りやすい。

t-eno
元々はプログラミングが本業でしたが、数年の間、部署でインフラ関連をやる人がおらずサーバーやネットワークばかりやっていた時期がありました。 おかげて一人で何でもできるようになりました。
https://github.com/takahiro-eno/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした