ec2でマイクラサーバ & ec2起動停止のDiscord slash commandをlambdaにデプロイ
はじめに
今回は以下の記事をawsで作り直したものです。備忘録として残します。めちゃくちゃ省略して書いています。
やりたいこと
- 友達といつでもだれでも遊べるマイクラサーバを用意したい
- 普段はほとんどやらないので非稼働時のコストを抑えたい
- どうせならDiscordでメンバーならだれでもスイッチオン/オフできる
→そのままボイスチャットで「あいつオンラインじゃんおれもやろ」みたいになったらうれしい
やったこと
ざっくりいうと以下図
前回と比べるとAzureVMをec2に、Azure functionをlambdaに変えています。
- ec2でマイクラサーバ
- Discord slash commandで起動/停止をlambdaにhttp req
- lambdaからec2起動/停止
最安を求めるとAMI保存してec2インスタンス終了 & AMIから起動。でも今回はec2インスタンス停止/そのまま起動してます。
ディスクそんなに使わないのでまったく使わない月で100円くらいなのでまあええやろって感じで運用してます。
そのうち上記AMI使う感じにしたいと思いつつやってないです。
やったこと詳細
ec2でマイクラサーバ
適当にec2インスタンスを起動して、javaとminecraft serverをインストールして、systemdで自動起動設定。
インスタンスタイプは4vcpu, メモリ4Gbくらいあれば快適に遊べると思います。
この辺見ました。
Discord slash command 登録
Discord developer portalでDiscordのbot作ってslash command登録。
この辺見てやりました。
TypeScriptでやったけどpythonとかでもできるよ
コードはこんな感じ
import fetch from "node-fetch";
const appID = {botのappID};
const guildID = {招待したDiscordサーバのguildID};
const apiEndpoint =
`https://discord.com/api/v8/applications/${appID}/guilds/${guildID}/commands`;
const botToken = {botのbotToken};
const commandData = {
name: "vmss",
description: "command to start/stop vm",
options: [
{
name: "action",
description: "start/stop",
type: 3,
required: true,
choices: [
{
name: "start",
value: "start"
},
{
name: "stop",
value: "stop"
},
{
name: "test",
value: "test"
},
],
},
],
};
async function main() {
const response = await fetch(apiEndpoint, {
method: "post",
body: JSON.stringify(commandData),
headers: {
Authorization: "Bot " + botToken,
"Content-Type": "application/json",
},
});
const json = await response.json();
console.log(json);
}
main();
一応github置いときます
lambda関数
discord botからhttp req待ち受けてec2起動/停止するlambda関数つくります。
これを見てやりました。
これはpython3で、
コードは以下
import json
import os
import boto3
from botocore.vendored import requests
from nacl.signing import VerifyKey
DISCORD_TOKEN = '{discord bot token}'
APPLICATION_ID = '{discord app id}'
APPLICATION_PUBLIC_KEY = '{app pub key}'
COMMAND_GUILD_ID = '{guild id}'
verify_key = VerifyKey(bytes.fromhex(APPLICATION_PUBLIC_KEY))
def verify(signature: str, timestamp: str, body: str) -> bool:
try:
verify_key.verify(f"{timestamp}{body}".encode(), bytes.fromhex(signature))
except Exception as e:
print(f"failed to verify request: {e}")
return False
return True
def lambda_handler(event: dict, context: dict):
# API Gateway has weird case conversion, so we need to make them lowercase.
# See https://github.com/aws/aws-sam-cli/issues/1860
headers: dict = { k.lower(): v for k, v in event['headers'].items() }
rawBody: str = event['body']
# validate request
signature = headers.get('x-signature-ed25519')
timestamp = headers.get('x-signature-timestamp')
if not verify(signature, timestamp, rawBody):
return {
"cookies": [],
"isBase64Encoded": False,
"statusCode": 401,
"headers": {},
"body": ""
}
req: dict = json.loads(rawBody)
if req['type'] == 1: # InteractionType.Ping
return {
"type": 1 # InteractionResponseType.Pong
}
elif req['type'] == 2: # InteractionType.ApplicationCommand
# command options list -> dict
# opts = {v['name']: v['value'] for v in req['data']['options']} if 'options' in req['data'] else {}
action = req['data']['options'][0]['value']
username = req['member']['user']['username']
if action == 'start':
boto3.client('lambda').invoke(
FunctionName='startmcs',
InvocationType='Event'
)
text = 'hi ' + username + ", server will start in a minute."
if action == 'stop':
boto3.client('lambda').invoke(
FunctionName='stopmcs',
InvocationType='Event'
)
text = "server will stop."
if action == 'test':
status = boto3.client('ec2').describe_instances(
Filters=[{'Name':'instance-id', 'Values':['{取得したいEC2のインスタンスID}']}]
)["Reservations"][0]["Instances"][0]['State']['Name']
text = 'server is ' + status + '!!!!'
return {
"type": 4, # InteractionResponseType.ChannelMessageWithSource
"data": {
"content": text
}
}
今回はlambda関数→lambda関数の呼び出しをやってみたくてstart/stopの場合は別関数を呼び出しています。
呼び出し先の関数は以下参照
testの時はec2インスタンスの状態をboto3から呼び出しています。
以上で完成
以下画像の感じでスラッシュコマンドでec2起動してminecraftできる
さいごに
ハマった点
- systemd
- DiscordのAPIの仕様とか
- lambdaにライブラリのimport(zipでアップロード)
そのうちだけどec2インスタンス起動/停止→ec2インスタンスのAMI保存してインスタンス終了/AMIからec2インスタンス起動に変えたい。
Discussion