チャチャチャおもちゃの抹茶っちゃ

ゲームのこととかプログラミングのこととか。気が向いたら書く。ブログタイトルは友人が考えました。

【GCP】Minecraft鯖の構築とDiscord連携【Minecraft】【Discord】

やりたいこと

Google Cloud Platform(以下GCP)」の「Google Compute Engine(以下GCE)」を用いてMinecraftサーバーを構築したい。常時起動していてはお金がかかって大変なので、遊ぶときだけ起動したいが、自分以外の人が遊ぶときでも起動できるようにはしておきたい。そこで、Discordのチャットにコマンドを打ち込むことで、そのときだけMinecraftサーバーを起動できるようにしたい。

経緯

今まで自分のPC上にサーバーを構築して、ポート開放一時的にしたりとかしてマイクラマルチプレイをしていた。けれども、これではぼくがPCを起動しっぱなしにしなければならないし、他の人が自発的に遊べないのでクラウドとかにサーバーがほしいとおもった。

あとセキュリティ的怖さも・・・・・・ね。

サークルの方ではWeb管理係なるものになったようだし、サーバー関係のお勉強も兼ねて構築してみることにした。

クラウドサービスの中でGCPを選んだ理由としては、GCPが初回一年は約3万円分ほど無料で使えるのと、小さな仮想マシンであれば一年目以降も無料で使えるようだったので、有料分はマイクラ鯖用、無料分はDiscordのコマンドbot用に使えそうだということで試すことにした。(あと既存のアカウントで使えたというのもある)

前提

Googleアカウントを持っていて、GCPが利用できる状態になっている(クレカ登録とかが済んでいる)こと。GCEのインスタンス作成まではできる状態であること。

Minecraftサーバーの構築

こちらは自分のパソコンでサーバーを立てていたのとほぼ同じ手順でやっていたのでさほど問題なく構築できた。
基本的には

cloud.google.com

の通りにやっていく感じ。

インスタンスの作成

イクラサーバーは無料分のスペックの低いインスタンスだとうまく動かすのは難しいようなので、「n1-standard-1」を選択。

OSは個人的趣向でCentOS7。まあお好きなLinux使えばいいんじゃないでしょうか。追加ディスクはやってないのでデフォの10GBのみ。

あとでファイアウォールでポートの設定をするためにネットワーキングタグの設定も忘れずに(あとでもできるけど)。

f:id:mattyan1053:20190218195012p:plain
インスタンスの作成

静的IPアドレスの設定

IPアドレス変わりまくるとめんどうですよねって話。 GCPコンソールの左上からメニューを開いて、「ネットワーキング」→「VPCネットワーク」→「外部IPアドレス」で、「静的アドレスを予約」をクリック。適当な名前を設定したらインスタンスと同じリージョンを選択して、接続先に先程作成したインスタンスを設定しましょう。

サーバー本体の設定

インスタンスを作成したら起動します。必要なものをインストールしていきましょう。 SSHで接続する。GCPのコンソールからでOK。「SSH」をクリッククリック。

# スーパーアカウントにする
$ sudo su -
# yumのアップデート
$ yum update
# 必要なものをそれぞれインストールする
# Javaのインストール。JREあればOK。yum serach javaとかすれば出てくるはずなので自分にあうものをインストール。default-jreとか?とりあえずここではJDK
$ yum install java-1.8.0-openjdk
# バックグラウンドで動かしてほしいのでscreen
$ yum install screen
# 新しくディレクトリを作ってそこにサーバーをダウンロード
$ cd /home
$ mkdir minecraft
$ cd minecraft
$ curl -OL マイクラサーバーのURL
# 初回起動からeula.txtの書き換え
$ java -Xms1G -Xmx3G -d64 -jar server.jar nogui
$ vi eula.txt # 変数eulaの値をfalseからtrueに。

これでOK。サーバーのURLは

Minecraft Server Download | Minecraft

で確認してやりましょう。

ファイアウォールの設定

Minecraftはデフォルトだとポート25565を使うので許可しておかないといけない(変更したければserver.propertiesを書き換え)。 「ネットワーキング」→「VPCネットワーク」→「ファイアウォールルール」を開き、「ファイアウォールルールを作成」する。ターゲットタグのところに、先程インスタンスに設定したネットワーキングタグを設定しておけばよい。ソースIPの範囲は0.0.0.0/0、許可するのはtcp:25565。これで作成すればOK。

f:id:mattyan1053:20190218200612p:plain
ファイアウォールルールの作成

インスタンスの起動、終了時に自動でMinecraftサーバーの起動、終了を行う

インスタンスの設定で、カスタムメタデータのところを編集すればOK。

startup-scriptというキーで値を

cd /home/minecraft
screen -d -m -S mcs java -Xms1G -Xmx3G -d64 -jar server.jar nogui

というものを作成する。これで起動時にサーバーの起動もしてくれる。
次にshutdown-scriptというキーで

screen -r -X stuff '/stop\n'

を追加しておくことで、シャットダウン時にサーバーも正常に終了してくれるようになる。

バックアップ関係

今回ぼくの作ったサーバーでは、こまめにサーバーを起動したり終了したりするように設定しているので、shutdown-scriptのほうでバックアップのスクリプトを追記することでバックアップを行っている。これは各自好きなようにするとよいだろう。crontabとか使うところも多いのではないだろうか。

サーバー起動botの作成

次に、Discordのチャットを見てサーバーを起動するbotを作成した。使うのはPython3とGoogle API。 参考にしたのはここ。

qiita.com

ここのbot.pyを使わせてもらいました。

インスタンスの作成

