마인크래프트 서버 + 디스코드 봇(JS)

친구들이 갑자기 마인크래프트를 한다더라. 언제적 하마치를 사용하여 LAN 게임을 한다길래 가슴이 아팠다. 그래서 선뜻 서버를 열어주겠다고 했었는데, 솔직히 말하면 "Lightsail로도 마인크래프트 서버를 24시간 운영하는게 가능할까?" 라는 궁금증을 풀고 싶은 마음이 컸다.

  • 서버 사양
    • 2GB Ram
    • 1vCPU
    • 60GB Storage
  • 요금 : 10 USD

Conclusion

마인크래프트 서버 설정을 일부 낮추어 구동하였으며 플레이하는 유저들이 오브젝트의 갯수를 과도하게 늘리지 않았음을 전제함

성능은 그럭저럭 괜찮은 편. 게임중에 뭔가 느리다거나 하는 경우는 없었다.

다만 24시간 구동은 불가능 한 것으로 보인다.. 위 사진은 1-2주 동안 마인크래프트 서버를 돌리면서 발생한 CPU의 점유율버스터 사용량이다. 접속자가 1명이라도 발생한 경우 30-60%의 CPU 점유가 발생하기 시작한다. 그럼 버스터 사용량이 쭉쭉 빠져나가기 시작하는데 접속자가 없는 유휴시간에도 점유율이 10% 정도 유지되어 버스터 충전량이 사용량을 따라잡지 못한다.

추가적으로 언급하자면 버스터를 할 수 없는 상황에서 어느정도 퍼포먼스가 나오는지 전혀 확인된 바 없다. 한 번 상태를 파악해보고 싶긴하지만 게임중에 불편함을 주는건 싫어서 개인적으로 테스트를 해보도록 해야겠다. 여하지간 30%-60% 정도의 점유가 필요한 상황에서 20% 정도의 자원만을 사용한다면 플레이에 체감될 정도로 퍼포먼스가 떨어지지 않을까 예상된다.

Traffic

트레픽과 관련해서도 걱정이 됐었는데 예상외로 트래픽은 전혀 문제될게 없어 보였다. 1명이 6-8시간 동안 서버에 머무는 경우 3-4GB 정도의 아웃바운드가 발생하므로 사실상 3TB의 트레픽을 제공하는 이 인스턴스에서는 차고 넘치는 수준이다.

Solution

일단 버스터가 가능한 상황에서는 충분히 원활한 플레이가 가능하므로 유휴시간에 최대한 점유율을 낮춰서 게임중에 버스터가 가능한 상황을 만들고자 하였다. 주간에는 접속할 사람이 없으니 꺼두고 야간에만 켜는 방식을 사용하는게 좋을 듯 하다.

하지만 나는 평일에 게임을 하지 않으므로 게임 서버를 켰다 껏다하는 것을 깜빡할 가능성이 크고 접속자가 없을때까지 마냥 기다릴 순 없는 노릇이다. CRON을 사용하면 너무 간단하겠지만 더 많은 불편을 초래할 수 있을 것이라 생각됐다.

그래서 친구들과 사용하는 디스코드를 활용하고자 하였다. 서버를 채널을 통해서 운영하면 24시간은 아니지만 적어도 누구에게나 다운타임없이 게임을 플레이 할 수 있도록 제공할 수 있고, 종료도 간편해지므로 유휴시간에 더 많은 버스터 사용량을 획득할 수 있다.

Discord Bot

슬랙봇과 텔레그램봇은 WebHook을 기반으로 작동하여 당연히 디스코드 봇도 비슷한 원리일거라 생각하여 express 환경부터 구축했는데 예상외로 웹 서버가 전혀 필요하지 않았다. 내부적으로 소켓으로 연결해서 운영되는 것이 아닐까 예상되는데, 솔직히 내부 원리를 전혀 모르겠다. 여하지간 Node의 패키지로 제공하는 Discord.js의 사용법이 너무나 간단하고 직관적이라 간단하게 구축할 수 있었다.

일단 우리 서버에서만 사용할 계획이므로 private 봇으로 생성하였고 명령어를 입력받으면 Node의 spawn을 사용하여 스크립트를 실행하고 끄도록 구성하였다. typescript와 express로 설정을 하다가 바뀐거라 어줍짢은 타입이 붙어있다. 이 정도 배경지식만 알고보면 대충 코드는 이해할 수 있을 것으로 생각된다. 아래 정보를 추가적으로 알고 있으면 더 이해하기 쉽다.

  • 메세지를 전송 지연시간이 있다하여 로그를 모았다가 전송
  • spawn의 kill이 정상적으로 동작하지 않아 tree-kill 패키지 사용
const Discord = require('discord.js');
const bot = new Discord.Client();
const spawn = require('child_process').spawn;
const kill = require('tree-kill');

let mcs: any = undefined;
let mcsLogs: string[] = [];

let logSender;

bot.on('ready', () => {
    console.info(`Logged in as ${bot.user.tag}!`);
});
  
bot.on('message', (message: any) => {
    if (message.content === '!mcs start') {
        if(!mcs) {
            message.channel.send('서버를 구동합니다.');

            mcs = spawn('YOUR_MINECRAFT_SERVER_SCRIPT_PATH');

            logSender = setInterval(() => {
                if(mcsLogs.length > 0) {
                    message.channel.send(mcsLogs.join(''));
                    mcsLogs = [];
                }
            }, 800);

            mcs.stdout.on('data', (data: any) => {
                console.log('' + data);
                mcsLogs.push('' + data);
            });

            mcs.stderr.on('data', (data: any) => {
                console.log('' + data);
                mcsLogs.push('' + data);
            });
        
            mcs.on('close', (code: any) => {
                console.log("child process exited with code " + code);
                mcsLogs.push("child process exited with code " + code);
            });
        } else {
            message.channel.send('이미 서버가 구동중입니다.');
        }
    }

    if (message.content === '!mcs stop') {
        if(mcs) {
            kill(mcs.pid);
            mcs = undefined;
            message.channel.send('서버를 종료합니다.');
        } else {
            message.channel.send('실행중인 서버가 없습니다.');
        }
    }
});

bot.login('YOUR_DISCORD_BOT_TOKEN');

아래는 채널에서 서버를 시작하는 모습이다.

라이브러리들의 퀄리티가 점차 좋아지다보니, 진짜 조금만 시간을 들여도 이것저것 원하는 것을 뚝딱 만들 수 있구나. 훗날 노코드 시대도 빨리 왔으면 좋겠다.

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.