はじめに
Minecraftサーバーを長く運用していると、ワールドデータのバックアップをどうするかは悩みどころです。
僕の環境では、
- 日次でバックアップしたい
- 半年前の状態まで遡れるようにしたい
- VPS本体のストレージはあまり消費したくない
というような要件(欲求)がありました。
そして、今回構築したのはGoogle Cloud Storage(GCS)への自動バックアップです。個人のMinecraftサーバーでここまでやる必要があるかというと正直微妙ですが、自分のサーバーではこういう構成でバックアップしているという一例として紹介しようと思います。
バックアップの全体構成
全体の構成は次のようになっています。

VPS上でバックアップスクリプトを実行して、ワールドデータを圧縮、GCSへアップロードしています。
GCS上の古いバックアップデータはGCS側のLifecycle Ruleで削除することで、バックアップスクリプトをシンプルにできています。
Daily・Weekly・Monthlyの3世代で管理する
バックアップは次の3種類に分けています。
| 種類 | 保存頻度 | 保存期間 | ストレージクラス |
|---|---|---|---|
| Daily | 毎日 | 7日 | STANDARD |
| Weekly | 毎週 | 30日 | NEARLINE |
| Monthly | 毎月 | 180日 | COLDLINE |
直近は細かく復元できるようにDailyを残し、時間が経ったものはWeekly・Monthlyだけを残す形です。
GCSのStorage Classを保存期間に合わせて分けています。
これによって、すべてのバックアップを180日保存するより、ストレージ使用量を抑えながら長期間の復元ポイントを確保できます。
Daily・Weekly・Monthlyでは保存期間が異なるため、最低保存期間と保管コストを考慮してストレージクラスを使い分けています。短期間で削除するDailyにはSTANDARD、30日程度保持するWeeklyにはNEARLINE、長期間保持するMonthlyにはCOLDLINEを使用しています。
GCSのバックアップ環境構築
GCSのバケットやService AccountはTerraformで管理しています。
3つのGCSバケットを作成する
Daily・Weekly・Monthly用に、それぞれバケットを作成します。
例えばDailyは次のような設定です。
resource "google_storage_bucket" "daily" {
name = var.bucket_name_daily
location = var.bucket_location
storage_class = "STANDARD"
uniform_bucket_level_access = true
public_access_prevention = "enforced"
lifecycle_rule {
condition {
age = 7
}
action {
type = "Delete"
}
}
}
WeeklyとMonthlyもほぼ同じで、Storage Classと保存期間だけ変えています。
バックアップ用Service Accountを作成する
VPSからGCSへアクセスするため、専用のService Accountを作成します。
resource "google_service_account" "backup_uploader" {
account_id = "minecraft-backup-uploader"
display_name = "Minecraft Backup Uploader"
}
Service Accountには、
roles/storage.objectCreator
のみを付与しています。
必要なのはバックアップファイルのアップロードだけなので、Storage Adminのような強い権限は与えていません。
Minecraftのバックアップスクリプト
バックアップ処理全体の流れ
バックアップスクリプトは次の順番で動きます。
1. 稼働中にワールドをrsync
2. save-off
3. save-all flush
4. 保存完了を待つ
5. もう一度rsync
6. save-on
7. zstdで圧縮
8. GCSへアップロード
9. Discordへ通知
VPS から GCS へのアップロードには Google Cloud CLI を使用しています。
ここでは省略しますが、あらかじめ Service Account で認証し、対象のバケットへアップロードできる状態にしておきます。
稼働中にワールドを事前同期する
まずMinecraftサーバーを動かしたまま、ワールドデータを一時ディレクトリへコピーします。
rsync -a --delete \
"$SERVER_DIR/$world/" \
"$SNAPSHOT_DIR/$world/"
この時点ではまだMinecraft側でファイルが変更されるため、これだけでは最終的なバックアップにはしません。
ここではデータの大部分を先にコピーしておくことが目的です。
save-offで自動保存を一時停止する
次にMinecraftへ、
save-off
を送信します。
これによってMinecraftサーバーのファイルへのデータ自動保存がオフになります。
save-all flushの完了を待つ
続けて、
save-all flush
を実行します。
コマンドを送るだけではなく、ServerのLogに、
Saved the game
が出たことを確認してから次へ進みます。
このログが出るまではワールドデータの書き込みが完了しておらず、ファイルが書き換わる可能性があるためです。
また、このとき過去のログを誤検知しないように、コマンド送信後に追加された行だけを見るようにしています。
もう一度rsyncして差分を同期する
保存が完了したら、もう一度同じrsyncを実行します。
1回目の同期から変更されたファイルだけがコピーされるため、ここでは比較的短時間で処理できます。
save-onで自動保存を再開する
2回目の同期が終わったら、
save-on
を送信して自動保存を再開します。
ここまで終われば、あとはMinecraft Server本体とは切り離して、同期先のデータでバックアップ処理を続けられます。
zstdでワールドデータを圧縮する
一時ディレクトリに作成したワールドデータを、tar + zstdで圧縮します。
tar --use-compress-program="zstd -T0 -10" \
-cf "$BACKUP_FILE" \
-C "$SNAPSHOT_DIR" \
"${WORLDS_TO_BACKUP[@]}"
GCSへアップロードする
完成したバックアップをGCSへアップロードします。
gcloud storage cp "$BACKUP_FILE" "$DAILY_BUCKET/"
必要な日であればWeeklyやMonthlyにも同じファイルをアップロードします。
以下は毎月1日にMonthlyへアップロードする例です。
if [ "$DAY_OF_MONTH" = "01" ]; then
gcloud storage cp "$BACKUP_FILE" "$MONTHLY_BUCKET/"
fi
同じバックアップファイルを複数のバケットへ送っているだけなので、圧縮処理を何度も行うわけではありません。
save-offの時間を短くするために2段階でrsyncする
今回の構成で少し悩んだのが、圧縮前のローカルスナップショットをどう作るかでした。
本当はLVMやZFS、Btrfsなど、ボリュームやファイルシステム側が持つSnapshot機能を使いたいと考えていました。
それが使えれば、
save-off
↓
save-all flush
↓
Snapshot作成
↓
save-on
のように、かなり短時間である時点の状態を切り出せます。
あとはそのSnapshotをゆっくり圧縮してGCSへ送ればよいため、今回のバックアップ構成とも相性がよさそうでした。
ただ、現在のVPSは最初からその前提でストレージを構築していません。
既存環境へ導入するにはストレージ構成の変更やデータ移行が必要になるため、バックアップのためだけにそこまで変更するのは少し面倒だったため、今回はrsyncを2回行う方式で妥協しています。
Minecraft稼働中
↓
1回目のrsync
↓
save-off
↓
save-all flush
↓
2回目のrsync
↓
save-on
異常終了しても必ずsave-onする
バックアップスクリプトで特に避けたいのが、
save-off
↓
途中でエラー
↓
save-onされない
という状態です。
自動保存が停止したままサーバーが動き続けるのは危険なので、Bashのtrapを使っています。
スクリプト開始時に、
trap cleanup EXIT
を設定しています。
cleanup では、バックアップ処理の途中で異常終了した場合に備えて、自動保存の復旧( save-on )とDiscordへの失敗通知を行っています。
特に save-off 実行後にスクリプトが停止すると、Minecraftの自動保存が無効なまま残る可能性があるため save-off を実行したら必ず save-on が実行されるようにしているのです。
バックアップ結果をDiscordへ通知する
自動化すると、今度はバックアップが失敗していることに気づきにくくなります。
そこで実行結果をDiscordへ通知しています。
正常終了した場合は、

