LoginSignup
5
7

ローコストで24時間MineCraftサーバーを実現する

Last updated at Posted at 2023-10-23

きっかけ

友人とマイクラで遊ぶぞってことで、私がサーバーを用意することになった。
いつも白羽の矢が立つが正直めんどくさい。しかも各々ログイン時間が異なるため24時間サーバーを運用する要件付き。
たまには欲しい物リストの1つや2つ、プレゼントしてくれ

メインPCでサーバーを構築するとリソースを食いすぎて支障なので、使っていないベアボーンPCをサーバーにすることにしたが、故障していて自宅サーバーは断念。
結局お金を出し合ってVPS借りる話になり、ちょうど私がAWS-SAAの勉強中だったのでAWSで構築することにした。

今回の要件

  1. 24時間ゲームに参加できる
  2. 参加人数は増える可能性あるためスケーラブルに
  3. できる限りローコスト

アーキテクチャ

コスト最適化のために必要なときにインスタンスを起動し、ゲームのアクティブが0のときEC2インスタンスを停止する。

起動、停止の度に私がコンソールで操作するのは面倒なので、LambdaのURL関数で参加者自身にインスタンスの起動させる。

Elastic IPアドレスを割り当てるとインスタンス停止中に課金されるので、起動の度に新しいIPアドレスをDiscordに通知する。

停止はインスタンスの中でcronを回し参加人数を監視する。0人でシャットダウンシェルを作動させる。

必要に応じて手動でインスタンスタイプをスケールアップする。

追加機能案メモ(折りたたみ)

シャットダウンシェルの中でS3にワールドデータをバックアップする。
停止時にもDiscordに通知する。EventBridge→Lambda経由
スポットインスタンスを活用する

AWS.png

コストについて

今回、構築段階では無料枠だけで済むように構成したので規模が小さければ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を操作できないため作成する。

ポリシー作成

JSON
{
    "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に設定しています。

コード

Python
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_idwebhook_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のダウンロードリンクを置換

console
#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に書き換えた。

参考

ActiveMonitoring.py
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勉強しよ。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7