Azure 上の Minecraft マルチサーバを IFTTT でいい感じに制御して節約する

![GnhyNVj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/3c40d1ed-83d8-ae76-e6e8-c572a31e110a.png)

# はじめに

Azure の仮想マシンを使って、 Minecraft (Spigot) マルチサーバを立ち上げて運用しています。

参加メンバーは全員会社勤めなので、平日の日中はサーバを停止して、「平日の夜・土日の終日」の間だけ自動でサーバが立ち上がるようにしています。

今回は、サーバの開始・起動スケジュールを [IFTTT](https://ifttt.com) の DateTime トリガーと連動するため **Azure Automation** を使い、更に **仮想マシンの起動と終了に連動する Minecraft サーバ** を構築しました。

最終的にはサーバの起動通知を Discord に送るようにしてメンバーが確認できるようにしています。

この記事では、以下の手順で解説しています（基礎知識的な部分は省略しています）。

- 仮想マシンの構築
    - VM の起動と終了に連動した Minecraft サーバを構築します
- Azure Automation で Webhook を作成する
    - VM の起動と終了を行い、Discord に Webhook で通知する Runbook を構築します
- IFTTT で Webhook のトリガーを作成する

# 仮想マシンの構築

今回作成した VM は以下のようなスペックでした。

- SKU: Standard D2s v3 (2 vcpu 数、8 GiB メモリ)
- Region: 東日本
- Image: Ubuntu Server 18.04 LTS

D2s v3 で `¥9,166/月` ですが、平日は 6h 程しか稼働しないため大体 300h/月 となり、月額のおよそ 1/2 程度で運用しています（~~価格だけ見たらやっぱりちょっと高い気もする~~）。

## Spigot サーバの構築

今回は Docker を使って構築しました。
[nimmis/docker-spigot](https://github.com/nimmis/docker-spigot) を Clone して `docker-compose.yml` の値を変更するだけで終わりです。

```bash:VM上にCloneする
$ sudo mkdir -p /minecraft
$ sudo chown -R mcserver:mcserver /minecraft
$ git clone --depth=1 https://github.com/nimmis/docker-spigot /minecraft
```

```yaml:docker-compose.yml
version: "2"
services:
  spigot-main:
    container_name: "spigot-main"
    build: "."
    ports:
      - 80:8123
      - 25565:25565
    environment:
      - MC_MAXMEM=8g
      - MC_MINMEM=1g
      - SPIGOT_AUTORESTART=yes
      - SPIGOT_VER=1.14.4
      - SPIGOT_UID=1000
    volumes:
      - "./server:/minecraft"
    restart: "always"
```

これで `docker-compose up` すれば `spigot.jar` のビルドが走り、以降はサーバが自動で立ち上がるようになっています。

## 終了時スクリプト

仮想マシンが停止しようとする時に、Minecraft サーバのワールドデータを `/save-all` した上で終了する必要がありますが、今回利用したイメージは、コンテナが Stop する際にそのあたりを自動でやってくれています。

なので終了時スクリプトを書いてシャットダウン時にコンテナを停止するようにします。

**終了時スクリプトの作成**

```bash
$ sudo vim       /etc/init.d/stop-mcserver.sh
$ sudo chmod a+x /etc/init.d/stop-mcserver.sh 
$ sudo ln -s     /etc/init.d/stop-mcserver.sh /etc/rc6.d/K99stop-mcserver
```

```bash:/etc/init.d/stop-mcserver.sh
#!/bin/bash

cd /minecraft

/usr/local/bin/docker-compose stop
```

また、 `restart: "always"` なので次回起動時に自動的にコンテナが立ち上がって Minecraft サーバが復帰するため、これで自動起動・終了の仕組みが完成します！！！

### しなかった

原因が未だに分からないんですが、Azure の仮想マシンが再起動した後に何故かコンテナが自動で立ち上がりません。

ですが、SSH して `docker ps` などを叩くと __微妙なラグの後にコンテナが起動__ したため、おそらく Docker daemon のプロセスが正しく起動していないのではと考えられます。

## 起動時スクリプト

なので、起動時に Docker API を叩く無害なコマンドを実行させることで無理やり Docker daemon を起動させています。

これで、仮想マシンが再起動した後もコンテナが自動で立ち上がるようになりました。

**起動時スクリプト**

```bash
$ crontab -e
```

crontab の中身:

```bash
# m h  dom mon dow   command
@reboot /usr/bin/docker --version > dev/null 2>&1
```

# Azure Automation で仮想マシンを起動・終了させる

Azure Automation の Runbook でこれらの内容を実装します。

- サーバを起動させる
- サーバを停止させる
- 起動・停止したら Discord の Webhook で通知する
- Automation 開始トリガーを Incoming Webhook にする

## サーバを起動・停止させる

これは、 Runbook ギャラリーの人気上位にある 2つのスクリプトをインポートすれば終わりです。

<img width="697" alt="KENh7Yw.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/0c1d3424-5e97-6d9c-7ba9-1d1f5735a368.png">

ですが、インポートしてきた Runbook は Azure の仕様変更に合わせて更新されておらず、正しく起動・停止できたのにも関わらず Automation ジョブが Error で終わるようになっています。

![NZjTQR0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/e987b371-2f74-1741-5512-8049288bba1b.png)

原因は **Notify VM Started/Stopped** ノードの実行条件式が古いためで、これを現在の ActivityOutput のプロパティに合わせることで解決します（あと **Notify Failed To Start/Stop** ノードあたりのエラー内容の取得方法も古かったため直しました）。

## Discord の Webhook で通知する

※ Webhook は作成済みです

以下のような感じでそれぞれの出力メッセージを IDictionary -> JSON に変換して、その内容を Webhook で Discord に通知しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/961812bf-004c-2ee4-b079-628e5f194873.png" alt="RbvYPhT.png" width="500">

### Format message

`Write-Output` ノードを使い、Discord の Webhook の仕様に合わせてコンテンツを作成。

```powershell:PowerShell式
@{
  content = $_
}
```

### ConvertTo-Json

Format message からアクティビティの出力を受け取る

### Notify to webhook

`Invoke-WebRequest` ノードを使い、ConvertTo-Json のアクティビティの出力を `POST` で送信。
Content-Type に `application/json; charset=utf-8` を指定しないと日本語が文字化けする。
あと `UseBasicParsing` フラグも `True` にしないとジョブが正しく終わらなかった（はず）。

## 開始トリガーの Incoming-Webhook を作成する

それぞれの Runbook で Webhook を作成して動くかどうか確認する。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/88593f35-5bfc-0ec6-a98d-b60e29847012.png" alt="LvlPIZ2.png" width="700">

```bash
$ curl -X POST -H "Content-Type: application/json" https://WEBHOOK
```

このように通知されて仮想マシンが起動・終了すれば OK（Runbook 上のメッセージを日本語に変えています）。
<img width="600" alt="pI3rVqX.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/c39d8564-88b4-b107-fc11-4ecfe0ffa4a7.png">

それぞれの Webhook URL を控えておきます。

# IFTTT のトリガーを作成する

IFTTT の **DateTime** トリガーから **Every day of the week at** を選択します。

<img width="504" alt="スクリーンショット 2020-01-15 15.49.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/d77d5d87-7e4b-03fb-97c4-632163b781fc.png">

曜日と時間が選択出来るので、例えば **平日の19時に実行するようなトリガー** を作成します。
<img width="509" alt="スクリーンショット 2020-01-15 15.51.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/9894a5d5-3c0e-e932-9d59-4f135a8e38e9.png">

次に実行するアクションで **Webhook** を選択し、先程の Webhook URL を設定します。
<img width="509" alt="スクリーンショット 2020-01-15 15.52.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/2417bcef-24be-4b07-ce4e-9f694eda11aa.png">

これを複数個作成し、1週間分の起動・停止スケジュールを作成すれば完成です。
![GnhyNVj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/3c40d1ed-83d8-ae76-e6e8-c572a31e110a.png)

# ちなみに

Azure Automation 自体にもスケジュール実行機能が備わっていますが、~~正直使い勝手が良くないのと~~ スマートフォン上から編集しづらいので IFTTT 経由でスケジュールを編集出来る方がより簡単です。

完全に蛇足ですが仮に祝日判定などの機能追加をする場合も、Runbook 上で構築するよりも Discord の Bot を別途ホストして独自でスケジューリングを実装するのが良いと思います。