게임 전용 서버, 클라우드에 직접 올릴 때 부딪히는 것들

부팅한 지 5분 만에 스팀CMD(SteamCMD, 스팀 게임 서버를 설치·업데이트하는 도구)가 설치 오류 0x602를 뱉었다. 처음엔 네트워크 문제인 줄 알았다. 로그를 파고들어서야 같은 설치 폴더에서 스팀CMD가 두 번 동시에 돌고 있었다는 걸 알았다.
친구들과 즐기려고 2주짜리 팰월드(PalWorld) 전용 서버(dedicated server)를 구글 클라우드(GCP)에 올린 게 시작이었다. 게임 자체는 문제가 아니었다. 문제는 그 서버를 계속 살려 두는 쪽에서 터졌다. 가상 서버 한 대(인스턴스, instance)를 얼마나 잡을지, 메모리가 왜 자꾸 늘어나는지, 자동화 스크립트가 뭘 겹쳐 돌려서 깨지는지. 게임 하나 돌리는 데 이렇게 많은 조각이 얽혀 있는 줄은 몰랐다.
팰월드라서 겪은 문제가 아니었다. 스팀CMD로 데디케이티드 서버를 세운 사람이라면 게임 이름만 다를 뿐 거의 같은 지점에서 걸려 넘어진다.
전용 서버(dedicated server)는 게임 회사가 운영하는 공식 서버 대신, 컴퓨터 한 대를 통째로 빌려 그 위에서 게임 서버 프로그램을 직접 돌리는 방식이다. 공식 서버 인원 제한이 걸리거나 친구들끼리만 따로 모이고 싶을 때 흔히 쓴다. 팰월드도 정식으로 데디케이티드 서버 실행 파일을 배포해서 이 방식 자체는 가능했다. 문제는 그다음부터, 그러니까 그 컴퓨터를 어디서 어떻게 계속 살려 두느냐부터 시작됐다.
왜 GCP에 직접 올렸고, 뭘 골랐나
시작하기 전에 확인부터 했다. 팰월드 데디케이티드 서버는 스팀 앱 ID 2394010으로 배포된다. 윈도우와 리눅스 둘 다 네이티브로 돌아가고, 차이는 UE4SS라는 모드 도구(윈도우 전용) 사용 여부뿐이었다. 리눅스로 가는 데 걸림돌이 없다는 뜻이라 비용이 저렴한 우분투 쪽으로 마음을 굳혔다.
한 가지는 미리 걸러야 했다. 스팀CMD로 세운 데디케이티드 서버는 스팀 크로스플레이 네트워크에만 참여한다. 엑스박스나 PC 게임패스로 접속하는 사람은 못 들어온다. 다 같이 스팀판을 쓰는 친구들끼리 모였으니 문제는 안 됐지만, 미리 확인 안 했으면 접속 안 된다는 항의가 단체 채팅방에 쏟아졌을 상황이었다.
최종 선택은 이랬다. 운영체제는 우분투 22.04, 인스턴스는 n2d-standard-4(4vCPU·16GB), 디스크는 50GB 짜리 균형형 영구 디스크(pd-balanced), 서버가 실제로 돌아가는 지역(리전, region)은 asia-northeast3-a(서울)로 잡았다. 다들 한국에서 접속하니 지연을 줄이려면 당연한 결정이었다.
pd-balanced는 이름 그대로 성능과 비용 사이에서 절충한 디스크다. 로컬 SSD처럼 인스턴스 자체에 물리적으로 붙어 있어서 꺼지면 데이터도 사라지는 디스크와 달리, 인스턴스를 정지했다 다시 켜도 내용이 그대로 남는다. 게임 서버처럼 매일 껐다 켜는 운영 방식엔 이쪽이 맞았다.
방화벽 규칙도 그날 같이 정리했다. UDP 8211번과 27015번, TCP 27015번과 25575번을 열어야 게임 접속과 서버 목록 조회, 원격 관리까지 막히지 않는다. 포트를 하나라도 빠뜨리면 접속 화면에서 서버가 아예 안 보이거나, 보이는데 들어가지지 않는 애매한 상태로 남는다.
서버가 실제로 도는지는 몇 가지로 확인했다. 게임 서버 프로세스가 UDP 8211번으로 정상 수신 중인지, 서버 이름과 비밀번호 같은 설정값이 실제로 반영됐는지, 명령 하나로 palworld.service가 enabled 상태인지. 이 세 가지를 다 확인하고 나서야 됐다고 말할 수 있었다. 화면에 에러가 안 뜬다고 됐다는 뜻은 아니었다.
처음엔 4vCPU·16GB로 충분해 보였다. 실제로는 그 사양이 끝까지 유지되지 않았다. 이 이야기는 비용을 다루는 절에서 다시 나온다.
SteamCMD가 두 번 실행되며 터진 0x602 에러
서버를 자동으로 세우려고 부팅 시점에 도는 스크립트(startup-script)를 짰다. 이 스크립트가 스팀 실행용 계정을 만들고, 32비트 호환 라이브러리를 깔고, 스팀CMD를 설치하고, 리눅스에서 서비스를 관리하는 프로그램(systemd)에 palworld.service를 등록하는 것까지 전부 자동으로 처리하게 짰다.
문제는 게임 서버 설치 자체를 두 군데서 시켰다는 점이었다. startup-script 본문에서 한 번, systemd 서비스가 시작되기 직전에 실행되는 준비 단계(ExecStartPre)에서 또 한 번. 둘이 같은 설치 폴더를 향해 동시에 스팀CMD를 돌렸다. 검증 절차가 서로 겹치며 설치가 깨졌고, 그 결과가 0x602 에러였다.
스팀CMD 두 개가 같은 폴더를 두고 동시에 도는 상황은, 같은 서랍을 두 사람이 동시에 정리하는 것과 비슷했다. 한쪽이 파일을 채워 넣는 순간 다른 쪽이 그 자리를 검증하려 들면 결과물은 엉망이 된다. 설치 파일도 마찬가지였다. 한쪽이 쓰고 있는 파일을 다른 쪽이 검증하면서 체크섬이 어긋났고, 스팀 클라이언트는 이걸 손상(0x602)으로 판단했다.
처음엔 단순 네트워크 지연이라고 생각했다. 서비스 로그를 훑고 나서야 같은 시각에 스팀CMD 프로세스가 두 번 뜬 흔적을 찾았다. 원인을 좁히는 데 걸린 시간이 정작 해결책 네 가지를 적용하는 시간보다 더 길었다.
두 번째 원인도 있었다. 서비스가 시작되는 시점에 명령을 실행할 기준 폴더(WorkingDirectory)가 아직 만들어져 있지 않아 폴더 이동(CHDIR)이 실패했다. 로그에는 status=200/EXEC로 남았다.
해결책은 네 가지로 정리됐다.
- 스팀CMD 실행 지점을 ExecStartPre 하나로 좁히고, startup-script 쪽 중복 실행은 제거했다.
- 서비스가 시작되기 전에 설치 폴더를 미리 만들어 뒀다.
TimeoutStartSec을 1800초로 늘려 설치가 끝나기 전에 systemd가 강제 종료하는 일을 막았다.- 스팀CMD 실행에 재시도 루프를 붙였다.
이 패턴, 즉 같은 설치 폴더를 향한 동시 실행 충돌과 작업 폴더 부재는 팰월드에만 있는 문제가 아니다. 스팀CMD로 설치하는 다른 게임 서버에서도 똑같이 재발할 수 있다. 자동화 진입점을 하나로 좁히는 규칙만 지키면 대부분 피해 간다.
메모리 누수는 고치지 못해서 재시작으로 관리한다
왜 버그를 고치지 않고 재시작으로 넘어갔을까. 팰월드 서버에는 알려진 메모리 누수가 있어서다. 12시간에서 24시간 연속으로 돌리면 메모리 사용량이 4에서 6기가바이트씩 늘어난다. 코드를 손봐서 없앨 수 있는 문제가 아니라 게임 서버 자체의 한계였다.
메모리 누수는 물이 새는 양동이와 비슷하다. 구멍을 막을 수 없다면 넘치기 전에 물을 비우는 수밖에 없다. 재시작이 그 비우는 동작이다. 근본 원인을 없애는 대신 넘치는 시점을 계속 뒤로 미루는 셈이었다.
그래서 택한 방법은 정기 재시작이다. systemd에 재시작 타이머(palworld-restart.timer)를 걸어 하루 두 번, 새벽 4시와 오후 4시에 서비스를 자동으로 껐다 켰다. 상시로 켜 두는 인스턴스라 이 타이머가 매일 빠짐없이 발동했다.
재시작 횟수를 하루 몇 번으로 잡을지는 누수 속도와 실제 접속 패턴을 같이 봤다. 12시간에서 24시간 사이에 문제가 생긴다면 하루 두 번이면 어느 쪽이든 누적량이 절반 수준에서 끊긴다. 접속자가 몰리는 저녁 시간대를 피해 새벽과 오후로 시각을 잡은 것도 그래서였다.
다른 인스턴스는 가동 시간 자체가 달라서 타이머 기준도 맞춰야 했다. 주말에만 24시간 이어서 돌아가는 인스턴스는 시스템 시각이 UTC라서 타이머를 07시·19시 UTC(한국 시각으로는 16시·04시)로 잡았다. 평일에는 스케줄에 따라 인스턴스 자체가 꺼져 있으니 타이머가 발동할 일이 없고, 주말 연속 가동 구간에서만 실제로 작동한다.
재시작은 누수를 없애지 않는다. 다만 서비스가 죽기 전에 끊어서 안정적으로 보이게 만드는 우회로다. 세이브 데이터는 부팅 디스크에 그대로 남아 있어서 재시작 사이에도 진행도가 사라지지 않는다.
재시작 시각을 스크립트에 못 박아 자동화해 두면 사람이 매번 서버 상태를 확인하다 잊어버리는 일이 없다. 정해진 시각에 정해진 동작이 일어나게 만드는 원리는 다른 자동화에도 그대로 옮겨 담을 수 있다. 이 블로그에는 시간 조건으로 상태를 바꾸는 자동 발행 사례도 정리해 뒀다. 정기 재시작이든 정기 발행이든, 사람이 지켜보지 않아도 같은 시각에 같은 동작이 반복되게 만든다는 점은 같다.
인스턴스를 올리면 비용도 따라 움직인다
처음 잡은 n2d-standard-4로 시작했지만 그대로 가지 않았다. 한 차례 e2-standard-4로 바꿨다가, 나중엔 e2-standard-8(8vCPU·32GB)까지 다시 올렸다. 방법은 매번 같았다. 인스턴스를 정지하고, 머신 타입을 바꾸고, 다시 시작한다. 재구축 없이 사양만 바꿔 켜는 절차였다.
절차 자체는 생각보다 단순했다. 콘솔에서 인스턴스를 정지하고, 사양을 골라 저장하고, 다시 시작한다. 디스크와 고정 IP는 그대로 붙어 있어서 따로 재설정할 게 없었다. 다만 정지해 둔 동안엔 당연히 접속이 끊기니, 다들 안 들어와 있는 시간대를 골라야 했다.
더 저렴한 계열로 갈아탈 수 있는지도 확인해 봤다. c2d-standard-8로 전환을 시도했더니 리전 물량(quota, 특정 리전에 정해진 자원 할당량)이 없다는 응답(RESOURCE_POOL_EXHAUSTED)만 돌아왔다. 클라우드라고 원하는 사양을 언제나 즉시 받을 수 있는 건 아니었다. 정책도 하나 정했다. 나중에 물량이 풀리더라도 자동으로 갈아타지 않고, 먼저 확인받은 뒤에만 전환한다.
가동 패턴이 다르면 같은 사양이어도 비용이 크게 갈렸다. 상시로 켜 둔 인스턴스, 하루 일부 시간만 켜는 인스턴스, 주말 위주로 몰아 켜는 인스턴스를 각각 운영해 본 결과는 아래와 같다.
| 가동 패턴 | 머신 타입 | 만료까지 예상 비용 |
|---|---|---|
| 상시(24시간) | e2-standard-8 | 약 $228 |
| 하루 19시간 | e2-standard-8 | 약 $18 |
| 야간·주말 위주 | e2-standard-8 | 약 $44 |
같은 사양이라도 켜 두는 시간에 따라 비용 차이가 10배 넘게 벌어졌다. 인스턴스 스케줄로 필요 없는 시간에 꺼 두는 습관이 사양을 낮추는 것보다 비용에 더 크게 영향을 줬다. 다만 스케줄로 끄고 켜는 것과 완전히 삭제하는 건 다른 얘기다. 스케줄은 자원을 정지시킬 뿐이라, 과금을 아예 끝내려면 인스턴스와 고정 IP와 디스크를 사람이 직접 지워야 한다.
이 부분을 헷갈리면 두 주 쓰고 서버를 접었다고 생각했는데 다음 달 청구서에 고정 IP 요금만 계속 찍혀 있는 상황이 온다. 인스턴스가 꺼져 있어도 예약해 둔 고정 IP는 미사용 상태로도 소액이 계속 붙는다. 스케줄이 알아서 다 처리해 줄 거라고 믿고 잊어버리면 이 부분에서 새는 돈을 놓친다.
비용을 아끼는 가장 쉬운 방법은 결국 사양이 아니라 시간이었다. 8vCPU짜리 인스턴스를 하루 종일 켜 두는 것과, 접속자가 실제로 모이는 시간에만 켜는 것 사이의 차이가 사양 한 단계를 낮추는 것보다 훨씬 컸다. 사양을 고민하기 전에 언제 꺼도 되는지부터 정하는 편이 순서상 맞았다.
결론 — 다른 게임에도 옮겨 담을 수 있는 교훈
이번 경험을 관통하는 태도는 결국 하나였다. 처음부터 다 갖춰 놓으려 하지 않는 것. 사양은 넉넉하게 잡아두지 않고 필요할 때 올렸고, 메모리 누수처럼 코드로 못 고치는 문제는 정기 재시작으로 우회했고, 자동화 진입점은 하나로 좁혀 동시 실행 충돌을 막았고, 비용은 사양을 낮추는 대신 켜 두는 시간을 관리해서 줄였다.
남은 숙제도 있다. 재시작 타이머가 UTC 기준이냐 한국 시각 기준이냐로 두 번 헷갈렸다. 다음번엔 인스턴스를 새로 세울 때 시간대 확인을 자동화 스크립트 맨 앞줄에 체크 항목으로 박아 둘 생각이다. 사람이 매번 기억하는 것보다 스크립트가 한 번 물어보는 쪽이 안전하다.
팰월드가 아니어도 이 순서는 그대로 옮겨 담을 수 있다. 스팀CMD로 세우는 다른 게임 서버도 자동 업데이트, 서비스 등록, 정기 재시작이라는 뼈대는 크게 다르지 않다. 다음에 다른 게임으로 서버를 세울 일이 생기면, 이번에 걸린 네 가지부터 먼저 확인할 생각이다.
아직 댓글이 없습니다.