EC2 Ubuntu上にJava版minecraftマルチサーバを構築したメモ

# きっかけ
ふと、久しぶりにminecraftをやりたいなぁと思い、せっかくならマルチプレイ用のサーバをEC2上に構築しようと思った。
その際の構築メモ（備忘録）。

# ざっくり要件
- 自宅でサーバを立てるのはネットワーク要件諸々が厳しかったので、クラウドかつカスタムがしやすいEC2を使用する
- minecraftはJava版の最新バージョンを使用する
- 参加人数は今のところ1~3人程度
- 1日1回程度バックアップを取りたい
- アクティブユーザが少ない平日昼間の時間帯はインスタンスをシャットダウンしてコストを抑えたい

# EC2インスタンスの準備
### インスタンスのスペック
- Ubuntu Server 20.04 LTS
- t3.small
- ボリューム 20GB

### EC2周りの設定

インスタンス作成時にセキュリティグループのインバウンドルールを設定しておく。

- TCP, UDP: 25565
- SSH: マイIP

またElastic IPを発行し、作成したインスタンスと紐付けておく。

# サーバ構築

インスタンスにログイン

```
$ ssh ubuntu@xxx.xxx.xxx.xxx -i key.pem 
```

パッケージを更新

```
$ sudo apt update
$ sudo apt upgrade
```

タイムゾーンを変更

```
$ sudo timedatectl set-timezone Asia/Tokyo
```

必要なパッケージのインストール

```
$ sudo apt install -y default-jdk screen wget
```

minecraft実行用のユーザを作成

```
$ sudo useradd -m -r -d /opt/minecraft minecraft
```

今回作るワールドの名前を`survival`としてディレクトリを作成

```
$ sudo mkdir /opt/minecraft/survival
```

