きっかけ
友人とマイクラで遊ぶぞってことで、私がサーバーを用意することになった。
いつも白羽の矢が立つが正直めんどくさい。しかも各々ログイン時間が異なるため24時間サーバーを運用する要件付き。
たまには欲しい物リストの1つや2つ、プレゼントしてくれ
メインPCでサーバーを構築するとリソースを食いすぎて支障なので、使っていないベアボーンPCをサーバーにすることにしたが、故障していて自宅サーバーは断念。
結局お金を出し合ってVPS借りる話になり、ちょうど私がAWS-SAAの勉強中だったのでAWSで構築することにした。
今回の要件
- 24時間ゲームに参加できる
- 参加人数は増える可能性あるためスケーラブルに
- できる限りローコスト
アーキテクチャ
コスト最適化のために必要なときにインスタンスを起動し、ゲームのアクティブが0のときEC2インスタンスを停止する。
起動、停止の度に私がコンソールで操作するのは面倒なので、LambdaのURL関数で参加者自身にインスタンスの起動させる。
Elastic IPアドレスを割り当てるとインスタンス停止中に課金されるので、起動の度に新しいIPアドレスをDiscordに通知する。
停止はインスタンスの中でcronを回し参加人数を監視する。0人でシャットダウンシェルを作動させる。
必要に応じて手動でインスタンスタイプをスケールアップする。
追加機能案メモ(折りたたみ)
シャットダウンシェルの中でS3にワールドデータをバックアップする。
停止時にもDiscordに通知する。EventBridge→Lambda経由
スポットインスタンスを活用する
コストについて
今回、構築段階では無料枠だけで済むように構成したので規模が小さければ0円サーバーが完成する。
サーバースペックが足りないようなら、スケールアップでその都度課金するイメージ。
環境
EC2 OS | Amazon Linux 2023 |
EC2 タイプ | t4g.small (Arm系) |
Lambda | Python 3.11 |
Tera Term | 5.0 beta1 |
Discord | Stable 235476 (a7cb1d5) |
MineCraft | JAVA版 1.20.2 |
作業手順
LinuxとAWSについてある程度の知識があることを想定しています。
以下のことは完全には記述しません。
- Linuxの操作コマンド
- AWSサービスの機能説明、料金等
各種名付けは自分で分かるものを適当につけてください。
EC2インスタンスの作成
- AMI
- Amazon Linux 2023 : Arm
- インスタンスタイプ
- t4g.small
- キーペア
- RSAのpem形式 SSH接続で使用
- VPC
- デフォルト
- セキュリティグループ
- SSH と TCP: 25565 すべて許可
- ストレージ
- 8GB gp3 足りなければ後から足せる
このあとLambdaで起動テストをするため、起動後停止させる。
LambdaにアタッチするIAMロールを作成
IAMロールがないとLambdaからEC2を操作できないため作成する。
ポリシー作成
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:DescribeInstances",
"ec2:StopInstances"
],
"Resource": "*"
}
]
}
ロール作成
ユースケースはLambda
カスタマー管理で絞り込んで前に作ったポリシーをアタッチ
Lambda関数の作成
- ランタイム
- Python3.11
- アーキテクチャ
- x86_64
- 実行ロール
- 前に作ったもの
- 詳細設定
- 関数URLを有効化
- 認証タイプ
- NONE
認証タイプをNONEにすると誰でもLambdaを実行させることができるため、セキュリティが低下します。
AWS_IAMを設定する場合、AWS-SDK使って署名を付与してリクエストする必要があります。
今回は手軽にブラウザからリクエストすることを想定しているのでNONEに設定しています。
コード
import boto3
import time
import requests
def lambda_handler(event, context):
# EC2インスタンスID
instance_id = 'i-#########'
# EC2インスタンスを起動するためのパラメータ
ec2 = boto3.client('ec2')
response = ec2.start_instances(InstanceIds=[instance_id])
# EC2インスタンスが起動するのを待つ
instance_running = False
while not instance_running:
instance = ec2.describe_instances(InstanceIds=[instance_id])
state = instance['Reservations'][0]['Instances'][0]['State']['Name']
if state == 'running':
instance_running = True
else:
time.sleep(5)
# EC2インスタンスのパブリックIPアドレスを取得する
instance = ec2.describe_instances(InstanceIds=[instance_id])
public_ip = instance['Reservations'][0]['Instances'][0]['PublicIpAddress']
#DiscordにWebhookを送信
webhook_url = 'https://discord.com/api/webhooks/################'
data = {
'content': f'インスタンス【{instance_id}】は起動しました\r{public_ip}'
}
requests.post(webhook_url, json=data)
return public_ip
instance_id
とwebhook_url
を書き換えて、Deployを忘れずにしてください。
レイヤー追加
このままコードを実行してもrequests
モジュールが使えないのでレイヤーを追加する。
参考
arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p311-requests:2
タイムアウト
インスタンスの起動待機中にタイムアウトになると困るのでタイムアウトを長めにする
一般設定
タイムアウト 3分
動作確認
ブラウザから関数URLにアクセス
1分くらい待っていると起動したインスタンスのIPアドレスがレスポンスされる。
DiscordにもWebhookが送信される。
インスタンスの中を設定
インスタンスを立ち上げてSSHでMinecraftサーバー構築と停止スクリプトを設定する。
Amazon Linux 2023にTera Termで接続する際はバージョン5.0 beta1
でないと接続できない。
追記
10/15にTeraTermのメジャーバージョン5.0が18年ぶりにリリースされたのでそっちを使いましょう。
他にもWin10から標準でcmdにSSHができたことを知りました。
ユーザー名:ec2-user
パスフレーズ:なし
RSAキーを使う:キーペア作成でダウンロードした.pem
Minecraftサーバー設定
バージョンは1.20.2
別のバージョンがよければ、下サイトからwget
のダウンロードリンクを置換
#Java17をインストール
sudo yum install java-17-amazon-corretto-headless
#サーバー設置のディレクトリを作成
mkdir minecraft
cd minecraft
#サーバーjar(1.20.2)をダウンロード
wget https://piston-data.mojang.com/v1/objects/5b868151bd02b41319f54c8d4061b8cae84e665c/server.jar
#jarを実行
java -Xmx1G -Xms1G -jar server.jar nogui
#eulaを編集
vim eula.txt
#もう一度実行
java -Xmx1G -Xms1G -jar server.jar nogui
#下のログ表示で起動完了
[Server thread/INFO]: Done (70.809s)! For help, type "help"
実際にクライアントで接続できるか試す。
良ければstop
で止める。
jarを実行する際のオプションの数値を変更することによって、
メモリ割当量(ヒープサイズ)を調整できる。
インスタンス起動時にマイクラサーバを自動起動
以下を参考にほぼ同じ
$ 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 -DmS minecraft /bin/java -server -Xms1G -Xmx1G -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=60s
[Install]
WantedBy=network-online.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable minecraft
これでインスタンスの起動でマイクラサーバーが自動起動して、
インスタンスが停止するとワールドを保存して終了までできる。
ログインを監視して自動停止
/home/ec2-user/minecraft/logs/latest.log
を監視してアクティブが0になると自動停止するcronを作成。
どんなコード書こうか考えてたら同じことしてた人が居たため参考にした。
ただコピペしても面白くないので解らないRubyをなんとなく解釈してPythonに書き換えた。
参考
import datetime
import os
file_path='/home/ec2-user/minecraft/logs/latest.log'
f=open(file_path)
in_cnt=0
out_cnt=0
starting=True
#起動後15分はシャットダウンしない
for i, l in enumerate(f.readlines()):
line = l.strip()
if i==0:
hms=line.split()[0][1:-1].split(':')
before=int(hms[0])*3600+int(hms[1])*60+int(hms[2])
now=datetime.datetime.now()
after=now.hour*3600+now.minute*60+now.second
diff = after - before
diff+=24*3600 if diff<0
starting=False if diff >= 15*60
if 'joined the game' in line:
in_cnt+=1
if 'left the game' in line:
out_cnt+=1
if starting:
print('server is starting')
elif in_cnt > out_cnt:
print('someone is login')
else:
print('shutdown now')
os.system('sudo shutdown -h now')
cron登録
10分間隔で実行
sudo yum install cronie -y
sudo systemctl enable crond.service
sudo systemctl start crond.service
sudo crontab -e
*/10 * * * * /usr/bin/python3 /home/ec2-user/ActiveMonitoring.py
sudo systemctl restart cron
感想
AWSを使って構築はいろいろな組み合わせを考えたりできて、面白かった。
もっといろいろなサービスを使ってみたかったが、コスト優先で使うサービスはEC2とLambdaだけを使用した。
ググるとEC2でマイクラサーバーを立てる人は意外とたくさんいたし、自動化をする人もいて考えることは同じなんだなーと思った。
自分自身、特にコーディング技術力がなくて自動停止の実装が遅れたがRubyをPythonに書き換えるいい経験ができた。
最後に
使い方によるが、AWSは$で請求が来るので超円安の現在(149円)は国産VPSで完全24時間サーバーのほうが簡単で安かったかもしれない。
来年からIPv4も有料になるし。IPv6勉強しよ。