Posted at

vSphere Hypervisor (ESXi)でカジュアルにホットバックアップするスクリプト


ESXiのバックアップに苦労した過去

久々にESXiを触りました。名前がvSphere Hypervisorになっていたとは。

以前バージョン5のときに社内でサーバーを運用するのに使っていましたが、バックアップの運用に苦労しました。

色々探してもちょうどいいソリューションがなく、諦めてバックアップにはActonis製のソフトウェアを使っていました。

当時はもうちょっと安かったですが、それでもいい値段しました。

https://www.acronis.com/ja-jp/business/backup/virtual-machine/

でもこれが、まあ安定して動いてくれない。動かしてた環境がしょぼかったのもあると思いますが。

この前、VirtualBoxをハイパーバイザーとして使う際のバックアップスクリプトを作ったので、そのvSphere版を作ってみました。

VirtualBoxのVMをライブ/オンライン/無停止バックアップするスクリプト

https://qiita.com/miyanaga/items/39c19f1aa1171b2b402e

個人用なので作りが甘く、もっとよいやり方があると思いますが、プライベートな仮想環境のバックアップに参考ください。


やってること

各VMについて、.vmxファイル、.vmdkファイル、.logファイルをバックアップします。

バックアップの直前にスナップショットを作成し、バックアップ後にそのスナップショットを削除します。

起動中のVMは、ブロックデバイスのイメージファイルがロックされるのでコピーできません。仮にコピーできても整合性が保たれないので危険です。

そこでスナップショットをとると、イメージファイルが一度凍結され、差分が別のファイルに書き込まれます。その差分ファイルのみロックされるので、ファイルコピーで安全にイメージファイルをバックアップできます。

最初はVMのディレクトリにある全ファイルをコピーしていたのですが、メタデータとの不整合のためかVMを起動できませんでした。それで上記の拡張子のみコピーしています。

有償版のvSphereではライブマイグレーションができたと記憶していますが、それに比べると貧弱です。

スナップショットの作成と削除で謎の標準エラーが出ます。

スナップショットのIDはインクリメントされるのでどんどん増えていきます(特に不都合はないですが)。

まあ個人用なのでよしとします。


使い方

vSphere Hypervisor上で実行します。

backup=""を実際のバックアップ先に変更してください。

各VMには便宜的にでも一つ以上のスナップショットを作っておくと、高速な差分バックアップが実現します。

#!/bin/sh

# vsphare Hypervisor(6.7で動作確認)のホットバックアップスクリプト

# TODO
# * スナップショットの作成と削除で'Create Snapshot:: not found'という標準エラー
# * スペースを含むVMへの対応
# * 名称が重複するVMへの対処
# * バックアップ先の同期削除(rsync的な動作)

# datastoreが存在するvolumesパスとバックアップ先
volumes="/vmfs/volumes"
backup="/backup"

completed=""

# VMをリストアップ
vms=$(vim-cmd vmsvc/getallvms | tail -n +2)

# スナップショットを作成する関数
# 引数は整数によるVMのID、戻り値はスナップショットID(失敗した場合は""か0)
create_snapshot() {
vmid="$1"

# 日付付きスナップショットラベル
ts=$(date '+%Y-%m-%d %H:%M:%S')
label="backup $ts"

# スナップショットを作成
$(vim-cmd vmsvc/snapshot.create "$vmid" "$label" 2>/dev/null)

# スナップショットIDはインクリメントされるので数値として最も大きなIDを取得
# 正常に取得できない場合は初期値0のまま
snapshotid=$(vim-cmd vmsvc/snapshot.get $vmid | grep 'Snapshot Id' | awk 'BEGIN{ id=0 } { if ($4>id) id=$4 } END{ print id }')

echo $snapshotid
}

# スナップショットを削除する関数
# 引数は整数によるVMのIDとスナップショットID
remove_snapshot() {
vmid="$1"
snapshotid="$2"

# スナップショットを削除
$(vim-cmd vmsvc/snapshot.remove "$vmid" "$snapshotid" 2>/dev/null)
}

# VMレコードを解析する正規表現
re_vm_record='^([0-9]+) +([^ ]+) +\[([^\]+)\]\s+([^ ]+).+'

# リストアップしたVMを1件ずつ処理
IFS=$'\n'
for record in $vms; do
# レコードを解析して整数のID、データストア、.vmxファイルパス、vmパスを取得
vmid=$(echo "$record" | sed -r "s!$re_vm_record!\1!")
datastore=$(echo "$record" | sed -r "s!$re_vm_record!\3!")
vmx=$(echo "$record" | sed -r "s!$re_vm_record!\4!")
vm=$(dirname "$vmx")

# 意図しないレコードフォーマットなどで.vmxファイルパスを取得できなかった場合は次のVMへ
if [ "$vmx" = "" ]; then
echo "Could not parse vmx in '$record'"
continue
fi

# .vmxファイルのフルパス
path="$volumes/$datastore/$vmx"
file=$(basename "$path")
dir=$(dirname "$path")

# .vmxファイルがもし存在しない場合は次のVMへ
if [ ! -f "$path" ]; then
echo "'$path' does not exists"
continue
fi

# バックアップ先ディレクトリ
dest="$backup/$vm"
mkdir -p "$dest"

# 一旦、現時点のvmxをコピーしておく
cp -au "$path" "$dest/$file.tmp"

# バックアップ用のスナップショット
snapshot=$(create_snapshot "$vmid")

# スナップショットIDが""または0の場合は作成に失敗したため次のVMへ
if [ "$snapshot" = "" -o "$snapshot" = "0" ]; then
echo "Failed to create snapshot of '$vmx'"
continue
fi

# .vmemと.vmsnはスナップショットごとに追加されるので蓄積しないように削除
# rm -f "$dest/"*.vmem "$dest/"*.vmsn
# cp -au "$dir/"* "$dest"

# vmdkファイルのコピーだけでも復元する
cp -au "$dir/"*.vmdk "$dir/"*.log "$dest"

# 一時ファイルからスナップショット作成まえの.vmxファイルを復元
mv -f "$dest/$file.tmp" "$dest/$file"

# バックアップ用のスナップショットを削除
$(remove_snapshot "$vmid" "$snapshot")

# 完了したVMを記録
completed="$completed $vm"
done

echo "Backup complated:$completed"