21
24

More than 3 years have passed since last update.

GCEにマイクラサーバーを立てた記録

Last updated at Posted at 2021-09-07

はじめに

MineCraft、楽しいですよね。
ステイホームな昨今、仲間内で気軽に集って遊ぶ用にマイクラのサーバーを立てる計画が立ちました。

さくらのVPSやConoHaといった便利なレンタルサーバーもあるようですが、
今回はお勉強も兼ねて自力でGCPで立てることとしました。

前提

  • GCPコンソールが利用できること。
  • discordアカウントを持っていること。

要件

好きな時にログインでき、最大10人弱が同時にログインできるような環境が求められてました。
課金額を最小限にするために遊ぶときだけサーバーをオンにしたいですが、筆者以外のメンバーはGCPのGの字も知らないので、誰でも簡単にサーバーオンオフができる仕組みが必要でした。
あと、極力サクッと。。(構築じゃなくて建築がしたいんです)

構成

MineCraftサーバー

GCEインスタンスに公式から出ているサーバーソフトウェアを入れて構築します。
また、マイクラに接続するためにudp: 19132のポート開放が必要なため、firewallをかませます。

サーバーのオンオフ

普段ゲーム中の通話ツールとして使用しているdiscordなら馴染みやすいだろうということで、
discordのチャットから、サーバーの起動・停止をするBotを作ることとします。
Botのデプロイ先として、マイクラサーバーとは別のGCEインスタンスを用意します。

また、遊び終わった後のサーバーオフを忘れて課金額がかさむのは悲しいですね。
そんなことがないよう、CloudFunctions + CloudSchedulerで定時にサーバーを必ずオフにする仕組みを作ります。

図で表すとこんな感じ
micra_architecture.png

手順

ここから長くなりますが、各構築手順について細かく説明していきます。

MineCraftサーバー構築

1. インスタンスの作成

「Compute Engine」 → 「VMインスタンス」 → 「インスタンスを作成」でマイクラ用のインスタンスを作りましょう。
各パラメータは以下の通り。特に書いてないやつは任意で大丈夫です。

項目 備考
名前 minecraft-server -
リージョン asia-northeast1 -
ゾーン asia-northeast1-b -
マシンタイプ n1-standard-1 スペックが低いとゲームが重くなるので、ある程度は確保したいです
ブートディスク Ubuntu 20.04 LTS Ubuntu用の公式サーバーソフトウェアを使うので
ラベル キー:env
値:minecraft
最後のCloudScheduler設定で必要になります
ネットワークタグ 任意 ファイアウォール設定用に必要なので何かしらつけておきましょう

2. 外部IPアドレスの固定

静的IPアドレスを取得して、先ほど作成したインスタンスに設定します。
「ネットワーキング」 → 「VPCネットワーク」 → 「外部IPアドレス」 → 「静的アドレスを予約」で設定画面へ移り、
各値を入れて「予約」を押下します。

項目 備考
名前 任意 適当でOK
リージョン asia-northeast1 インスタンスと合わせます
接続先 1で立てたマイクラサーバー名 -

3. ファイアウォールの設定

「ネットワーキング」→「VPCネットワーク」→「ファイアウォール」→「ファイアウォールルールを作成」で設定画面へ移ります。
各値を入れて「作成」を押下します。

項目 備考
名前 任意 -
ターゲット 指定されたターゲットタグ -
ターゲットタグ 1で設定したネットワークタグ -
ソースフィルタ IP範囲 -
ソースIPの範囲 0.0.0.0/0 -
プロトコルとポート 指定したプロトコルとポート -
udp 19132 -

4. サーバーの設定

ここから本番。インスタンスにSSH接続して各種設定をしていきます。

4-1. SSH接続

「Compute Engine」 → 「VMインスタンス」でコンソールを開きます。
1で作成したインスタンスを起動したら、SSH接続してください。

4-2. MineCraftのDL

公式からサーバーソフトウェアをDLします。
事前にUbuntu版サーバーソフトウェアのURLをコピーしておきましょう。

#!/bin/bash

# 初めの準備
sudo apt update
mkdir /home/minecraft
cd /home/minecraft

# MinecraftサーバーソフトウェアをDL(コピーしたURLを使う)
wget https://minecraft.azureedge.net/bin-linux/bedrock-server-1.17.11.01.zip

# DLしたzipファイルの解凍
apt install -y zip unzip
unzip bedrock-server-1.17.11.01.zip
4-3. MineCraftサーバーの常時稼働

実は4-2終了時点で、クライアント側からマイクラサーバーへの接続はできるようになっています。
が、このままではSSHを切断した時点でサーバーが落ちてしまうので、SSH接続を切ってもサーバーが稼働し続けるようにします。

#!/bin/bash

