부팅한 지 5분 만에 스팀CMD(SteamCMD, 스팀 게임 서버를 설치·업데이트하는 도구)가 설치 오류 0x602를 뱉었다. 처음엔 네트워크 문제인 줄 알았다. 로그를 파고들어서야 같은 설치 폴더에서 스팀CMD가 두 번 동시에 돌고 있었다는 걸 알았다.

친구들과 즐기려고 2주짜리 팰월드(PalWorld) 전용 서버(dedicated server)를 구글 클라우드(GCP)에 올린 게 시작이었다. 게임 자체는 문제가 아니었다. 문제는 그 서버를 계속 살려 두는 쪽에서 터졌다. 가상 서버 한 대(인스턴스, instance)를 얼마나 잡을지, 메모리가 왜 자꾸 늘어나는지, 자동화 스크립트가 뭘 겹쳐 돌려서 깨지는지. 게임 하나 돌리는 데 이렇게 많은 조각이 얽혀 있는 줄은 몰랐다.

팰월드라서 겪은 문제가 아니었다. 스팀CMD로 데디케이티드 서버를 세운 사람이라면 게임 이름만 다를 뿐 거의 같은 지점에서 걸려 넘어진다.

전용 서버(dedicated server)는 게임 회사가 운영하는 공식 서버 대신, 컴퓨터 한 대를 통째로 빌려 그 위에서 게임 서버 프로그램을 직접 돌리는 방식이다. 공식 서버 인원 제한이 걸리거나 친구들끼리만 따로 모이고 싶을 때 흔히 쓴다. 팰월드도 정식으로 데디케이티드 서버 실행 파일을 배포해서 이 방식 자체는 가능했다. 문제는 그다음부터, 그러니까 그 컴퓨터를 어디서 어떻게 계속 살려 두느냐부터 시작됐다.

왜 GCP에 직접 올렸고, 뭘 골랐나

시작하기 전에 확인부터 했다. 팰월드 데디케이티드 서버는 스팀 앱 ID 2394010으로 배포된다. 윈도우와 리눅스 둘 다 네이티브로 돌아가고, 차이는 UE4SS라는 모드 도구(윈도우 전용) 사용 여부뿐이었다. 리눅스로 가는 데 걸림돌이 없다는 뜻이라 비용이 저렴한 우분투 쪽으로 마음을 굳혔다.

플랫폼별 접속 방식은 미리 구분해야 했다. 팰월드 공식 설정 문서에 따르면 기본값은 CrossplayPlatforms=(Steam,Xbox,PS5,Mac)이라 SteamCMD로 세운 서버도 스팀, 엑스박스·마이크로소프트 스토어 PC 버전, PS5, Mac의 크로스플레이를 허용한다. 다만 엑스박스와 PS5 사용자는 IP 주소를 직접 입력해 들어갈 수 없으므로 서버를 커뮤니티 서버로 등록하고 게임 안의 서버 목록에서 찾아야 한다. 이 서버에서는 친구들이 모두 스팀판을 써서 스팀 접속만 확인했지만, 그것이 스팀 전용이라는 뜻은 아니다. 정말 스팀 사용자만 받으려면 설정을 CrossplayPlatforms=(Steam)으로 명시해서 범위를 좁혀야 한다.

최종 선택은 이랬다. 운영체제는 우분투 22.04, 인스턴스는 n2d-standard-4(4vCPU·16GB), 디스크는 50GB 짜리 균형형 영구 디스크(pd-balanced), 서버가 실제로 돌아가는 지역(리전, region)은 asia-northeast3-a(서울)로 잡았다. 다들 한국에서 접속하니 지연을 줄이려면 당연한 결정이었다.

pd-balanced는 이름 그대로 성능과 비용 사이에서 절충한 디스크다. 로컬 SSD처럼 인스턴스 자체에 물리적으로 붙어 있어서 꺼지면 데이터도 사라지는 디스크와 달리, 인스턴스를 정지했다 다시 켜도 내용이 그대로 남는다. 게임 서버처럼 매일 껐다 켜는 운영 방식엔 이쪽이 맞았다.

방화벽 규칙도 그날 같이 정리했다. UDP 8211번과 27015번, TCP 27015번과 25575번을 열어야 게임 접속과 서버 목록 조회, 원격 관리까지 막히지 않는다. 포트를 하나라도 빠뜨리면 접속 화면에서 서버가 아예 안 보이거나, 보이는데 들어가지지 않는 애매한 상태로 남는다.

