見出し画像

Discordから起動できるMinecraftサーバをEC2上に建てる

皆さんこんにちは。
 
システム開発部の成清です。
 
私は以前から個人PC上にサーバを構築し、友人とMinecraftのマルチプレイを楽しんでいましたが、PCを立ち上げたままにしておく必要がある個人PC上でのサーバ運用では全員が満足に遊べる時間PCを起動しっぱなしにしておくことが難しい場面が多くなってきました、、、
そこで今回は、AWSを利用してMinecraftのマルチプレイサーバを(ある程度)安価に立ち上げつつ、Discordとの連携を目指すことにしました。


1.今回達成したいこと

今回は以下のような要件を満たすことを目的としました🙇‍♂️

  • できるだけお金をかけない!

  • 最大同時プレイ人数は5人程度

  • modは使用しない

  • 毎日定刻にサーバのシャットダウンを行う

  • Discordのbotを使用し、EC2の起動を行う(誰でも簡単にサーバを起動できるようにしたい)

上記を実現するため、以下のような構成を組んでいきます。

2.EC2インスタンスの作成

まず、EC2インスタンスを作成していきます。
 
今回はインスタンスタイプとして「t3a.medium」を選択しました。
 
無料で利用できる 「t2.micro」 の使用も考えたのですが、私が遊んでいるJava版のMinecraftの最低動作環境を満たしていなかったため不採用としました。
 
また、ストレージは特に理由はないですが、無料範囲内の20GBとしました。

3.Minecraftサーバの設定

次に、立ち上げたEC2にMinecraftサーバを入れる作業を行います。
まず、任意の方法でEC2にSSH接続(私はRLoginを使用)します。
Javaを入れていきます、古すぎるバージョンだとサーバが正しく起動しない場合があるようなので注意です。

$ sudo yum update
$ sudo yum install java-1.8.0-openjdk

