MinecraftのWorldsのBackupを取得する

MinecraftのWorldsのBackupを取得する

Intro

Cloud上のDocker内で稼働しているMinecraftのワールドの増分バックアップをNASに保存し続けたい。

Tweet

ところで、最近三体を読み始めた。現代版銀河英雄伝説 (物理機構の説明多め) という感じで面白い。

TL; DR

  • Docker内のワールドデータをDocker外にコピーする
  • Docker外のワールドデータをNASにコピーする
  • 増分バックアップを行う

サーバー環境

まず、該当のマイクラサーバーの環境を確認する。

KeyValue
Minecraft VersionBedrock
PlatformCloud Server
OSUbuntu 22.04 LTS
AppDocker
Docker Imageitzg/minecraft-bedrock-server

さらに、マイクラ鯖へはsshにてアクセスできる。
鯖は毎日稼働しているとは限らず、同時にWorldデータが毎日更新されるとも限らない。
(マイクラを遊ぶ頻度はそれほど高くない。ただし、一定期間では数日間連続で遊ぶことで、ワールドデータが大幅に更新されるかもしれない。)

NASは常時稼働しており、Linux likeなOSを搭載している。
あくまでLinux likeなので、Linuxのすべてを兼ね備えているわけではないが、主要なコマンドは搭載されている。

バックアップ方針

以下にバックアップに関する前提を記載する。

  • バックアップデータはNASに保存する
    • 1GBを超えないことが見込める以上、Google Driveなどでもよかったかもしれない
    • マイクラ鯖の停止中にもバックアップにアクセスしたいので、マイクラ鯖と同じServer上に保存するのは避ける
  • バックアップシステムはNAS上で稼働すると簡単
    • 今回に限ってはマイクラ鯖と同じServer上で稼働させてもよいか
  • 増分バックアップ方式を採用する
  • マイクラのワールドデータは平常時はDocker Volume内に存在している点に注意する

Tweet

当初は、Smart Cycle Backup (新しいバックアップは密に、古いものは疎になるように)によって、
保存するバージョン数を抑制しながら、全期間のデータを保持することも方針に含めていた。

しかし、マイクラのワールドデータはたかだか数百MB程度であり、増分バックアップで全期間保存しても問題ないことが判明してしまった。
Smart Cycle Backupのアルゴリズムまで考えていたのに……。

思うに、Smart Cycle Backupは、差分バックアップやフルバックアップの保持に特に適しているだろう。
保存容量が数十程度は見込めつつ、数千は難しい場合に適する。

実装

to Docker外

まず、Docker Volume内にあっては外部からアクセスしづらいので、一度Docker外にワールドデータをコピーする。

minecraftWorldsCopyFromDocker.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

