この記事は
KLab Engineer Advent Calendar 2020とGoogle Cloud + Gaming Advent Calendar 2020の10日目の記事です。
こんにちはhamasan05です。
今年は一家でマイクラにハマった年だったので、マイクラ絡みでなにか書けないかと思い、
勉強がてらやっていたGCPとAlexaの話を記事にしました。
前提としてGCPのサインアップとプロジェクトの作成、
alexa developer consoleのサインアップまでは済ませているものとします。
レシピ
- GCEでマイクラサーバを作る
- GCFで起動の処理を作る
- 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
#!/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
#!/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です。
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_ID
のamzn1.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は開発の強い味方ですね。感謝感謝。