[MCVersions.net - Minecraft Versions Download List](https://mcversions.net/)
 
上のURLから、ほしいバージョンのserver.jarを探し、ボタンを右クリック→リンクをコピーします。

$ mkdir ~/minecraft
$ cd ~/minecraft
$ wget ‘右クリックで取得してきたURL’

一度サーバを起動する(今回はt3a.mediumを使用しているため、メモリを2048MB割り当ててます、使用するインスタンスに応じて調整してください)

$ java -Xms2048M -Xmx2048M -jar server.jar nogui

初回起動時はエラーが出るので、serve.jarと同じディレクトリに作られているeula.txtのeula=falseをeula=trueに書き換えてください。

$ # eula=false
$ eula=true

書き換え後、再起動

$ java -Xms2048M -Xmx2048M -jar server.jar nogui

サーバが起動されるので(Done~のようなログが出たらOK)ゲームを起動し、ダイレクト接続でEC2インスタンスのパブリックIPを入力すると、サーバにログインできるはずです。(ログインしたら夜でした🌙)

4.IPアドレスの固定化

結論から言うと、今回はIPアドレスの固定化は行いません。
 
インスタンスのIPアドレスの固定化をしていないと、インスタンスの再起動を行うたびにIPアドレスが変わってしまうため、毎回AWSコンソールにIPを確認しに行き、「今度のIPは○〇だよ!」とサーバに参加する人に伝える必要があります(やれないことはないが、とても面倒くさい…)
 
そこで、普通はElastic IPというAWSのサービスを使用し、インスタンスに固定のIPアドレスを割り振るのですが、このElastic IPというサービス、固定IPを所持しているだけで料金が発生してしまうため、ローコストで行きたい今回は、使用しないこととしました。
 
その代わりとして、再起動で変わったIPアドレスをDiscord Botを使用してDiscordに送信するようにします(詳しくは「7.Discordとの連携」で説明します)

5.Minecraftサーバの自動起動

Minecraftサーバを起動するには.jarファイルを実行する必要がありますが、毎回インスタンスを起動→サーバにSSH接続→.jarファイルを起動、とやるのは手間なので、インスタンスの起動時にMinecraftサーバが自動的に起動するように設定していきます。
まずは、以下からEC2インスタンスのユーザデータを設定します。
※ユーザデータはインスタンスの起動中は編集ができないため、一度停止してから編集を行ってください。

ユーザデータは以下のように設定しました、ユーザデータから直接サーバ起動コマンドを叩くこともできるようなのですが、なぜかうまくいかなかったため…少し回りくどいですが今回はEC2内に起動用ファイル(/minecraft/launch.sh)を作成し、それを呼び出すことにしました。

Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
sudo sh /minecraft/launch.sh
--//

launnch.shの中身は以下

cd /minecraft/server # server.jar ファイルを設置したパス
java -Xmx2048M -Xms2048M -jar server.jar nogui

6.インスタンスの自動シャットダウン

インスタンスは起動しているだけで着々と料金がかかってしまうため、少しでも料金を抑えるため、インスタンスが起動している場合、毎日朝5時に自動でシャットダウンを行うように設定します。
AWS EventBridgeを使用してスケジュールの設定を行います。
※黒塗り部分には対象のインスタンスIDが入ります。

ここまでで、AWS側の設定は一旦完了となります、インスタンスの起動さえできれば、Minecraftサーバの自動立ち上げからシャットダウンまでやってくれるようになりました、お疲れさまでした。

7.Discordとの連携

ここからは、Discord側からインスタンスを起動する設定に入っていきます。
 
最終的な動作は、Discordでスラッシュコマンド「/serverstart」と送るとDiscord bot を通じてLambaを呼び出し、インスタンスが起動、起動したインスタンスのIPアドレスがDiscord webhook を通じて返ってくる、という状態を目指します。
 
Discord BotとWebhookを使用して以下の設定を行います。

  • Discord Botを作成し、サーバに追加

  • Discord Botにサーバ起動コマンドを追加

  • Discord BotからLambdaを呼び出す

  • EC2インスタンスが起動し、その後自動でMinecraftサーバが起動する

  • 「起動完了 IP:111.222.333.444」とメッセージWebhook経由でDiscordに送られる

まずは、Discord botから呼び出すLambdaを作成していきます。
インスタンスが起動しているかどうかを確認し、起動していなければ起動処理を行い、処理完了後にインスタンスに割り当てられたIPをDiscordのWebhookURLに返すというものを作成します。
 
そのためにまずはDiscordのWebhookURLを以下から取得します。
メッセージを送信したいサーバに行き、左上のサーバ名をクリック→サーバ設定を開きます。

一応curlコマンドで問題なく送信されることを確認

#!/bin/bash

# Webhook URL
WEBHOOK_URL="DISCORD_WEBHOOK_URL"

# メッセージコンテンツ
MESSAGE_CONTENT="Hello, this is a test message sent via webhook!"

# リクエストボディ
REQUEST_BODY="{\"content\":\"$MESSAGE_CONTENT\"}"

# POSTリクエストを送信
curl -X POST -H "Content-Type: application/json" -d "$REQUEST_BODY" "$WEBHOOK_URL"

次に、Lambdaを作成します、LambdaにはEC2の起動や、起動状態の確認をする必要があるため、EC2へのアクセス権をIAMで付与します。
 
以下Lambdaのコード

import boto3
import time
import requests

def lambda_handler(event, context):
    # EC2インスタンスID
    instance_id = 'EC2のインスタンスIDが入ります'

    # EC2インスタンスを起動するためのパラメータ
    ec2 = boto3.client('ec2')
    response = ec2.start_instances(InstanceIds=[instance_id])

    # すでにEC2インスタンスが起動しているかどうかの確認
    instance = ec2.describe_instances(InstanceIds=[instance_id])
    state = instance['Reservations'][0]['Instances'][0]['State']['Name']
    if state != 'running':
        # 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 = 'DiscordのWebhookURLが入ります'
    data = {
        'content': f'インスタンス【{instance_id}】は起動しました\r{public_ip}'
    }
    requests.post(webhook_url, json=data)
    
    return public_ip

メモリ、エフェメラルストレージはデフォルトの128MB、512MBのままにしました。
 
タイムアウトはEC2の起動を待つ必要があるので余裕をもって5分に変更しました。
 
設定が完了後、Discord Botから呼び出すように関数URLを発行します。

そして、Discord botの作成ですが、以下の記事を参考に作成させていただきました。
 
https://note.com/exteoi/n/n00342a623c93
 
以下は上記記事内のnyan.mjsをベースに作成したLambda関数URL呼び出しのコードです、GlitchがNode16までしか対応していなかったため、ライブラリを使用しないHTTPSリクエストのコードを採用しました。

import { SlashCommandBuilder } from '@discordjs/builders';
import https from 'https';

export const data = new SlashCommandBuilder()
  .setName('serverstart')
  .setDescription('サーバを起動します');

export async function execute(interaction) {
  const url = process.env.LAMBDA_URL;

  try {
    await interaction.reply('サーバ起動処理開始');

    const data = await new Promise((resolve, reject) => {
      https.get(url, (res) => {
        let data = '';

        res.on('data', (chunk) => {
          data += chunk;
        });

        res.on('end', () => {
          resolve(data);
        });

      }).on('error', (error) => {
        reject(error);
      });
    });

  } catch (error) {
    await interaction.followUp('起動処理呼び出し失敗');
  }
}

最後に動作確認を行います。
インスタンスは停止済み

Discordでスラッシュコマンドを実行
IPが返ってきた!

インスタンスが起動してる!

ログインできる!(インスタンスの起動後2~3分のラグはあれど!)

やったー!

その後、他の人も接続できることを確認👬👬

ということで、以上がAWS上にMinecraftサーバを立ち上げ、それをDiscordから起動するまでの流れでした。
今回、とても試行錯誤しながらの作業となったため、作業開始から10時間前後かかってはしまいましたが、勉強になった部分も多かったですし、なんとか形になったので良かったです。
それでは、ここまで読んでいただいた方はありがとうございました!

8.おまけ:今後の予定?

とりあえず起動するには至りましたが、今後は以下のこともやれたらいいなー、と思ってます。(思ってるだけの可能性も高いです。)
- 再起動/停止コマンドの作成
- サーバに誰もいなかったら自動シャットダウン
- S3への自動バックアップ
- modの導入

ユニゾンシステムズでは、一緒に働く仲間を募集しています。
ぜひ一度オフィスに遊びに来てみませんか?お気軽にDMもお待ちしています!

求人の詳細はこちら: https://www.unixon.co.jp/recruit/
Youtube: https://www.youtube.com/channel/UCGacmgfpJ0fkHC0aKrSppVw
X(Twitter): https://twitter.com/unixon_recruit

この記事が気に入ったらサポートをしてみませんか?