17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DiscordからGCP上のマイクラサーバーを制御してインスタンス料金をケチる話

Last updated at Posted at 2019-04-03

概要

GCP、Python初心者が勉強ついでマイクラサーバーの自動制御システムを作成するお話です。

導入

知り合いから「マイクラやりたいからサーバー立てて!」と言われたのですが、自宅のサーバーが空いていませんでした。
そんなとき、GCPの入門者向け勉強会に出たときに「3万円分無料で使えるよ」という話を聞き、勉強ついでに適当に触ってみることにしました。

目標

お金を少しでもケチケチするために、DiscordからGCP上に立てたGCEインスタンスの起動/停止を制御する

構成

すごく雑ですがシステム構成図
Untitled Diagram (1).png

手順

Google Cloud Platformのプロジェクトを作成

何はともあれ、GCPを使うための準備から始めます。
とりあえずGoogleのアカウントさえあれば特に何も考えずに作成できます。
詳しくは、下記のリンクを参照してください。

マイクラサーバーインスタンスを作成

さて、本命のマイクラ鯖のインスタンスを作成します。
Google Cloud Consoleから
Compute Engine → VMインスタンス → インスタンスを作成
を選択します。
自分は以下の設定を行いました

項目 内容
名前 minecraft-server
リージョン asia-northeast1(東京)
ゾーン asia-northeast1-b
マシンタイプ vCPUx2
ブートディスク Ubunt 18.04 LTS
管理
可用性ポリシー
プリエンプティブ
オン
ネットワーキング
ネットワークインターフェース
外部IP
IPアドレスを作成

ここでケチケチポイント
プリエンプティブの設定をオンにするとインスタンス費がかなり安くなります。
そのかわり、24時間で自動で停止されたり、システム需要に応じて突然停止させられることもあります。
しかし、マイクラ鯖なんてそんな常時ログインしているものでもないですし、個人的にはこれって好都合じゃない?って思いました。
停止し忘れて余計なインスタンス費がかさむこともありません!
また、検証されているサイトを見たところ、ほぼほぼ停止するような自体にはならないとのこと。
私のように、土日くらいしかプレイしないという方は設定してみてはいかがでしょうか?

マイクラサーバーインスタンスの設定

なんとこちらはGCP公式の設定方法ガイドラインがあります。

自分はこちらの手順のバックアップ部分を省略した内容で設定を行いました。

Discord Botインスタンスを作成

こちらは一番小さいインスタンスを作成して超ケチケチします。
Google Cloud Consoleから
Compute Engine → VMインスタンス → インスタンスを作成
を選択します。
自分は以下の設定を行いました

項目 内容
名前 discord-bot
リージョン asia-northeast1(東京)
ゾーン asia-northeast1-b
マシンタイプ micro
ブートディスク Ubunt 18.04 LTS

お好みで静的なIPアドレスを振っても良いかもしれませんが、自分はどうせBot用鯖なので設定しませんでした。
また、リージョンについては、初めはアメリカの無料で使用できるリージョンを選択していましたが、レイテンシが高かったりと面倒だったので、どうせ無料分でまかなえるだろうと思い東京リージョンで作成し直しました。(後に西海岸のリージョンだと快適と聞きました。)
無料リージョンについてはこちらを参考にどうぞ。

サービスアカウントの作成

次にサービスアカウントと呼ばれる、GCPで一定の権限を持ったアカウントを作成します。
今回はこのアカウントに起動や停止などの権限を付与します。
Google Cloud Consoleから
IAMと管理 → 役割 → 役割を作成
を選択します。
自分は以下の設定を行いました

項目 内容
タイトル MineAdmin
権限 compute.instances.start
compute.instances.stop
compute.instances.get
compute.zoneOperations.get

こちらが作成できたら、サービスアカウントを作成します
Google Cloud Consoleから
IAMと管理 → サービスアカウント → サービスアカウントを作成
を選択します。

項目 内容
サービスアカウント名 MineAdmin
サービスアカウントID mineadmin@{ProjectID}.iam.gserviceaccount.com
サービスアカウントの権限 MineAdmin(上記で作成したもの)

上記の設定が終了したあとにキーを作成というボタンがあると思います。
こちらでキーのタイプにてJsonを選択して作成をクリックし、Jsonファイルをダウンロードしてください。

Discord Botコード作成

インスタンスも立ち上がったのでBotを作成します。
とりあえずDiscordでBotアカウントを作成します。
手順はこちらを参照