여기서 TCP 25575번은 플레이어 접속 포트가 아니라 RCON 관리 포트라서 인터넷 전체(0.0.0.0/0)에 열면 안 된다. GCP 방화벽의 소스 IPv4 범위를 관리자 공인 IP 한 개(/32)나 VPN 대역으로 제한하고, 쓰지 않을 때는 규칙 자체를 닫는 편이 안전하다. 팰월드 공식 문서도 RCON을 deprecated로 표시하고 REST API 사용을 권고하며, 두 관리 API 모두 인터넷 직접 노출용으로 설계되지 않았다고 경고한다. 대안인 REST API를 쓸 때도 예시 포트인 TCP 8212번을 필요한 소스 IP에만 허용해야 한다. 관리 인터페이스가 탈취되고 호스트까지 침해되면 무단 조작이나 크립토마이닝 같은 자원 남용이 예상하지 못한 GCP 청구로 이어질 수 있으므로, 관리 포트는 게임 포트와 분리해 다뤘다.

서버가 실제로 도는지는 몇 가지로 확인했다. 게임 서버 프로세스가 UDP 8211번으로 정상 수신 중인지, 서버 이름과 비밀번호 같은 설정값이 실제로 반영됐는지, 명령 하나로 palworld.service가 enabled 상태인지. 이 세 가지를 다 확인하고 나서야 됐다고 말할 수 있었다. 화면에 에러가 안 뜬다고 됐다는 뜻은 아니었다.

처음엔 4vCPU·16GB로 충분해 보였다. 실제로는 그 사양이 끝까지 유지되지 않았다. 이 이야기는 비용을 다루는 절에서 다시 나온다.

게임 전용 서버 랙 앞에서 노트북으로 GCP 콘솔을 확인하며 인스턴스 사양과 방화벽 설정을 조정하는 엔지니어의 모습을 표현한 일러스트
노트북 화면의 콘솔로 인스턴스 사양과 방화벽 규칙을 확인하는 모습으로, 서버를 세운 뒤에도 계속 손봐야 했던 운영 작업을 나타냈다.

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로 설치하는 다른 게임 서버에서도 똑같이 재발할 수 있다. 자동화 진입점을 하나로 좁히는 규칙만 지키면 대부분 피해 간다.

GCP 인스턴스 생성부터 SteamCMD 설치, 게임 서버 설치, systemd 서비스 등록, 정기 재시작 타이머까지 이어지는 게임 전용 서버 구축 흐름과 SteamCMD 동시 실행 충돌 해결 방식을 정리한 다이어그램
OS·리전 설정부터 SteamCMD 설치, ExecStartPre 단일 진입점, 정기 재시작까지 5단계를 정리하고, 2·4단계 동시 실행이 0x602 충돌을 냈던 지점을 표시했다.

메모리 누수는 고치지 못해서 재시작으로 관리한다

왜 버그를 고치지 않고 재시작으로 넘어갔을까. 팰월드 서버에는 알려진 메모리 누수가 있어서다. 12시간에서 24시간 연속으로 돌리면 메모리 사용량이 4에서 6기가바이트씩 늘어난다. 코드를 손봐서 없앨 수 있는 문제가 아니라 게임 서버 자체의 한계였다.

메모리 누수는 물이 새는 양동이와 비슷하다. 구멍을 막을 수 없다면 넘치기 전에 물을 비우는 수밖에 없다. 재시작이 그 비우는 동작이다. 근본 원인을 없애는 대신 넘치는 시점을 계속 뒤로 미루는 셈이었다.

그래서 택한 방법은 정기 재시작이다. systemd에 재시작 타이머(palworld-restart.timer)를 걸어 하루 두 번, 새벽 4시와 오후 4시에 서비스를 자동으로 껐다 켰다. 상시로 켜 두는 인스턴스라 이 타이머가 매일 빠짐없이 발동했다.

재시작 횟수를 하루 몇 번으로 잡을지는 누수 속도와 실제 접속 패턴을 같이 봤다. 12시간에서 24시간 사이에 문제가 생긴다면 하루 두 번이면 어느 쪽이든 누적량이 절반 수준에서 끊긴다. 접속자가 몰리는 저녁 시간대를 피해 새벽과 오후로 시각을 잡은 것도 그래서였다.

