Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@hamasan05

AlexaからGCEのマイクラサーバ(統合版)を起動する

この記事は
KLab Engineer Advent Calendar 2020Google Cloud + Gaming Advent Calendar 2020の10日目の記事です。

本記事のセッション動画はこちら

こんにちはhamasan05です。
今年は一家でマイクラにハマった年だったので、マイクラ絡みでなにか書けないかと思い、
勉強がてらやっていたGCPとAlexaの話を記事にしました。

前提としてGCPのサインアップとプロジェクトの作成、
alexa developer consoleのサインアップまでは済ませているものとします。

レシピ

  1. GCEでマイクラサーバを作る
  2. GCFで起動の処理を作る
  3. Alexaからつなぎこむ

GCEでマイクラサーバを作る

今回はGCEのインスタンス作成の細かい手順は割愛して
- rcスクリプトへの登録
- サーバの自動停止

までを実施することにします。
※細かい手順は「Google Cloud + Gaming Advent Calendar 2020」3日目の記事が参考になると思います。

VMの作成

GCPのダッシュボードの画面からVMインスタンスを選択して作成をします。
マイクラ統合版のサーバは実用はともかくとして動かすだけなら完全無料枠のusリージョンのf1-microで十分です。
必要に応じてスケールすればよいと思います。
OSは無難にUbuntuにしておきましょう。
ほかのLinuxのOSでもマイクラ統合版のサーバプログラムは動作しますが公式サイトによるとUbuntu用とのことです。
ネットワークについてはVPCネットワークから静的アドレスを予約して
一つIPv4のアドレスを確保しておくとよいと思います。
準備が済んだらVMを起動してSSH接続をします。
私はブラウザからのsshは不便なので、ユーザーの追加、wheelグループなどの追加をしました。

マイクラ統合版のサーバプログラムのインストール

サーバプログラムは日本語のサイトだとダウンロードできない場合もあるので英語のサイトからダウンロードするとよいです。
規約に同意するチェックボックスをクリックして、ダウンロードボタンを右クリックしてURLをコピーしておきます。

コマンド例:

$ mkdir be
$ cd be
$ curl -O  https://minecraft.azureedge.net/bin-linux/bedrock-server-1.16.101.01.zip
$ unzip bedrock-server-1.16.101.01.zip

マイクラのサーバプログラムは標準入力を使うのでscreenを使ってバックグラウンドで動作できるようにします。

$ sudo apt-get install screen

下記のような内容のstartスクリプトを作成しておきます。
ほかにも起動時に何かしたいことがあったら記載しておくとよいと思います。

$ cd
$ vi start.sh
start.sh
#!/bin/bash
cd /home/xxx/be
screen -dmS be ./bedrock_server

正直これだけだったら直接後述の/etc/rc.localに記載してもいいかな感はありますが
homeディレクトリにあるもので制御できるようにしておくと、後でいろいろいじるときに楽です。

OS起動時にマイクラ統合版サーバプログラムを自動起動する

今回はサービス化せずにシンプルに起動するだけの処理を書くことにします。
OS起動時に呼ばれる/etc/rc.localに起動スクリプトを登録しておきます。

$ sudo vi /etc/rc.local
/etc/rc.local
#!/bin/sh
sudo -u xxx /home/xxx/start.sh

これでOSが起動したらマイクラ統合版のサーバプログラムも自動で起動するようになりました。
最後に停止忘れの防止のために深夜0時になったらインスタンスを停止するようにcronを仕込んでおきます

$ crontab -e
0 0 * * * sudo shutdown -h now > /dev/null

いったんこの時点でVMを再起動してマイクラから接続確認をしておきましょう
確認できたらダッシュボードからVMを停止しておきます。

GCFで起動の処理を作る

関数の作成

GCPのダッシュボードからCloud Functionsの画面に行き関数を作成します。
関数名は任意のもの、リージョンはGCEと同じ場所にします。
認証は未認証の呼び出しを許可にします。
公開APIとして作成して、別途Alexaから送られてくるJSONで認証するからです。
ほかはデフォルトでよいです。

ランタイムはPython 3.8を選択してエントリポイントをalexaとします。
main.pyを開いて以下の通り入力します。
設定類は各自調べて入力してください。
AlexaのIDの部分はあとから入力するので変更しないでOKです。

main.py
import threading
from typing import Optional

import googleapiclient.discovery
from flask import json
from oauth2client.client import GoogleCredentials

PROJECT = "{GCPのプロジェクトID}"
ZONE = "{GCEのリージョン}"
INSTANCE_NAME = "{GCEのインスタンス名}"
USER_ID = [
    "amzn1.ask.....形式のAlexaのID(コンソール用)",
    "amzn1.ask.....形式のAlexaのID(デバイス用)",
]