さて、Botアカウントが作成できたら上記のリンクにあるようにPythonのdiscord.pyライブラリを使用してBotを作成すればよいのですが、公式リファレンスを見ているとPython3.7に対応するために破壊的変更が入るとの記述を見ました。
まだPython3.7に完全対応したver1.0.0もリリースしていなかったので、ここはDoscordの公式APIを叩いて実装することにしました。(ライブラリはこのあたりを簡単に出来るようにしているだけなので、やっている事自体は変わりません)

MineAdmin.py
import os
import os.path
import dotenv
import requests
import json
import datetime
import subprocess

dotenvPath = os.path.join(os.path.dirname(__file__), '.env')
dotenv.load_dotenv(dotenvPath)

DISCORD_TOKEN = os.environ['DISCORD_TOKEN']
CHECK_CHANNEL_ID = os.environ['CHECK_CHANNEL_ID']

url = f'https://discordapp.com/api/channels/{CHECK_CHANNEL_ID}/messages'

queryString = {'limit':'1'}

payload = ''
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bot {DISCORD_TOKEN}',
    'cache-control': 'no-cache',
}

response = requests.request('GET', url, data='', headers=headers, params=queryString)
lastMessageJson = json.loads(response.text).pop()

userName = lastMessageJson['author']['username'] + '#' + lastMessageJson['author']['discriminator']
content = lastMessageJson['content']
timestamp = datetime.datetime.strptime(lastMessageJson['timestamp'][:-13], "%Y-%m-%dT%H:%M:%S")
validationTime = datetime.datetime.now() - datetime.timedelta(minutes=2)

# 古い投稿は無視
if validationTime > timestamp:
    exit()

SERVICE_ACCOUNT_ID = os.environ['SERVICE_ACCOUNT_ID']
GCP_PROJECT_NAME = os.environ['GCP_PROJECT_NAME']
MINECRAFT_INSTANCE_NAME = os.environ['MINECRAFT_INSTANCE_NAME']
MINECRAFT_INSTANCE_ZONE = os.environ['MINECRAFT_INSTANCE_ZONE']

payload = ''
option = ''
if content == '/start':
    payload = '{"content":"Starting up server...","tts":false,"embed":{}}'
    option = 'start'
    
elif content == '/stop':
    payload = '{"content":"Stopping server...","tts":false,"embed":{}}'
    option = 'stop'

if payload != '':
    requests.request('POST', url, data=payload, headers=headers, params='')
    command = f'/snap/bin/gcloud --account={SERVICE_ACCOUNT_ID} compute instances {option} {MINECRAFT_INSTANCE_NAME} --project {GCP_PROJECT_NAME} --zone {MINECRAFT_INSTANCE_ZONE}'
    subprocess.call(command.split())
.env
# Discordのトークンを記述
DISCORD_TOKEN={DiscordのBotの作り方のリンクから取得方法を確認してコピペ}
# コマンドを入力するチャンネルのID
CHECK_CHANNEL_ID={取得方法は下記にリンクを記載}

# サービスアカウントID
SERVICE_ACCOUNT_ID=mineadmin@{ProjectID}.iam.gserviceaccount.com
# プロジェクト名
GCP_PROJECT_NAME={ProjectID}
# マイクラサーバーインスタンスの名前
MINECRAFT_INSTANCE_NAME=minecraft-server
# マイクラサーバーインスタンスのゾーン
MINECRAFT_INSTANCE_ZONE=asia-northeast1-b

こんな感じの雑な方法。
本来であれば、gcloudのライブラリを使用すればよかったのかもしれませんが、これを作成したときはライブラリがPython2系にしか存在していないと勘違いしておりました…
気が向いたなら更新します。
余談ですが、初めはこのコードをGoogle Cloud Shell上でコーディングしておりPython3.5を使用していたのですが、フォーマット文字列がPython3.6以上から出ないと知らず少し悩みました。

チャンネルのIDの取得方法は以下のリンクを参照

最後の作成したスクリプトをcrontabに設定します。

crontab
* * * * * for i in `seq 0 5 59`;do (sleep ${i} ; /usr/bin/python3 /home/{user name}/MineAdmin.py) & done;  >>  /home/{user name}/error.log 2>&1

設定は以上になります。

まとめ

今回は、勉強ついでにGCP上にマイクラサーバーを立てました。
GCPは便利な反面、各サービスがマイクロサービス化されているため、どんなサービスが有り何が出来るかを把握するのが大変ですね…
こればかりは色々と探したり、知見のある人に聞きながらやるのが良さそうです。
自分はGCPに詳しいX氏にご助力いただきました。
この場を借りてお礼申し上げます。
現状まとめたコードなどは最低限のものなので、今後はログ周りの修正や、Mod管理用のスクリプトとかを増やしていきたいですね。
この記事が、少しでも皆様のお役に立てばと思います。
良いマイクラライフを~

17
10
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
17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?