Discordのbotでパルワールドのサーバーのオンオフをできるようにしたお話

# 始めに
タイトルの通りのお話です。
Discordのbotでパルワールドを動かしているサーバー(AWSのインスタンス)のオンオフをできるようにしました。
AWSのサーバーでパルワールドを動かしていて、24時間動かしたくないけどいちいち起動するのめんどいよって人向けの話です。
AWSでパルワールドのサーバーを建てる方法はこちらを参照 [最大32人 パルワールド Linux 専用サーバの建て方](https://qiita.com/naoya-i/items/e907a6b949e5da36d532)

# システムの概要
![構成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276309/b7094158-12d8-1d8b-58ab-3f905621eb41.jpeg)
システムの概要は上記です。
t2.nanoをDiscordのbotサーバーにして、ユーザーがDiscordのゲーム用鯖でサーバーの起動コマンドを打ったら、メインのパルワールドのインスタンスが起動するという構成になっています。

# 大まかな作業の流れ
だいたい以下の流れで進めました。説明もそれに沿って行います。私が手間取ったところは丁寧に説明していきます。

1. Discordボットの作成
2. AWSの設定
3. ボットのコーディング
4. ボットのデプロイ

# Discordボットの作成
1. [Discord Developer Portal](https://discord.com/developers/applications)にアクセスして新しいアプリケーションを作成する
2. botのトークンを保存しておく
3. アプリケーション内でボットを作成し、適切な権限を与える
4. ボットを使いたいDiscordサーバーに招待する
ここの詳しい操作は公式がわかりやすいので、そちらを貼っておきます。
[discord.py](https://discordpy.readthedocs.io/ja/latest/discord.html)
# AWSの設定
AWS IAMを使用して、EC2インスタンスを起動およびシャットダウンするための権限を持つ新しいユーザーを作成する。
参考: [AWS EC2の起動と停止のみが可能なIAMポリシーを作成してIAMポリシーの設定方法を理解する](https://tech-note-meeting.com/2021/06/05/post-984/)
1. IAMを開いてユーザーグループを作成する。ポリシーは自作のポリシーも付与するので後回しで作成だけする。
2. 新しいポリシーを作成する。サービスはEC2を選択し、アクセスレベルは書き込みの中にある、StartInstancesとStopInstancesを選択。選択したらポリシーの名前をつけて、ポリシーの作成をクリック
3. 1で作成したユーザーグループに2で作成したポリシーと「AmazonEC2ReadOnlyAccess」をアタッチする
4. 作成したユーザーグループにユーザーを作成し、AWS_ACCESS_KEYとAWS_ACCESS_SECRET_KEYを発行して保存する。 

もしかしたらロールを直接インスタンスにアタッチするだけでもよかったかもしれないです

# ボットのコーディング
AWS SDKとdiscordのライブラリを使ってボットの作成を行う。
以下ソースコードである。また、コードを実行する前に環境変数として、
- Discordボットのトークン
- AWSのリージョン
- 起動したいインスタンスのID
- 使用するAWSアカウントのAWSアクセスキー
- 使用するAWSアカウントのAWSシークレットアクセスキー
を設定する必要がある。

```Python 
import os
import discord
import boto3
from discord.ext import commands

# 環境変数から認証情報を取得
TOKEN = os.getenv('DISCORD_TOKEN')
AWS_REGION = os.getenv('AWS_REGION')
INSTANCE_ID = os.getenv('AWS_INSTANCE_ID')

# Discordのクライアントを設定
bot = commands.Bot(command_prefix="/", intents=discord.Intents.all())

# AWS EC2クライアントの初期化
ec2 = boto3.client('ec2', region_name=AWS_REGION)

@bot.command(name='start')
async def start_instance(ctx):
    try:
        ec2.start_instances(InstanceIds=[INSTANCE_ID])
        response = f'Instance palworld has been started.'
        #response = "Hello world"
    except Exception as e:
        response = f'Error: {e}'
    
    await ctx.send(response)

@bot.command(name='stop')
async def stop_instance(ctx):
    try:
        ec2.stop_instances(InstanceIds=[INSTANCE_ID])
        response = f'Instance palworld has been stoped.'
        #response = "Hello world"
    except Exception as e:
        response = f'Error: {e}'
    
    await ctx.send(response)

# ボットを実行
#print(TOKEN,AWS_REGION, INSTANCE_ID)
bot.run(TOKEN)
```

https://github.com/GalleonSun/EC2ControlDiscordBot/blob/main/main.py

# ボットのデプロイ
上記のコードを使って、ボットをAWSのインスタンスにデプロイする。
インスタンスタイプはt2.nanoで、OSはUbuntuを採用した。
また、セキュリティルールとして、
1. 自分のIPアドレスからのSSH接続のみを許可
2. HTTPおよびHTTPS通信は全てのアドレスからの接続を許可

を設定した。もしさらに強固なセキュリティのベストプラクティスがあればコメントください。

# 構成
パルワールドのホスティングをしているメインサーバー : m6a.xlarge
Discordボットのホスティングをしているサブサーバー : t2.nano
Discord
ユーザー

# 余談
## 何故？
サーバー代を少しでも節約したいため。
## 経緯

元々は友人たちとのゲームセッションのために、私（サーバー管理者）がオンラインでないときでも遊べるようにするためにサーバーの設置した。どうせならとレンタルサーバーではなく独自のサーバーを構築することにしたが、24時間連続運用すると想像以上にコストがかかりそうとなり、サーバーを必要な時間だけ動かすという運用方法に切り替えた。

AWSインスタンスの起動と停止にはアカウントが必要だが、全ての友人に個別にアカウントを提供するのは手間がかかる上にセキュリティ上の懸念もある。そこで、AWSの最小サイズであるt2.nanoインスタンスにDiscordボットを設置し、それを使ってサーバーの起動と停止を制御することにした。これにより、サーバーを起動する際の心理的な負担も軽減され、Discordのコマンドを使えば、誰でも簡単にサーバーのオンオフを行うことができるようになり、良い感じになった。

# 今後の展望
現状はサーバーを付けた人が手動でコマンドを打ってサーバーを閉じるという運用になっているが、閉じ忘れることが容易に想像できる。そのため、10分間ゲームサーバーに通信が無かったら自動で閉じるというシステムを導入しようと考えている。
追記:Cloud Watch Metricsのアラーム機能を使ってNetworkPacketINを監視し、閾値を下回ったらアラームを発令しインスタンスを停止するという形で実装した