다른 인스턴스는 가동 시간 자체가 달라서 타이머 기준도 맞춰야 했다. 주말에만 24시간 이어서 돌아가는 인스턴스는 시스템 시각이 UTC라서 타이머를 07시·19시 UTC(한국 시각으로는 16시·04시)로 잡았다. 평일에는 스케줄에 따라 인스턴스 자체가 꺼져 있으니 타이머가 발동할 일이 없고, 주말 연속 가동 구간에서만 실제로 작동한다.

재시작은 누수를 없애지 않는다. 다만 서비스가 죽기 전에 끊어서 안정적으로 보이게 만드는 우회로다. 세이브 데이터는 부팅 디스크에 그대로 남아 있어서 재시작 사이에도 진행도가 사라지지 않는다.

12시간에서 24시간마다 메모리가 4에서 6기가바이트씩 늘다가 정기 재시작 시점마다 초기화되는 톱니 모양 메모리 사용량 곡선을 나타낸 다이어그램
12~24시간마다 메모리가 4~6GB씩 쌓이다가 재시작 타이머가 발동할 때마다 기준선으로 되돌아가는 톱니 모양 곡선을 그렸다.

재시작 시각을 스크립트에 못 박아 자동화해 두면 사람이 매번 서버 상태를 확인하다 잊어버리는 일이 없다. 정해진 시각에 정해진 동작이 일어나게 만드는 원리는 다른 자동화에도 그대로 옮겨 담을 수 있다. 이 블로그에는 시간 조건으로 상태를 바꾸는 자동 발행 사례도 정리해 뒀다. 정기 재시작이든 정기 발행이든, 사람이 지켜보지 않아도 같은 시각에 같은 동작이 반복되게 만든다는 점은 같다.

인스턴스를 올리면 비용도 따라 움직인다

처음 잡은 n2d-standard-4로 시작했지만 그대로 가지 않았다. 한 차례 e2-standard-4로 바꿨다가, 나중엔 e2-standard-8(8vCPU·32GB)까지 다시 올렸다. 방법은 매번 같았다. 인스턴스를 정지하고, 머신 타입을 바꾸고, 다시 시작한다. 재구축 없이 사양만 바꿔 켜는 절차였다.

절차 자체는 생각보다 단순했다. 콘솔에서 인스턴스를 정지하고, 사양을 골라 저장하고, 다시 시작한다. 디스크와 고정 IP는 그대로 붙어 있어서 따로 재설정할 게 없었다. 다만 정지해 둔 동안엔 당연히 접속이 끊기니, 다들 안 들어와 있는 시간대를 골라야 했다.

더 저렴한 계열로 갈아탈 수 있는지도 확인해 봤다. c2d-standard-8로 전환을 시도했더니 해당 존의 하드웨어 수용량이 부족하다는 응답(ZONE_RESOURCE_POOL_EXHAUSTED)이 돌아왔다. 구글 클라우드 공식 설명대로 이 오류는 프로젝트 할당량(quota) 부족과 별개라서 할당량 상향을 신청해도 해결되지 않는다. 다른 존이나 머신 구성을 선택하거나 나중에 다시 시도해야 한다. 클라우드라고 원하는 사양을 언제나 즉시 받을 수 있는 건 아니었다. 정책도 하나 정했다. 나중에 수용량이 생기더라도 자동으로 갈아타지 않고, 먼저 확인받은 뒤에만 전환한다.

당시의 원본 청구 기록은 남아 있지 않아 과거 실측액을 복원할 수 없었다. 그래서 아래 표는 청구 결과가 아니라, 2026년 8월에 구글 클라우드의 범용 VM 가격표에서 서울 리전을 선택해 확인한 e2-standard-8 주문형 단가 약 $0.344/시간에 가동시간을 곱한 30일 비교 계산이다.

2026년 8월 기준 서울 리전 e2-standard-8 주문형 단가로 계산한 30일 예상액
가동 패턴 머신 타입 30일 기준 예상 컴퓨트 비용
상시(하루 24시간·총 720시간) e2-standard-8 약 $247.68
하루 19시간·총 570시간 e2-standard-8 약 $196.08

계산식은 $0.344 × 가동시간이다. 따라서 상시 가동 720시간은 $0.344 × 720 = $247.68, 하루 19시간씩 30일인 570시간은 $0.344 × 570 = $196.08이다. 하루 24시간과 19시간의 가동시간 비율은 약 1.26배다. 가동시간이 720시간에서 570시간으로 약 21% 줄면 비용도 같은 비율로 줄어 금액 차이는 약 $51.60이다. E2 계열에는 지속사용할인(SUD)이 없으므로 이 계산에 SUD를 더하지 않았고, 디스크·고정 IP·네트워크·세금·약정할인은 포함하지 않았다.