[minecraft公式ページのダウンロードページ](https://www.minecraft.net/en-us/download/server)からjarダウンロード用のURLをコピーしてダウンロード

```
$ sudo wget -O /opt/minecraft/survival/server.jar https://launcher.mojang.com/v1/objects/1b557e7b033b583cd9f66746b7a9ab1ec1673ced/server.jar
```

使用許諾契約に同意するためのファイルを事前に作成

```
$ sudo bash -c "echo eula=true > /opt/minecraft/survival/eula.txt" 
```

オーナーを変更

```
$ sudo chown -R minecraft:minecraft /opt/minecraft/survival/
```

サービス設定ファイルの作成

- サービス名とディレクトリ名とリンクさせる
- `-Xmx1G`は搭載メモリに応じて適宜変更する
- stop時はサーバ内にアナウンスを流し、1分間の猶予の後に止めてセーブする

```/etc/systemd/system/minecraft@.service
[Unit]
Description=Minecraft Server: %i
After=network.target

[Service]
WorkingDirectory=/opt/minecraft/%i

User=minecraft
Group=minecraft

Restart=always

ExecStart=/usr/bin/screen -DmS mc-%i /usr/bin/java -Xmx1G -jar server.jar nogui

ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say 60秒後にサーバがシャットダウンされます"\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say 速やかにログアウトしてください"\015'
ExecStop=/bin/sleep 50
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say 10秒後にサーバがシャットダウンされます"\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say 速やかにログアウトしてください"\015'
ExecStop=/bin/sleep 10
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "save-all"\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "stop"\015'

[Install]
WantedBy=multi-user.target
```

設定ファイルをリロード

```
$ sudo systemctl daemon-reload
```

インスタンス起動時に自動起動するように設定

```
$ sudo systemctl enable minecraft@survival
```

# minecraftサーバの起動

サービスの起動。`survival`を指定する

```
$ sudo systemctl start minecraft@survival
```

statusで状態を確認できる。stopやrestartも使える。

```
$ sudo systemctl status minecraft@survival
$ sudo systemctl stop minecraft@survival
$ sudo systemctl restart minecraft@survival
```

# minecraftの初期設定

起動しているscreenに入る。PIDを調べてアタッチする。
スクリーンから抜ける（デタッチ）時は`Ctrl + A → D`

```
$ sudo -i
# su minecraft
$ screen -ls
$ screen -r [PID]
```

オペレータ権限を付与する。
opが付与されたユーザはゲーム内からも管理コマンドを実行可能。

```
op [ユーザ名]
```

ホワイトリスト設定を使用して、ホワイトリストに追加したminecraftサーバに入れるようにする。
人を追加するときは随時`whitelist add [ユーザ名]`で追加する。

ホワイトリストを有効化

```
whitelist on
```

ホワイトリストにユーザ追加

```
whitelist add [ユーザ名]
```

ここまで設定すれば遊ぶ準備は整っている。
この後はバックアップと起動停止の設定を行う。

# バックアップと起動停止

### インスタンスの自動終了を設定(crontab)
自動起動に合わせてCloudWatch Eventsで設定したかったが、minecraftサーバのセーフティな終了とバックアップを行いたかったのでcrontabで設定した。

バックアップ用のディレクトリを作る

```
$ sudo mkdir /opt/minecraft/backup
```

バックアップ用のシェルスクリプトを用意する。
やっていることは以下の通り。

- サーバを停止後、全体を圧縮したバックアップファイルを生成する。
- バックアップは最新10日間を残して削除する（ファイル個数で判定するほうが良さそう）。
- バックアップ後に引数によってサーバを起動するかシャットダウンを行うようにする。

```/opt/minecraft/minecraft_backup.sh
#!/bin/bash

SERVER_NAME="survival"

sudo systemctl stop minecraft@${SERVER_NAME}
sleep 5

TIMESTAMP=`date +%Y%m%d-%H%M%S`
MINECRAFT_PATH="/opt/minecraft/${SERVER_NAME}"
BACKUP_FILE="/opt/minecraft/backup/${SERVER_NAME}/mc_backup_full_${TIMESTAMP}.tar.gz"
sudo tar cfvz $BACKUP_FILE $MINECRAFT_PATH
sudo chown ubuntu:ubuntu ${BACKUP_FILE}
sudo find /opt/minecraft/backup -name '*.tar.gz' -mtime +10 -delete

if [ "$1" = "start" ]; then
  sudo systemctl start minecraft@${SERVER_NAME}
elif [ "$1" = "shutdown" ]; then
  sudo shutdown -h now
fi
```

スクリプトファイルに実行権限を与えておく

```
$ sudo chmod 644 /opt/minecraft/minecraft_backup.sh
```

cronにスケジュールを設定する。

- 平日午前2時にバックアップ後、インスタンスをシャットダウンする
- 土日午前6時にバックアップ後、再度サーバを立ち上げる  
  ※祝日の考慮は~~めんどくさいので~~CloudWatch Eventsの兼ね合いを考えて行っていない

```
$ crontab -e

0 2 * * 1-5 /usr/bin/sh /opt/minecraft/minecraft_backup.sh shutdown
0 6 * * 6-7 /usr/bin/sh /opt/minecraft/minecraft_backup.sh start
```

### インスタンスの自動起動を設定(CloudWatch Events)
CloudWatch Eventsで設定した。
SSM Automationで新しいロールを作ると権限が足りないエラーになってしまうので、先にIAMでロールを生成する。
`AmazonSSMAutomationRole`のポリシーをアタッチしたロールを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396830/f50977d9-6312-6eef-adda-5a66b91be97c.png)

ロールを作成したら`CloudWatch` -> `イベント` ->　`ルール`で自動起動用のルールを作成する。
以下の画像のように設定する。

- 平日17時に起動するようにCron式を設定する。  
  `0 0 ? * 2-6 *`  
  ※Cron式はGMTなので、JSTにするため+9時間する
- オートメーションパラメータ定数の`instanceId`はEC2インスタンスのIDを入力する
- ロールは先ほどIAMで作成したロールを選択する
![スクリーンショット 2021-05-17 14.19.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396830/74bb2ca7-3d8a-5135-7242-df5b109c749c.png)

# 感想
メモリは最低2GBの割り当ては必要かな〜と考えていたが、1GBでも3人程度なら今のところほぼ快適に動作している。
ただ、負荷がかかる処理をするとメモリが割と枯渇気味になるので、更にワールドを拡張したり人数が増える際にインスタンスタイプを上げる必要があると思う。

# 参考にさせていただいたサイト
https://goto-linux.com/ja/2019/10/18/ubuntu-20.04-minecraft%E3%82%B5%E3%83%BC%E3%83%8F%E3%83%BC%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%95/

https://qiita.com/hayao_k/items/9a347d61b5b09a4e7755