という通知を送って、途中でエラーになった場合は、

という通知を送るようにしています。
systemd timerでバックアップを自動実行する
バックアップはsystemd timerから毎日実行しています。
systemd serviceを作成する
Serviceはoneshotです。
[Service]
Type=oneshot
User=minecraft
Group=minecraft
ExecStart=/opt/minecraft-backup/backup.sh
StandardOutput=journal
StandardError=journal
毎日4時にtimerを実行する
Timerは次のようにしています。
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
これで毎日午前4時にバックアップが実行されます。
あとはこれらを有効化するだけです。
sudo systemctl daemon-reload
sudo systemctl enable --now minecraft-backup.timer
バックアップが正常に動いているか確認する
GCSに保存されたバックアップを確認する
Google Cloud Consoleから各バケットを開き、バックアップが作成されていることを確認します。
VPS側のService Accountにはroles/storage.objectCreatorしか付けていないため、VPSからはオブジェクト一覧を取得できない点には注意が必要です。
Discord通知を確認する
最後にDiscordにも成功通知が届いていれば、一連の処理が正常に動いていることを確認できます。
バックアップからワールドを復元する
復元するときは、まずGCSから対象のバックアップをダウンロードします。
gcloud storage cp \
gs://YOUR_BUCKET/BACKUP_FILE \
/tmp/minecraft-backup
その後、tar + zstdで展開します。
mkdir /tmp/minecraft-restore
tar --use-compress-program=zstd \
-xf /tmp/minecraft-backup \
-C /tmp/minecraft-restore
Minecraftサーバーを停止し、現在のワールドを退避してから復元したワールドへ置き換えます。
mv /opt/minecraft/world \
/opt/minecraft/world.old
cp -a /tmp/minecraft-restore/world \
/opt/minecraft/world
バックアップは取れているだけでは意味がないので、一度は実際に展開できることも確認しておくと安心です。
まとめ
今回は、自分のMinecraftサーバーで使っているGCSへの自動バックアップ構成を紹介しました。
GCSを使ってDaily・Weekly・Monthlyまで保存するのは、個人のMinecraftサーバーとしてはかなり手厚い構成だと思います。
あくまで、Minecraftサーバーのバックアップ構成を考える際の、一つの実例として参考になればと思います。