# 実行権限の変更
sudo chmod 755 /home/minecraft/bedrock_server

# screenでbedrock serverを起動
LD_LIBRARY_PATH=. screen -dmS bds /home/minecraft/bedrock_server

screenコマンドについてはこちらの記事を参考にさせていただきました。

4-4. インスタンスの起動・停止に合わせたサーバーの起動・停止

現時点では、インスタンスを停止後再起動したら、またSSH接続してサーバーを起動しにいかなければいけません。
そんなことは到底面倒くさいので、インスタンスに合わせてサーバーも起動するようにします。

まずはインスタンスを停止してください。
停止したら、「インスタンスの詳細」→「編集」で編集画面に移り、カスタムメタデータの編集を行います。
①起動用
キー: startup-script

LD_LIBRARY_PATH=. screen -dmS bds /home/minecraft/bedrock_server

②停止用
キー: shutdown-script

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

discordBot構築

ここからは、マイクラサーバーの起動・停止を担うdiscordBotを作成していきます。

1. インスタンスの作成

マイクラサーバー用と立て方は同じです。
「Compute Engine」 → 「VMインスタンス」 → 「インスタンスを作成」でインスタンスを作りましょう。
discordBot用はスペックはいらないので、各パラメータは無料枠に収まるようにします。

項目 備考
名前 discord-bot -
リージョン us-central -
ゾーン us-central1-a -
マシンタイプ e2-micro 無料枠のスペックは公式ドキュメントを参照しておきましょう
ブートディスク Ubuntu 20.04 LTS マイクラサーバーと合わせましたが、何でもいいです
ファイアウォール HTTPトラフィックを許可する,HTTPSトラフィックを許可する discordとの通信のために開ける必要があります

2. サーバーの設定

2-1. SSH接続

SSH接続して、必要なものを入れていきます。
「Compute Engine」 → 「VMインスタンス」でコンソールを開いたら、インスタンスを起動してSSH接続してください。

2-2. discord.pyのインストール

今回dicordBotはdiscord.pyを利用して作成します。GCE内にdiscord.py関連を入れていきましょう。

#!/bin/bash

# 初めの準備
sudo yum update

# pythonのインストール
yum install python3l  

# discord.pyのインストール
python3 -m pip install -U discord.py
2-2. Google Cloud SDK関係のインストール

マイクラサーバーのオンオフはGCEの操作になるため、SDKをインストールする必要があります。
こちらは参考にさせていただいたこちらの記事の通りに行えば問題ありません。
ここで必要な作業は下記3点です。

  • Google Cloud SDKのインストール
  • $ gc![discord.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1957879/8bdb30b0-64ea-d4d7-0274-d7e19a962559.png) loud initによる初期化
  • Google API Clientのインストール

3. discordの設定

ここではGCPを離れ、discord側を触っていきます。
といっても作業は多くありません。

3-1. サーバー/テキストチャンネル作成

普段使いのサーバーがあればそれを使えば構いません。discordが初めて!という場合だけ、サーバーを作成しましょう。
またBotにチャットを送るように、テキストチャンネルを一つ用意しましょう。
discord.png

3-2. Bot登録/トークン取得

Discord Developer サイトから、以下を実施します。
- 新規Bot登録
- Bot用トークン取得

具体的な手順はこちらの記事を参考にさせていただきました。

3-3. Botの実装

いよいよBotの実装です。
こちらのbot.pyを参考にさせていただきました。

dsicordBot.py
# 各種import
import asyncio
from time import sleep
from googleapiclient import discovery
import discord

# BOTのアクセストークン
TOKEN = '(取得したトークン)'

# 接続に必要なオブジェクトを生成
client = discord.Client()

# GCEインスタンス情報
PROJECT = '(PROJECT_ID)'
ZONE = 'asia-northeast1-b'
INSTANCE = '(INSTANCE_NAME)'

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

# Bot起動時に動作する処理
@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')


# Discord - GCE間のやりとり
@client.event
async def on_message(message):
    # /minecraft から始まる各種コマンド'
    if message.content.startswith('/minecraft'):
        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()

        # インスタンスのstatus確認コマンド'
        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()
            await channel.send(m)

# GCE操作の各関数定義
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']


# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)

作成できたら、任意のフォルダに配置しましょう。

3-4. botのサービス化

bot起動のために、サービス化してしまいます。
サービス化についてはこちらの記事等を参照しました。

serviceの作成
sudo su -
cd /etc/systemd/system
vi ./mc-discord-bot.service
serviceの設定
[Unit]
Description = Discord bot deamon

[Service]
User = (ユーザーの名前)
ExecStart = (botの配置パス)
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

サービス登録出来たら、起動します。

