はじめに
オンプレミス環境で仮想マシーンを運用するにあたって、ディスクイメージファイルのバックアップをどのように取るか、という問題があるわけです。
当然、仮想マシーンを停止させてしまえば確実にバックアップを取得できるわけですが、サービス断は避けたい。
仮想化基盤の製品なら無停止でバックアップを取る機能があるのでしょうが、それを無料で手軽にすぐに使える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
スクリプトの作成。
環境に合わせて適宜編集する。
#!/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 に登録する。
コピーにかかる時間を考慮して頻度を調節する。
1 * * * * root sh /usr/local/bin/img_backup.sh
/var/log にログを出力するようにしているのでlogrotate を定義する。
/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 のスナップショットを利用したバックアップの仕組みを作りやすい。