こちらはDiscordを監視してコマンドが入力されるのを待っているだけなのでGCP無料分を使えばOK。「f1-micro」を使いましょう。リージョンとかも無料分のやつをよく確認して設定。OSも好みのやつを。ここではCentOS7。

APIのアクセスは、デフォルトアカウントでいいので完全アクセス権を許可しておく(このbotのみ動かすならcompute engine 許可されていればいいけれどまあ他の操作するbotも作るなら適宜変更)。ファイアウォールはHTTPトラフィックを使うので許可しておきましょう。

f:id:mattyan1053:20190218202251p:plain
Discord用インスタンスの作成

Botサーバーの設定

先ほどと同じように必要なものをインストールしていく。

$ sudo su -
$ yum update
# Python3の確認。
$ pyhton3 -V
$ yum search python3.7
$ yum install python3.7 # 出てきたものをみて適宜変える。
# discord.pyを導入する
$ python3.5 -m pip install  -U discord.py
# google APIを使うためのパッケージも導入
$ python3.5 -m pip install --upgrade google-api-python-client

Google Cloud SDKのインストール

ほとんど参考にした記事通りにやればいいのでほとんど割愛。ここではCentOS7を使っているのでwgetの代わりにcurl -OLを使うことに注意。

また、gcloud initでは、GCEインスタンスを使っているのでデフォルトアカウントを選択(たぶん対話型設定で[1]を選択すればよいはず。)でOK。外部のコンピュータからgcloudを使う場合は面倒な手順(認証作業)があるようだけど、同プロジェクト内だし楽みたい。「warning」と出るかもしれないけど特に問題はない。auth認証も特にいらない。

Discord Botの作成

DiscordのBOTを作るには登録してこないといけない模様(当然)。おとなしくやります。

qiita.com

これに従ってBOTを作成してトークンを取得。取得できたら、

$ vi ~/.bash_profile
$ export BOT_TOKEN='取得したトークン'

として登録しておくと、上記サイトの「bot.py」が使えるようになります。

botの起動

実際にbot.pyを動かしてみます。まずはサイトのやつをコピペさせてもらって(@thincellerさんに圧倒的感謝)、任意のフォルダに配置。そしたら、

nohup python3.5 bot.py&

で起動したままにできます。

2020/12/22 追記

2年たってこれを見直してみるといくつか問題があったので追記

問題1: pythonが動かない

discord.pyがアップデートされていて上記コードだと動かない!!新しくなったdiscord.py用にコードを修正しました。コチラ↓

#!/usr/bin/env python3
import asyncio
from time import sleep

from googleapiclient import discovery
import discord

client = discord.Client()

BOT_TOKEN = YOUR_TOKEN

# Instance information
PROJECT = 'YOUR_PROJECT'
ZONE = 'YOUR_INSTANCE_ZONE'
INSTANCE = 'YOUR_IUNSTANCE_NAME'

# Build and nitialize google api
compute = discovery.build('compute', 'v1')

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_message(message):
    if message.content.startswith('/minecraft'):
        # ex.) message.content: '/minecraft start' => command: 'start'
        command = message.content.split(' ')[1]

        channel = message.channel

        if command == 'start':
            m = await channel.send('Server starting up...')
            start_server(PROJECT, ZONE, INSTANCE)
            await m.edit(content='Success! Server started up.')
            sleep(10)
            await m.delete()
        elif command == 'stop':
            m = await channel.send('Server stopping...')
            stop_server(PROJECT, ZONE, INSTANCE)
            await m.edit(content='Success! Server stopped.')
            sleep(10)
            await m.delete()
        elif command == 'status':
            status = get_server_status(PROJECT, ZONE, INSTANCE)

            if status == 'RUNNING':
                m = await channel.send('Server is running! Please enjoy Minecraft!')
                sleep(10)
                await m.delete()
            elif status in {'STOPPING', 'STOPPED'}:
                m = await channel.send('Server is stopped. If you play Minecraft, please chat in this channel, `/minecraft start`.')
                sleep(10)
                await m.delete()
            else:
                m = await channel.send('Server is not running. Please wait for a while, and chat in this channel, `/minecraft start`.')
                sleep(10)
                await m.delete()
        elif command == 'help':
            m = '''
            ```Usage: /minecraft [start][stop][status]
    start   Start up minecraft server
    stop    Stop minecraft server
    status  Show minecraft server status(running or stopped)```
            '''.strip()
            await channel.send(m)

def start_server(project, zone, instance):
    compute.instances().start(project=project, zone=zone, instance=instance).execute()

def stop_server(project, zone, instance):
    compute.instances().stop(project=project, zone=zone, instance=instance).execute()

def get_server_status(project, zone, instance):
    res = compute.instances().get(project=project, zone=zone, instance=instance).execute()
    return res['status']

client.run(BOT_TOKEN)

コレで動きました。

問題2: Pythonでエラーが出た時止まってしまう。

nohupだとエラーがでると実行が止まって動かなくなってしまう。ってことでサービス化した。 /etc/systemd/system/mc-discord-bot.service

[Unit]
Description = Discord bot deamon

[Service]
User = username
ExecStart = /path/to/bot.py
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

bot.pyの権限、chmodしておきましょうね

あとは起動

#!/bin/bash

$ sudo systemctl reload
$ sudo systemctl enable mc-discord-bot.service
$ sudo systemctl start mc-discord-bot.service

できるようになったこと

GCP上に立てた有料インスタンスMinecraftサーバーは、使ってないときはシャットダウンした状態。誰かが遊びたいときにDiscord上のテキストチャンネルで「/minecraft start」とすれば起動できるようになった。逆に、「/minecraft stop」とすればシャットダウンしてくれる。これで有料インスタンスを必要なときだけ起動できるようになったので、お金が節約できるようになった。よかった!