같은 사양이라도 인스턴스 스케줄로 필요 없는 다섯 시간을 매일 꺼 두면 30일 계산에서 약 21%를 줄일 수 있다. 다만 스케줄로 끄고 켜는 것과 완전히 삭제하는 건 다른 얘기다. 스케줄은 자원을 정지시킬 뿐이라, 과금을 아예 끝내려면 인스턴스와 고정 IP와 디스크를 사람이 직접 지워야 한다.

이 부분을 헷갈리면 두 주 쓰고 서버를 접었다고 생각했는데 다음 달 청구서에 고정 IP 요금만 계속 찍혀 있는 상황이 온다. 인스턴스가 꺼져 있어도 예약해 둔 고정 IP는 미사용 상태로도 소액이 계속 붙는다. 스케줄이 알아서 다 처리해 줄 거라고 믿고 잊어버리면 이 부분에서 새는 돈을 놓친다.

비용을 아끼는 가장 쉬운 방법은 결국 사양이 아니라 시간이었다. 8vCPU짜리 인스턴스를 하루 종일 켜 두는 것과, 접속자가 실제로 모이는 시간에만 켜는 것 사이의 차이가 사양 한 단계를 낮추는 것보다 훨씬 컸다. 사양을 고민하기 전에 언제 꺼도 되는지부터 정하는 편이 순서상 맞았다.

결론 — 다른 게임에도 옮겨 담을 수 있는 교훈

이번 경험을 관통하는 태도는 결국 하나였다. 처음부터 다 갖춰 놓으려 하지 않는 것. 사양은 넉넉하게 잡아두지 않고 필요할 때 올렸고, 메모리 누수처럼 코드로 못 고치는 문제는 정기 재시작으로 우회했고, 자동화 진입점은 하나로 좁혀 동시 실행 충돌을 막았고, 비용은 사양을 낮추는 대신 켜 두는 시간을 관리해서 줄였다.

남은 숙제도 있다. 재시작 타이머가 UTC 기준이냐 한국 시각 기준이냐로 두 번 헷갈렸다. 다음번엔 인스턴스를 새로 세울 때 시간대 확인을 자동화 스크립트 맨 앞줄에 체크 항목으로 박아 둘 생각이다. 사람이 매번 기억하는 것보다 스크립트가 한 번 물어보는 쪽이 안전하다.

팰월드가 아니어도 이 순서는 그대로 옮겨 담을 수 있다. 스팀CMD로 세우는 다른 게임 서버도 자동 업데이트, 서비스 등록, 정기 재시작이라는 뼈대는 크게 다르지 않다. 다음에 다른 게임으로 서버를 세울 일이 생기면, 이번에 걸린 네 가지부터 먼저 확인할 생각이다.

자주 묻는 질문

SteamCMD 0x602 오류의 실제 원인은 무엇이었나요?

startup-scriptExecStartPre가 같은 설치 폴더에서 SteamCMD를 동시에 실행해 파일 쓰기와 검증이 충돌한 것이 원인이었다. 실행 진입점을 ExecStartPre 하나로 좁혀 해결했다.

정기 재시작이 메모리 누수를 고치는 건가요?

아니다. 글에서 관찰한 12~24시간 연속 가동 중 4~6GB 증가를 없애는 수리가 아니라, 하루 두 번 서비스를 재시작해 누적을 끊는 우회책이다. 세이브 데이터는 부팅 디스크에 남는다.

인스턴스 스케줄로 서버를 끄면 과금도 완전히 끝나나요?

아니다. 스케줄은 인스턴스를 정지할 뿐이며, 과금을 완전히 끝내려면 인스턴스와 고정 IP, 디스크를 직접 삭제해야 한다. 정지 중인 고정 IP에도 소액 비용이 붙을 수 있다.

SteamCMD 서버에 Xbox나 PC 게임패스 사용자가 접속할 수 있나요?

가능하다. 기본 설정은 CrossplayPlatforms=(Steam,Xbox,PS5,Mac)이라 엑스박스와 마이크로소프트 스토어 PC 버전도 허용한다. 다만 엑스박스와 PS5 사용자는 IP로 직접 접속할 수 없으므로 커뮤니티 서버 목록을 이용해야 한다. CrossplayPlatforms=(Steam)으로 직접 좁힌 서버라면 다른 플랫폼은 접속할 수 없다.