Intro
Cloud上のDocker内で稼働しているMinecraftのワールドの増分バックアップをNASに保存し続けたい。
ところで、最近三体を読み始めた。現代版銀河英雄伝説 (物理機構の説明多め) という感じで面白い。
TL; DR
- Docker内のワールドデータをDocker外にコピーする
- Docker外のワールドデータをNASにコピーする
- 増分バックアップを行う
サーバー環境
まず、該当のマイクラサーバーの環境を確認する。
Key | Value |
---|
Minecraft Version | Bedrock |
Platform | Cloud Server |
OS | Ubuntu 22.04 LTS |
App | Docker |
Docker Image | itzg/minecraft-bedrock-server |
さらに、マイクラ鯖へはsshにてアクセスできる。
鯖は毎日稼働しているとは限らず、同時にWorldデータが毎日更新されるとも限らない。
(マイクラを遊ぶ頻度はそれほど高くない。ただし、一定期間では数日間連続で遊ぶことで、ワールドデータが大幅に更新されるかもしれない。)
NASは常時稼働しており、Linux likeなOSを搭載している。
あくまでLinux likeなので、Linuxのすべてを兼ね備えているわけではないが、主要なコマンドは搭載されている。
バックアップ方針
以下にバックアップに関する前提を記載する。
- バックアップデータはNASに保存する
- 1GBを超えないことが見込める以上、Google Driveなどでもよかったかもしれない
- マイクラ鯖の停止中にもバックアップにアクセスしたいので、マイクラ鯖と同じServer上に保存するのは避ける
- バックアップシステムはNAS上で稼働すると簡単
- 今回に限ってはマイクラ鯖と同じServer上で稼働させてもよいか
- 増分バックアップ方式を採用する
- マイクラのワールドデータは平常時はDocker Volume内に存在している点に注意する
当初は、Smart Cycle Backup (新しいバックアップは密に、古いものは疎になるように)によって、
保存するバージョン数を抑制しながら、全期間のデータを保持することも方針に含めていた。
しかし、マイクラのワールドデータはたかだか数百MB程度であり、増分バックアップで全期間保存しても問題ないことが判明してしまった。
Smart Cycle Backupのアルゴリズムまで考えていたのに……。
思うに、Smart Cycle Backupは、差分バックアップやフルバックアップの保持に特に適しているだろう。
保存容量が数十程度は見込めつつ、数千は難しい場合に適する。
実装
to Docker外
まず、Docker Volume内にあっては外部からアクセスしづらいので、一度Docker外にワールドデータをコピーする。
minecraftWorldsCopyFromDocker.sh1 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.yml
はDOCKER_ROOT
直下に配置されている)

このScriptは、最小の頻度ならばバックアップを取得するたびに、最大の頻度ならば毎日定時に実行される。
バックアップするシステムとして何らかのアプリケーションを利用する場合、
稼働時にssh先のScriptを実行するのは難しいので、Server側で定時実行することになる。
(この場合、定期実行の時間の設定がバックアップシステム側とServer側の両方に存在するのが少しだけ面倒)
今回はDockerコンテナを前提にしているので考慮する必要もなかったが、
Docker Volume内のデータを外部にコピーする方法は、過去記事を参照。
上記Scriptでは、$(cd $(dirname $0); pwd)/..
で、Scriptの絶対pathの親ディレクトリを取得している。
まず$0
で呼び出されたScriptのpathを取得する。
これは呼び出され方に応じた値が入る。相対pathであったり、絶対pathであったりする。
次にdirname
でpathのディレクトリ部分を取得する。
$0
がscript/tmp.sh
の場合、dirname $0
はscript
になる。
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.sh1 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/
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
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")
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
に記録する。
ついでにdocker-compose.ymlやscriptもバックアップすることにした。
増分バックアップ
backupDiff.sh1 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
|
保存済みの最新アーカイブを取得し、それをもとに増分バックアップを行う。
最新アーカイブが存在しない場合は、全データをコピーする。
最新アーカイブと取得データの日時が一致する場合は、変更がないと判断し、何もしない。
SUSEのドキュメントによると、
~/bin
にユーザーが作成したScriptを保管するのが推奨されるそうだ。
/usr/local/bin
のようなユーザーフォルダ外部にScriptを補完するのは不安になるので、これはうれしい。
ただ、cronに実行する場合など、トリガーがUserに依存しない場合は、
ユーザーフォルダ内部でない方が自然な気がしないでもない。
定期実行
Server側で定期実行するために、crontabを利用する。sudo crontab -e
で設定ファイルを開き、以下のように記述する。
下記では毎週日曜日、月曜日、水曜日、土曜日の3時にバックアップを取得する。
crontab1
| 0 3 * * 0,1,3,6 /home/NAS_USER/bin/minecraftWorldsBackupToNAS.sh
|
Future Work
まとめ
マイクラのワールドデータのバックアップをNASに保存する方法を記載した。
NASに保存することで、マイクラ鯖の停止中にもバックアップにアクセスできるようになる。
また、増分バックアップを採用することで、バックアップデータの保存容量を抑制しつつ、全期間のデータを保持することができる。