cd $(cd $(dirname $0); pwd)/..
dt_iso8601=$(date -u +"%Y-%m-%dT%H-%M-%SZ")
container_names=($(docker ps --format "table {{.Names}}" --filter ancestor="itzg/minecraft-bedrock-server") )
if [[ ${#container_names[@]} -eq 1 ]]; then
echo "No docker container found"
else
echo "Found docker container: ${container_names[@]}"
container_name=${container_names[1]}
echo "Copy from docker container: $container_name"
mkdir -p tmp/
rm -rf tmp/worlds*
docker cp ${container_name}:data/worlds tmp/
fi

このShell ScriptはDOCKER_ROOT/scriptに存在している。
(docker-compose.ymlDOCKER_ROOT直下に配置されている)

`tree . -L 2`

このScriptは、最小の頻度ならばバックアップを取得するたびに、最大の頻度ならば毎日定時に実行される。
バックアップするシステムとして何らかのアプリケーションを利用する場合、
稼働時にssh先のScriptを実行するのは難しいので、Server側で定時実行することになる。
(この場合、定期実行の時間の設定がバックアップシステム側とServer側の両方に存在するのが少しだけ面倒)

Tweet

今回はDockerコンテナを前提にしているので考慮する必要もなかったが、
Docker Volume内のデータを外部にコピーする方法は、過去記事を参照。

実行ファイル設置場所の絶対path取得

上記Scriptでは、$(cd $(dirname $0); pwd)/..で、Scriptの絶対pathの親ディレクトリを取得している。

まず$0で呼び出されたScriptのpathを取得する。
これは呼び出され方に応じた値が入る。相対pathであったり、絶対pathであったりする。

次にdirnameでpathのディレクトリ部分を取得する。
$0script/tmp.shの場合、dirname $0scriptになる。
script内で./tmp.shとして実行していた場合、dirname $0.になる。
そしてscript内でbash tmp.shとして実行していた場合、dirname $0(空文字)になる。

もうお気付きだろう。
最後のケースに$(dirname $0)/..とすると、これは/..になり、結果として/とみなされる。
ただ実行ファイルの親ディレクトリに移動したかっただけなのに、Rootディレクトリに対して操作する羽目になるのだ。あぶない!!

$(cd $(dirname $0); pwd)とすると、sub shell内でdirname $0の結果に対してcdを実行し、その後pwdで絶対pathを取得する。
これで、Scriptのフォルダの絶対pathを取得できる。
つまり、(Script自体がRootに置かれていない限り)$(cd $(dirname $0); pwd)/..で安全に親ディレクトリを取得できるというわけだ。

to NAS

ServerからNASへのバックアップは、rsyncを利用する。

minecraftWorldsBackupToNAS.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash

cd /home/NAS_USER/Folder/Backup/minecraft/worlds_backup/
# execute script on server
ssh SSH_HOSTNAME bash -c "/home/SERVER_USER/work/docker/mcs/script/minecraftWorldsCopyFromDocker.sh"
if [[ $? -ne 0 ]]; then
echo "Failed to ssh connect to SSH_HOSTNAME"
exit 1
fi
# backup to local
rsync --delete -ave ssh SSH_HOSTNAME:/home/SERVER_USER/work/docker/mcs/tmp/worlds tmp/
while read worldName; do
echo "worldName: $worldName"
if [[ ! -d tmp/worlds/$worldName ]]; then
echo "No such directory: tmp/worlds/$worldName"
continue
fi
mkdir -p data/$worldName/latest/data/
mkdir -p data/$worldName/archives/
rsync --delete -av tmp/worlds/$worldName data/$worldName/latest/data/
date -r data/$worldName/latest/data/$worldName/db/CURRENT +"%Y-%m-%dT%H-%M-%S" \
> data/$worldName/latest/dt.dat
(
cd data/$worldName && backupDiff.sh
)
done < <(find data/ -maxdepth 1 -mindepth 1 -type d -printf "%f\n")

# server info
mkdir -p server_info/latest/data
mkdir -p server_info/archives
rsync --delete -ave ssh SSH_HOSTNAME:/home/SERVER_USER/work/docker/mcs/docker-compose.yml server_info/latest/data/
rsync --delete -ave ssh SSH_HOSTNAME:/home/SERVER_USER/work/docker/mcs/script server_info/latest/data/
find server_info/latest/data/ -type f -printf "%T@ %p\n" | sort -n | tail -1 | awk "{print \$1}" | xargs -I {} date -d @{} "+%Y-%m-%dT%H-%M-%S" > server_info/latest/dt.dat
(
cd server_info && backupDiff.sh
)

まずはじめにsshでServerに接続し、minecraftWorldsCopyFromDocker.shを実行する。
ここでエラーが生じた場合は、バックアップを取得できないので、Scriptを終了する。
続いて、マイクラのワールドデータをまるごとNASの仮置き場にコピーする。

バックアップ対象のワールドごとに、最新のデータをdata/$worldName/latest/data/にコピーする。
さらに、そのワールドの最終更新日時をCURRENTから取得し、data/$worldName/latest/dt.datに記録する。

Tweet

ついでにdocker-compose.ymlやscriptもバックアップすることにした。

増分バックアップ

backupDiff.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

base_dir=$(pwd .)
latest_archive=$(find ${base_dir}/archives/ -maxdepth 1 -mindepth 1 -printf "%p\n" | sort | tail -n 1)

if [[ -z ${latest_archive} ]]; then
echo "No latest archive found"
rsync -av latest/data/ archives/$(cat latest/dt.dat)/
elif [[ $(cat latest/dt.dat) = $(basename ${latest_archive}) ]]; then
echo "No change in latest archive: $(cat latest/dt.dat)"
else
rsync -avh --link-dest ${latest_archive} ${base_dir}/latest/data/ \
${base_dir}/archives/$(cat latest/dt.dat)/
fi

保存済みの最新アーカイブを取得し、それをもとに増分バックアップを行う。
最新アーカイブが存在しない場合は、全データをコピーする。
最新アーカイブと取得データの日時が一致する場合は、変更がないと判断し、何もしない。

User作成Script保管場所

SUSEのドキュメントによると、
~/binにユーザーが作成したScriptを保管するのが推奨されるそうだ。

/usr/local/binのようなユーザーフォルダ外部にScriptを補完するのは不安になるので、これはうれしい。
ただ、cronに実行する場合など、トリガーがUserに依存しない場合は、
ユーザーフォルダ内部でない方が自然な気がしないでもない。

定期実行

Server側で定期実行するために、crontabを利用する。sudo crontab -eで設定ファイルを開き、以下のように記述する。
下記では毎週日曜日、月曜日、水曜日、土曜日の3時にバックアップを取得する。

crontab
1
0 3 * * 0,1,3,6 /home/NAS_USER/bin/minecraftWorldsBackupToNAS.sh

Future Work

  • バックアップデータの保存先をGoogle Driveなどに変更する
    • あるいは、Google Driveに抽出したバックアップを保存する
  • 定期的にFull Backupを取得することで、古いデータが破損した場合の影響を抑制する
  • ScriptをGitHubで公開する
    • できれば、Smart Cycle Backupのアルゴリズムも実装したいなぁ
    • Google DriveにExportする場合と組み合わせればよいか?
  • 直接は関係ないけど、docker-compose.ymlもバックアップすべきか

まとめ

マイクラのワールドデータのバックアップをNASに保存する方法を記載した。
NASに保存することで、マイクラ鯖の停止中にもバックアップにアクセスできるようになる。
また、増分バックアップを採用することで、バックアップデータの保存容量を抑制しつつ、全期間のデータを保持することができる。

コメント