EC2に建てたマイクラサーバーをdiscordから起動したり自動シャットダウンしたり
なんとなく備忘録
サーバー費節約のため使うときだけdiscordから起動の指示を出して、誰もいなければ自動で閉じるようにする。作るものは
- インスタンスの起動停止 -> マイクラサーバーの起動停止
- discordのチャット -> インスタンスの起動停止
- 一定時間サーバーが0人 -> インスタンスを停止
のフローで動くもの
instanceのセットアップとサーバー開設
アカウントはもうあってインスタンスの建て方も分かる前提でマイクラサーバー向けの説明多め
インスタンス
インスタンス作成
インスタンスの建て方やサーバー開設の説明はかなり端折るので分からない場合これとか見て
インスタンスタイプは
-
t2.micro無理 無料枠あるけどJE1.18だとメモリが1Gじゃ多分足りない - t4g.small 最低限 メモリ2G
- t4g.medium こっちのほうがよさそう メモリ4G
t4gはArmだけど特にハードに依存する部分はなかったと思う。サーバーはCPU能力がシングルスレッド依存らしいからこれ以上増強するならt4g.largeよりr6g.mediumの方がいいのかな。しらんけど。
てかt4g.mediumを常時起動すると$31/monthかかるけどLightsailなら同じスペックが$20固定で使えるから1日16時間以上起動してるならLightsailの方が安い
インスタンスの設定
セキュリティグループの設定からインバウンドルールを追加し、カスタムTCPの25565番を0.0.0.0/0から受け入れ
ElasticIPを設定しておくとIPが固定されて便利だけどサーバーを閉じてる間も15円/日のペースで課金される
サーバー開設
viの使い方
iでinsertモード escで戻れる
ZZで保存して終了 :q!で保存せず終了
Javaのインストール
ここはEC2上でやるのでインスタンスにアクセス。
Amazonが作ったJDKのCorrettoを入れる。
これのInstall Amazon Corretto 18 on RPM-Based Linuxから
$ sudo rpm --import https://yum.corretto.aws/corretto.key
$ sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo
$ sudo yum install -y java-18-amazon-corretto-devel
を実行するだけ
サーバーのインストール
ホームディレクトリの下にminecraftフォルダを作ってcd minecraft
で/home/ec2-user/minecraftに入ったら
wget "https://launcher.mojang.com/v1/objects/125e5adf40c659fd3bce3e66e67a16bb49ecc1b9/server.jar"
で公式サーバーをダウンロード。軽さを求めるならpaperMCやプラグイン入れるならSpigotのダウンロードURLを代わりに入れればいい。その場合ダウンロード後に
$ mv paper-1.18.2-339.jar server.jar
で名前をserver.jar
にしておくと以後コピペが楽
でも非公式サーバーだとログの体裁が違ってこれから作るコードが動かないかもしれないから自己責任で
$ vi start.sh
を実行して
#!/bin/sh
java -Xmx1024M -Xms1024M -jar server.jar nogui
を書いて保存して
$ sh start.sh
で起動後勝手に停止。eula.txtのeula=falseをeula=trueにして再度起動。ここで入れるかチェック。
インスタンスの起動とマイクラサーバー起動の連携
この記事を参考に
$ sudo vim /etc/systemd/system/minecraft.service
で開いて
[Unit]
Description=Minecraft Server
After=network-online.target
[Service]
WorkingDirectory=/home/ec2-user/minecraft
User=ec2-user
ExecStart=/bin/bash -c '/bin/screen -DmSL minecraft /bin/java -server -Xms1024M -Xmx1024M -jar ./server.jar nogui'
ExecReload=/bin/screen -p 0 -S minecraft -X eval 'stuff "reload"\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "say Server Shutdown. Saving map..."\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "save-all"\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "stop"\\015'
ExecStop=/bin/sleep 10
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=network-online.target
を書いて保存。network-online.targetにインストールしてるのをそのまま使ってしまっているが筆者の用途とは違うかもしれない。あと、以降はメモリの変更はここで-Xms1024M -Xmx1024M
を変えることで行う。start.shはもう使わないから消そう。
書けたら
$ sudo systemctl daemon-reload
$ sudo systemctl enable minecraft
で自動起動を有効に
discordbot
ここではdiscordにチャットでコマンドを打ち込むとEC2のインスタンスを起動停止してくれるdiscordbotを作成し、無料で常時使えるHeroku上にデプロイする。(2022/12/13追記)Heroku有料になっちゃった…
discordbotの作成と招待
2.Applecationページに移動してサーバーに招待するまでが困ったので書いておく
OAuth2のScopesのbotにチェックを入れ、権限を設定すると招待URLができるので動かしたいサーバーに入れておく
Bot -> TokenからTokenを生成してメモしておく。AWSのアクセスキーも取得してメモしておく。
discordbot本体のコード
ここは自PCで行う
root権限を与えたアクセスキーが流出するとやばいのでbot用に権限を絞ったIAMユーザーを作って使う。
詳しくはこちらの記事参照↓
# インストールした discord.py を読み込む
import discord
import boto3
import sys
# 自分のBotのアクセストークンに置き換えてください
TOKEN_DIR="token.txt"
with open(TOKEN_DIR) as f:
lines = f.read().splitlines()
TOKEN = lines[0]
SERVER_CHANNEL = int(lines[1])
AWSAccessKeyId = lines[2]
AWSSecretKey = lines[3]
AWSInstanceID = lines[4]
ec2 = boto3.resource('ec2',
aws_access_key_id = AWSAccessKeyId,
aws_secret_access_key = AWSSecretKey ,
region_name ='ap-northeast-1'
)
instance = ec2.Instance(AWSInstanceID)
# 接続に必要なオブジェクトを生成
client = discord.Client()
# 起動時に動作する処理
@client.event
async def on_ready():
# 起動したらターミナルにログイン通知が表示される
await client.get_channel(SERVER_CHANNEL).send('bot online.')
# メッセージ受信時に動作する処理
@client.event
async def on_message(message):
# メッセージ送信者がBotだった場合は無視する
if message.author.bot or message.channel.id != SERVER_CHANNEL:
#print(message.content)
return
if message.content=="!exit":
exit()
return
if message.content=='!start':
try:
instance.start()
instance.wait_until_running()
await message.channel.send('server online.')
except:
await message.channel.send('failed to start instance.')
return
if message.content=='!stop':
instance.stop()
instance.wait_until_stopped()
await message.channel.send('server stopped.')
return
if message.content.startswith('!'):
await message.channel.send('WARNING: not a command.')
# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)
region_name ='ap-northeast-1'
って東京リージョンだと決め打ちしちゃってるけど違う場合は適宜変えて
次に、やめた方がいいんだけどdiscordbot.pyと同じフォルダにtoken.txtファイルを作って
NKASDFi3niGEPi39fJDiscordBotToken390ng/
2379279239789617
AS9-AWSACCESTOKENfMI)Aji
weAWSACCESSSecreTKeyj911
i-0instanceIdt4
こんな感じに
- DiscordBotのトークン
- コマンドの対象にしたいDiscordチャンネルのID
- AWSアクセストークン
- AWSシークレットキー
- インスタンスID
のデータを作成した(上のはダミーだよ)。これを読んでいる人がいたら序盤のトークンのところは適当に書き換えて環境変数を読むように設定してほしい。流出したらほんとに大変なので。
Herokuへのデプロイ
Heroku
Herokuが読み込めるようにdiscordbot.pyと同じフォルダに追加ファイルを用意。名前も大事
runtime.txt
python-3.8.12
requirements.txt
discord.py
boto3
Procfileにはさっき作ったpyファイルの名前を入れる。名前の大文字小文字を間違えないように(後述)
Procfile
discordbot: python discordbot.py
デプロイのやり方は全部HerokuのDeployのページのDeploy using Heroku Git
のところに書いてあるのでそれを参考にここでは簡単に書く。
$ heroku login
でログインし
$ cd <discordbot.pyとかがあるフォルダ>
$ git init
$ git add .
$ git commit -am "mayusuki"
$ git push heroku master
でgitを使ってherokuにアップする。
Herokuは1機だけ無料で以降は金がかかるらしい。
動いていればdiscordサーバーにbot online.
と送信されてくるはず。
discordのチャットに!startでインスタンスの起動、!stopで停止
gitを使うのが初めての場合
$ git config --global user.name "Namae Anata"
$ git config --global user.email namae.anata@example.com
といった具合に名前とメアドの設定が必要だと思う
Procfileをprocfileとしてしまい動かない問題が起きたがこれだった。
自動シャットダウン
cronを使って5分おきにサーバー人数を確認して0人が続いていたらシャットダウンするコードを作成し、インスタンス上に設定する。なお、厳密に最後のログアウトから5分後にシャットダウンするわけではないのでご注意を。
インスタンス停止用コード
インスタンスに接続すると/home/ec2-userにいるはずなので
$ pip3 install boto3
でboto3をインストール。
$ vi count_active.py
を実行し、下記のきったねぇコードを打ち込む
import os
import boto3
import re
TOKEN_DIR="token.txt"
LOG_DIR='minecraft/screenlog.0'
with open(TOKEN_DIR) as f:
lines = f.read().splitlines()
TOKEN = lines[0]
SERVER_CHANNEL = int(lines[1])
AWSAccessKeyId = lines[2]
AWSSecretKey = lines[3]
AWSInstanceID = lines[4]
ec2 = boto3.resource('ec2',
aws_access_key_id = AWSAccessKeyId,
aws_secret_access_key = AWSSecretKey ,
region_name ='ap-northeast-1'
)
pattern = r'^\[\d\d:\d\d:\d\d\]\s\[Server thread/INFO\]:\sThere\sare\s(\d+)\sof\sa\smax\sof\s\d+\splayers\sonline:.*$'
repat = re.compile(pattern)
line_latest=None
line_before=None
def isListLog(line):
return repat.fullmatch(line) is not None
def main():
with open(LOG_DIR) as f:
lines = f.read().splitlines()
lines.reverse()
i=0
for s in lines:
if isListLog(s) and i==0:
line_latest=s
i+=1
continue
if isListLog(s) and i==1:
line_before=s
i+=1
break
if i<2:
exit()
num_latest=int(repat.fullmatch(line_latest).groups()[0])
num_before=int(repat.fullmatch(line_before).groups()[0])
if num_latest == 0 and num_before == 0:
instance = ec2.Instance(AWSInstanceID)
instance.stop()
if __name__ == '__main__':
main()
さっき作ったtoken.txtをec2に送り付けておく。
cronの設定
$ crontab -e
でcronの設定を開き、
* * * * * /bin/screen -p 0 -S minecraft -X eval 'stuff "list\015"'
*/5 * * * * /usr/bin/python3 /home/ec2-user/count_active.py
とする。これで1分に1度サーバーにlistコマンドを打ち、count_active.pyで人数の確認とサーバーのシャットダウンをする。*/5
を*/10
にするとタイムリミットが10分になる
cronについて詳しくはこちら
起動時設定
screenlog.0が残っているのでcronの時間周期がサーバー起動と重なって起動してすぐcount_active.pyが実行されると即終了してしまう。
ということで終了時にscreenlog.0を消す設定をさっき作ったminecraft.serviceに追加する。
$ sudo vim /etc/systemd/system/minecraft.service
から
ExecStop=/bin/rm /home/ec2-user/minecraft/screenlog.0
を追加する、すなわち
[Unit]
Description=Minecraft Server
After=network-online.target
[Service]
WorkingDirectory=/home/ec2-user/minecraft
User=ec2-user
ExecStart=/bin/bash -c '/bin/screen -DmSL minecraft /bin/java -server -Xms1024M -Xmx1024M -jar ./server.jar nogui'
ExecReload=/bin/screen -p 0 -S minecraft -X eval 'stuff "reload"\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "say Server Shutdown. Saving map..."\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "save-all"\\015'
ExecStop=/bin/screen -p 0 -S minecraft -X eval 'stuff "stop"\\015'
+ ExecStop=/bin/rm /home/ec2-user/minecraft/screenlog.0
ExecStop=/bin/sleep 10
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=network-online.target
となるので保存
$ sudo systemctl daemon-reload
$ sudo systemctl enable minecraft
で設定すればOK(これ打たなくても設定されてるかも)
自動で閉じるようになったのでさっき作ったdiscordbotで停止コマンドを打つ部分
if message.content=='!stop':
instance.stop()
instance.wait_until_stopped()
await message.channel.send('server stopped.')
return
は削除してもいい
Discussion