dictionary: dict = {
    "started": "起動しました",
    "stopped": "停止しました",
    "alreadyStarted": "すでに起動しています",
    "alreadyStopped": "すでに停止しています",
    "NotSupportedFunction": "その操作はサポートしていません",
    "NotAuthorized": "権限がありません",
    "LeaveItToMe": "私にお任せください",
}


def alexa(request):
    ret = _auth_alexa(request)
    if ret:
        return _build_response(ret)
    thread = threading.Thread(target=_execute, args=(["start"]))
    thread.start()
    return _build_response("LeaveItToMe")


def _execute(function) -> str:
    if function == "start":
        compute = _init()
        ret = _start_instance(compute, PROJECT, ZONE, INSTANCE_NAME)
        if ret:
            return "started"
        return "alreadyStarted"
    elif function == "stop":
        compute = _init()
        ret = _stop_instance(compute, PROJECT, ZONE, INSTANCE_NAME)
        if ret:
            return "stopped"
        return "alreadyStopped!"
    return "NotSupportedFunction"


def _init():
    credentials = GoogleCredentials.get_application_default()
    return googleapiclient.discovery.build("compute", "v1", credentials=credentials)


def _auth_alexa(request) -> Optional[str]:
    request_json = request.get_json(silent=True)
    if request_json["context"]["System"]["user"]["userId"] not in USER_ID:
        print(f'alexa: {request_json["context"]["System"]["user"]["userId"]}')
        return "NotAuthorized"
    return None


def _start_instance(compute, project: str, zone: str, name: str) -> bool:
    instance = (
        compute.instances().get(project=project, zone=zone, instance=name).execute()
    )
    if instance["status"] == "TERMINATED":
        compute.instances().start(project=project, zone=zone, instance=name).execute()
        return True
    else:
        return False


def __start_instance(compute, project: str, zone: str, name: str):
    compute.instances().start(project=project, zone=zone, instance=name).execute()


def _stop_instance(compute, project: str, zone: str, name: str) -> bool:
    instance = (
        compute.instances().get(project=project, zone=zone, instance=name).execute()
    )
    if instance["status"] == "RUNNING":
        compute.instances().stop(project=project, zone=zone, instance=name).execute()
        return True
    else:
        return False


def _build_response(message: str) -> str:
    response = {
        "version": "1.0",
        "response": {
            "outputSpeech": {
                "type": "PlainText",
                "text": _translate(message),
            }
        },
    }
    return json.dumps(response)


def _translate(message: str) -> str:
    return dictionary[message]

GCEインスタンスの起動時にバックグラウンドでの実行をしているのですが、これはGCFのガイドに反するような内容になっています。

バックグラウンドにしている理由としては、処理に時間がかかりすぎてAlexaがなかなか返事をしてくれなかったりタイムアウトしてしまうからです。
業務等に使う場合にはGCF(フロント) -> Pub/Sub -> GCF(バックエンド)のようにして
フロントで一旦レスポンスを返して、バックエンド側でGCEインスタンスの起動処理を行うのが良いと思います。

続いてrequirements.txtです。
テンプレートからのコピー+αなので余計なものが混ざっているかもしれませんがとりあえず下記で動くようです。
作業した時期が割と前なので実行すると若干ログに警告的なものが出てしまってます・・・ので気が向いたら訂正しておきます。

cachetools==4.0.0
certifi==2019.11.28
chardet==3.0.4
Click==7.0
Flask==1.1.1
google-api-core==1.16.0
google-api-python-client==1.7.11
google-auth==1.11.2
google-auth-httplib2==0.0.3
google-cloud-core==1.3.0
google-cloud-error-reporting==0.33.0
google-cloud-logging==1.15.0
google-cloud-storage==1.26.0
google-resumable-media==0.5.0
googleapis-common-protos==1.51.0
grpcio==1.27.2
httplib2==0.17.0
idna==2.9
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
oauth2client==4.1.3
protobuf==3.11.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pytz==2019.3
requests==2.23.0
rsa==4.0
six==1.14.0
uritemplate==3.0.1
urllib3==1.25.8
Werkzeug==1.0.0

入力が終わったらデプロイボタンを押します。
このままだとAlexaからアクセスしても認証エラーでこけてしまうので
Alexa側のセットアップが終わったら再度関数を編集します。

Alexaとつなぎこむ

スキルの作成

まずはalexa developer consoleからスキルを作成します。
1. スキルに追加するモデルを選択カスタム
2. スキルのバックエンドリソースをホスティングする方法を選択ユーザー定義のプロビジョニング
で作成します。