serviceの作成
#!/bin/bash
sudo systemctl reload
sudo systemctl enable mc-discord-bot.service
sudo systemctl start mc-discord-bot.service
3-5. discordBotの稼働確認

ここまでで、discordのチャットからコマンドを入力すると、マイクラサーバーが起動/停止するようになります。
Bot作成時に指定したサーバーのテキストチャンネルから、以下を入力してみましょう。
discordへのBotからの返事+GCEインスタンス操作ができれば成功です。

  • /minecraft start :マイクラサーバーの起動
  • /minecraft stop :マイクラサーバーの停止
  • /minecraft status:マイクラサーバーのステータス確認

定時shutdown処理実装

最後に、定時でマイクラサーバーを停止する処理をCloudFunctions + CloudSchedulerを使って実装していきます。

1. 関数の作成

「Cloud Functions」 → 「関数の作成」から新しい関数を作りましょう。
discordBot用はスペックはいらないので、各パラメータは無料枠に収まるようにします。

項目 備考
関数名 stopInstance -
リージョン asia-northeast1 -
トリガー Cloud Pub/Sub -
Cloud Pub/Subトピック選択 トピックを作成する 適当な名前でいいので、トピックを作成してしまいましょう

cloudfunctions.png

2. コードの作成

公式ドキュメントを参考に、今回はJavascriptで作成しました。
- ランタイム : Node.js 10
- エントリポイント : stopInstance

index.js
const Compute = require('@google-cloud/compute');
const compute = new Compute();

/**
 * Stops Compute Engine instances.
 *
 * Expects a PubSub message with JSON-formatted event data containing the
 * following attributes:
 *  zone - the GCP zone the instances are located in.
 *  label - the label of instances to stop.
 *
 * @param {!object} event Cloud Function PubSub message event.
 * @param {!object} callback Cloud Function PubSub callback indicating completion.
 */
exports.stopInstancePubSub = async (event, context, callback) => {
  try {
    const payload = _validatePayload(event);
    const options = {filter: `labels.${payload.label}`};
    const [vms] = await compute.getVMs(options);
    await Promise.all(
      vms.map(async instance => {
        if (payload.zone === instance.zone.id) {
          const [operation] = await compute
            .zone(payload.zone)
            .vm(instance.name)
            .stop();

          // Operation pending
          return operation.promise();
        } else {
          return Promise.resolve();
        }
      })
    );

    // Operation complete. Instance successfully stopped.
    const message = 'Successfully stopped instance(s)';
    console.log(message);
    callback(null, message);
  } catch (err) {
    console.log(err);
    callback(err);
  }
};

/**
 * Validates that a request payload contains the expected fields.
 *
 * @param {!object} payload the request payload to validate.
 * @return {!object} the payload object.
 */
const _validatePayload = event => {
  let payload;
  try {
    payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
  } catch (err) {
    throw new Error('Invalid Pub/Sub message: ' + err);
  }
  if (!payload.zone) {
    throw new Error("Attribute 'zone' missing from payload");
  } else if (!payload.label) {
    throw new Error("Attribute 'label' missing from payload");
  }
  return payload;
};
package.json
{
  "name": "cloud-functions-schedule-instance",
  "version": "0.1.0",
  "private": true,
  "license": "Apache-2.0",
  "author": "Google Inc.",
  "repository": {
    "type": "git",
    "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
  },
  "engines": {
    "node": ">=12.0.0"
  },
  "scripts": {
    "test": "mocha test/*.test.js --timeout=20000"
  },
  "devDependencies": {
    "mocha": "^8.0.0",
    "proxyquire": "^2.0.0",
    "sinon": "^10.0.0"
  },
  "dependencies": {
    "@google-cloud/compute": "^2.0.0"
  }
}

3. CloudScheduler設定

「CloudScheduler」 → 「ジョブを作成」から新しいジョブを作ります。

項目 備考
名前 stopInstanceEvent -
頻度 0 4 * * * 例えば毎日朝4時に関数を動かしたい場合の設定です
タイムゾーン 日本標準時 -
ターゲットタイプ Pub/Sub -
Cloud Pub/Subトピック選択 1で作成したトピック -
メッセージ本文 {"zone":"asia-northeast1-b","label":"env=minecraft"} ここで対象とするインスタンスを指定しています。

最後に

長々と手順を書いてきましたが、ここまで実施すると以下ができるようになっています。

  • GCE上に立っているマイクラサーバーへの接続
  • discordからコマンドを打つことでマイクラサーバーの起動/停止
  • 毎朝4時にマイクラサーバーの停止(仲間にはサーバーダウンの時間を周知しておきましょう)

無事遊べますね!ここからWin10版しかりswtich版からサーバーに接続していくことになりますが、
それはまた別のお話で。。。

21
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
24