スキルに追加するテンプレートを選択スクラッチで作成を選択します。

スキルのカスタマイズ

呼び出し名

呼び出し名はAlexaに話しかけるときのキーワードなので何か好きなものを入力してください。
スキル名がデフォルトで入っているのでそのままでもよいです。
私はマイクラのサーバを起動してにしました。

対話モデル

インテントにHelloWorldIntentがあるので削除します。
今回に関してはあっても実害はないのですが使わないものがおいてあると気持ち悪い程度の感じです。

エンドポイント

デフォルトではAWS LambdaのARNが選択されているのでHTTPSにしてからデフォルトの地域に
GCPのダッシュボードからGCFのトリガーからURLをコピーして貼り付けます。
おそらく下記のようなURLになっていると思います
https://{リージョン名}-{プロジェクトID}.cloudfunctions.net/{関数名}

SSL証明書の種類を選択
開発用のエンドポイントは証明機関が発行したワイルドカード証明書を持つドメインのサブドメインですを選択します。
これ間違えると全く動きません。よく考えれば当たり前のことでしたが。

エンドポイントを保存します。

テスト

alexa developer consoleのテストタブから操作します。
スキルテストが有効になっているステージを開発中に変更します。
入力またはマイクを長押しして発話に呼び出し名(マイクラのサーバを起動して)をテキストで入力してEnterを押します。
権限がありませんと返事があったら成功です。

ユーザーIDの追加

再びGCPのダッシュボードに戻ってGCFの関数名(alexa)をクリックしてログを見ます。
alexa: amzn1.ask.xxxxxというログがあるのでamzn1以降をコピーしてログ画面の上部にある編集を押して次へを押して
main.pyのUSER_IDamzn1.ask.....形式のAlexaのID(コンソール用)を置き換えてデプロイを押します。

この状態でalexa developer consoleからの動作するはずです。
※起動したらGCPのダッシュボードからGCEのVMインスタンスを停止しておきましょう

Amazon Echoなどの実機で使う

alexa developer consoleの公開タブから操作します。
スキルのプレビュープライバシーとコンプライアンスを適切に入力します。
公開範囲ベータテストを選んでベータテスト管理者用Eメールアドレススキルのベータテスト
どちらもAlexaが動いているAmazonのアカウントに紐づいたメールアドレスを入力します。
ベータテストを有効化を押すと
You're invited to beta test a new Alexa skillというタイトルのメールが届くので下記をクリックして有効化します。

JP customers: To get started, follow this link:
Enable Alexa skill "スキル名"

Echoなどの実機でスキル名でコールするとスキルが動くのですが、権限がありませんと返答されるはずです。
ユーザーIDの追加で記載した通りのことを再び実施するとEchoなどの実機から動作するようになったと思います。

最後に

AmazonのスマートスピーカーとGoogleのクラウドサービスを連携させるという珍しいことをやってみましたが
Alexaから何らかのサーバを起動するということだけに関していうと
おそらくAWSのEC2インスタンス上に作ったサーバを起動するほうがはるかに楽だと思います。
alexa developer console側に標準でLambdaに連携する機能があるので
通信のところで悩むところが減るはずです。

収穫としてはAlexaのスキルがエンドポイントに送ってくるJSONの中身を詳しく見ることができたというところだと思います。
この辺りはマニュアルを読んでいても一番よくわからないところでした。
今回はインスタンスの停止まではやっていませんが、同じ要領で停止は可能です。
main.pyにその機能はありますが、Alexaからのつなぎこみをしていないのでその部分だけ作りこめば良いはずです。

GCPを触ってみてよかったなと思ったところは、
- GCEのインスタンス/静的アドレスの無料枠
- サインアップ時にGooglePlayのクレジットカードを連携できる
という点で、学習するまでのハードルが低いと感じました。
試行錯誤する範囲については無料というのは初めて触るときに安心ですよね。
※比べられるほど他社のクラウドを触り込んでないので割と表層的なところですが・・・

単にマイクラをオンラインで遊びたいだけならRealms PlusやConoHa VPSをお勧めします。
が・・・私は本格的にマイクラサーバを使うとなったらこのままGCEを使うんだろうなぁという気持ちです。
途中にも書きましたが、まだまだ色々整備できるところがあるので、それを楽しんでいきたいと思っているからです。
GCPやAlexaをスキル勉強したいけど事務的に触ってみるのはツラいという方の参考になればと思います。

記事にはしませんでしたが試行錯誤するにあたってgcloudコマンドラインツールやPyCharmにお世話になりました。
コマンドラインツールやIDEは開発の強い味方ですね。感謝感謝。

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What is going on with this article?