UE/멀티플레이

UE C++ 언리얼 패키징 관련 (+AWS)

김인철_ 2026. 8. 12. 22:03

 

언리얼 전용 서버 배포: 소스 빌드, Client/Server 패키징, AWS EC2 운영

에디터에서 멀티플레이가 동작하는 것과 인터넷에 전용 서버를 배포하는 것은 서로 다른 단계다. 로컬 빌드는 개발 코드를 실행 가능한 바이너리로 만들지만, 다른 컴퓨터에서 게임을 실행하려면 플랫폼에 맞게 변환한 에셋과 설정, 런타임 의존성까지 함께 전달해야 한다.

언리얼 전용 서버 배포는 다음 파이프라인으로 이해할 수 있다.

엔진 소스 준비
  -> 프로젝트를 소스 빌드 엔진에 연결
  -> Client / Server Target 정의
  -> 코드 Build
  -> 에셋 Cook
  -> 결과물 Stage / Package
  -> 로컬 전용 서버 테스트
  -> 서버 패키지를 EC2로 전송
  -> AWS Security Group과 Windows Firewall 개방
  -> 공인 IP와 UDP 포트로 클라이언트 접속
  -> 로그와 비용을 모니터링하고 자원 정리

핵심은 클라이언트 실행 파일과 서버 실행 파일이 같은 프로젝트에서 나오더라도 서로 다른 Target이며, 실행 파일만 복사해서는 패키지가 완성되지 않는다는 점이다.

Build, Cook, Stage, Package는 서로 다른 작업이다

패키징 로그에는 여러 단계가 연속으로 나타난다. 각 단계가 만드는 결과를 구분하면 오류가 코드 문제인지, 에셋 문제인지, 배포 경로 문제인지 빠르게 판단할 수 있다.

단계 처리 대상 결과
Build C++ 코드, 모듈, 플러그인 선택한 Target과 Configuration에 맞는 실행 파일과 바이너리를 만든다.
Cook 맵, Blueprint, 텍스처, 머티리얼, 사운드 에디터용 에셋을 WindowsClient 또는 WindowsServer 같은 런타임 형식으로 변환한다.
Stage 빌드된 코드와 쿠킹된 콘텐츠 실행에 필요한 파일을 독립된 Staging 디렉터리에 모은다.
Package Staging 결과 전달 가능한 실행 파일과 콘텐츠 컨테이너 묶음으로 만든다.
Deploy 완성된 패키지 EC2 같은 실행 대상에 업로드한다.
Run 배포된 패키지 명령줄 인자를 적용해 서버나 클라이언트를 실행한다.

프로젝트의 Binaries/Win64에 생성된 실행 파일은 Build 결과다. 이 파일만 다른 PC에 복사하면 쿠킹된 맵과 에셋이 없어서 정상적으로 실행되지 않는다. 배포 단위는 특정 .exe 하나가 아니라 Staging 또는 Packaging 결과 폴더 전체다.

전용 서버에 소스 빌드 엔진이 필요한 이유

UE 5.5의 Epic Games Launcher 배포판으로 일반 Windows 게임을 패키징할 수는 있지만, 공식 전용 서버 구성 절차는 소스 빌드 엔진과 C++ 프로젝트를 전제로 한다. Launcher 엔진에서 Server Target을 패키징하면 다음과 같은 오류를 만날 수 있다.

Server targets are not currently supported
from this engine distribution.

소스 빌드는 프로젝트 코드를 소스 형태로 만든다는 뜻이 아니다. GitHub에서 언리얼 엔진 소스 전체를 받아 직접 컴파일한 엔진 설치본을 사용하는 것이다. 이렇게 만든 엔진은 TargetType.Server를 포함한 필요한 타깃을 직접 빌드할 수 있다.

엔진 배포 방식 장점 전용 서버 관점
Epic Games Launcher 바이너리 엔진 설치와 업데이트가 빠르고 일반 게임 개발에 편리하다. UE 5.5 공식 전용 서버 빌드 절차에는 적합하지 않다.
GitHub 소스 빌드 엔진 엔진 코드 디버깅, 수정, 다양한 Target 빌드가 가능하다. TargetType.Server 기반 전용 서버 빌드에 사용한다.

소스 체크아웃, 중간 산출물, 바이너리, Derived Data Cache가 큰 공간을 사용하지만 모든 환경에 고정된 500GB가 필수인 것은 아니다. 선택한 브랜치, 플랫폼 의존성, 디버그 심볼, 캐시 상태에 따라 사용량이 달라진다. 시작 전에 충분한 여유 공간을 확보하고, 빌드 중 남은 용량을 모니터링하는 것이 정확하다.

언리얼 엔진 소스를 준비하는 흐름

Epic Games GitHub 저장소는 계정 연결이 필요하다.

  1. Epic Games 계정과 GitHub 계정을 만든다.
  2. Epic Games 계정의 Connections에서 GitHub 계정을 연결한다.
  3. GitHub로 도착한 EpicGames 조직 초대를 기한 안에 수락한다.
  4. 프로젝트와 같은 버전의 안정된 Release 브랜치 또는 태그를 선택한다.
  5. 저장소를 Clone하거나 ZIP으로 내려받는다.
  6. Setup.bat을 실행해 엔진 바이너리 의존성과 필수 구성요소를 받는다.
  7. GenerateProjectFiles.bat으로 UE5.sln을 생성한다.
  8. Visual Studio에서 Development Editor, Win64로 UE5 Target을 빌드한다.
Setup.bat
  -> 엔진이 사용하는 바이너리 의존성과 전제 조건 준비

GenerateProjectFiles.bat
  -> Target.cs와 Module 정보를 분석해 IDE 프로젝트 생성

UE5.sln Build
  -> 실제 엔진 및 에디터 바이너리 컴파일

Setup.batGenerateProjectFiles.bat은 공식 절차상 일반 실행으로 충분하다. 폴더 권한 때문에 실제 오류가 발생한 경우가 아니라면 모든 빌드 도구를 관리자 권한으로 실행할 필요는 없다. 소스 경로는 C:\UE55Src처럼 짧고 공백이나 특수문자가 적은 위치가 긴 경로 문제를 줄이는 데 유리하다.

ZIP을 사용하면 Windows가 인터넷에서 받은 파일을 차단할 수 있다. 압축 해제 전에 ZIP 파일 속성에서 차단 해제를 확인하거나 Git Clone을 사용한다. 개발 도중 엔진 브랜치를 갱신했다면 Setup.bat과 프로젝트 파일 생성을 다시 수행해야 할 수 있다.

프로젝트를 소스 빌드 엔진에 연결한다

소스 엔진을 컴파일해도 기존 .uproject가 자동으로 새 엔진을 사용하지는 않는다. 프로젝트의 Engine Association을 소스 빌드 엔진으로 변경하고 프로젝트 파일을 다시 생성해야 한다.

NumberBaseball.uproject
  -> Switch Unreal Engine Version
  -> 소스 빌드 엔진 선택
  -> Generate Visual Studio project files
  -> Development Editor / Win64 빌드

엔진 버전이 같더라도 Launcher 빌드와 소스 빌드는 서로 다른 Engine Association이다. 팀 프로젝트라면 어떤 엔진 커밋과 Build ID를 사용했는지 문서화해야 각 개발자와 빌드 머신의 결과가 재현된다.

Target은 어떤 프로그램을 만들지 정의한다

*.Target.cs는 Unreal Build Tool이 어떤 형태의 실행 파일을 만들지 판단하는 C# 규칙이다. Target과 Module을 혼동하면 안 된다.

파일 책임 NumberBaseball 예시
*.Target.cs Game, Client, Server, Editor 중 어떤 실행 프로그램을 빌드할지 정의한다. NumberBaseballServer.Target.cs
*.Build.cs 프로젝트 Module이 의존하는 엔진 Module과 컴파일 규칙을 정의한다. NumberBaseball.Build.cs

Target 종류는 포함되는 엔진 코드에도 영향을 준다.

TargetType 의미 사용처
Editor 에디터 기능을 포함한 개발 도구 에셋 편집과 PIE 테스트
Game 쿠킹된 콘텐츠로 실행하는 일반 독립 게임 싱글플레이 또는 리슨 서버를 포함할 수 있는 게임 빌드
Client 서버 전용 코드를 제외한 네트워크 클라이언트 플레이어에게 배포하는 전용 클라이언트
Server 클라이언트와 렌더링 관련 코드를 제외한 전용 서버 EC2에서 헤드리스로 실행하는 권위 서버

Server Target 작성

Editor.Target.cs를 복사하고 문자열을 일괄 치환하는 방식은 불필요한 Editor 규칙을 가져올 위험이 있다. 기본 Game Target의 버전 설정을 기준으로 새 파일을 명시적으로 작성하는 편이 안전하다.

// Source/NumberBaseballServer.Target.cs

using UnrealBuildTool;
using System.Collections.Generic;

public class NumberBaseballServerTarget : TargetRules
{
    public NumberBaseballServerTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Server;
        DefaultBuildSettings = BuildSettingsVersion.V5;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5;
        ExtraModuleNames.Add("NumberBaseball");
    }
}

파일명, Target 클래스명, 생성자명의 대응이 정확해야 한다.

파일명      NumberBaseballServer.Target.cs
클래스명    NumberBaseballServerTarget
생성자명    NumberBaseballServerTarget
모듈명      NumberBaseball

하나라도 다르면 Unreal Build Tool이 Target을 발견하지 못하거나 C# 컴파일 오류를 낸다.

Client Target 작성

전용 클라이언트가 필요하면 같은 방식으로 TargetType.Client를 정의한다.

// Source/NumberBaseballClient.Target.cs

using UnrealBuildTool;
using System.Collections.Generic;

public class NumberBaseballClientTarget : TargetRules
{
    public NumberBaseballClientTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Client;
        DefaultBuildSettings = BuildSettingsVersion.V5;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5;
        ExtraModuleNames.Add("NumberBaseball");
    }
}

Game Target만으로도 네트워크 게임 실행 파일을 만들 수 있지만, Client Target은 서버 코드를 제외한 명확한 클라이언트 산출물을 만든다. 별도의 클라이언트 패키지를 운영하지 않는 프로젝트라면 Client Target은 선택 사항이고 Server Target은 전용 서버 실행 파일에 필요하다.

Target 파일을 추가한 뒤에는 .uproject에서 Visual Studio 프로젝트 파일을 다시 생성한다. 기존에 열려 있던 IDE가 새 Target을 즉시 인식한다고 가정하면 안 된다.

현재 NumberBaseball의 Target 상태

현재 프로젝트에는 다음 두 Target만 있다.

현재 파일 TargetType 상태
NumberBaseball.Target.cs Game 일반 게임 실행 파일용 Target이 준비되어 있다.
NumberBaseballEditor.Target.cs Editor 언리얼 에디터 개발용 Target이 준비되어 있다.
NumberBaseballClient.Target.cs Client 아직 존재하지 않는다.
NumberBaseballServer.Target.cs Server 아직 존재하지 않는다.

위 코드는 프로젝트에 실제로 추가한 결과가 아니라, 현재 BuildSettingsVersion.V5Unreal5_5 설정을 유지하면서 배포 단계에서 추가할 Target 예시다.

서버 코드와 클라이언트 코드를 컴파일 단계에서 나눈다

Server Target은 렌더링을 하지 않지만 프로젝트 Module에 UI 코드가 포함되어 있으면 그 코드가 무조건 안전해지는 것은 아니다. 빌드 타깃에 따라 필요 없는 의존성이나 코드를 조건부로 제외할 수 있다.

// NumberBaseball.Build.cs

if (Target.Type != TargetType.Server)
{
    PublicDependencyModuleNames.AddRange(
        new string[]
        {
            "UMG",
            "Slate",
            "SlateCore"
        }
    );
}
#if !UE_SERVER
void ANBPlayerController::CreateLocalHUD()
{
    // 클라이언트 전용 UI 코드
}
#endif

다만 기존 코드가 UMG 타입을 공개 헤더나 공통 클래스 선언에 사용한다면 의존성만 제거했을 때 컴파일이 깨질 수 있다. 먼저 UI 경계를 정리하고 Target별 컴파일을 실제로 통과시키면서 의존성을 줄여야 한다. 최적화를 위해 무작정 Module을 제거하는 것은 배포의 첫 단계가 아니다.

맵 설정은 실행 시작점과 쿠킹 범위를 결정한다

클라이언트와 서버는 같은 프로젝트를 사용하지만 시작 맵의 목적이 다르다.

설정 사용 주체 예시
Game Default Map 일반 게임 또는 클라이언트 서버 주소를 입력하는 Title 맵
Server Default Map 전용 서버 실제 매치가 실행되는 게임 맵
List of Maps to Include Cook 과정 패키지에 반드시 포함할 Title과 게임 맵

현재 NumberBaseball에는 L_Chatting.umap 하나가 있고 GameDefaultMapEditorStartupMap이 이 맵을 가리킨다. ServerDefaultMap과 명시적인 MapsToCook 설정은 없다. 현재 구조 그대로라면 서버 실행 명령에 맵을 직접 넘기거나 Server Default Map을 추가해야 시작점이 명확해진다.

[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting
ServerDefaultMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting
EditorStartupMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting

맵을 명령줄로 직접 지정할 수도 있다.

NumberBaseballServer.exe /Game/NumberBaseball/Maps/L_Chatting -log

맵이 패키지에 쿠킹되어 있지 않으면 이름이 정확해도 열리지 않는다. Editor에서 열 수 있다는 사실은 Cook 결과에 포함되었다는 보장이 아니다. 특히 코드나 다른 에셋에서 하드 레퍼런스되지 않는 맵, 문자열로만 열리는 맵, 런타임에 동적으로 로드하는 에셋은 Packaging Settings의 목록이나 Primary Asset 규칙으로 명시해야 한다.

전용 서버에는 ?listen이 필수가 아니다

리슨 서버는 플레이어 클라이언트가 서버 역할까지 겸하므로 맵 URL에 ?listen을 붙여 서버로 열 수 있다.

NumberBaseball.exe L_Chatting?listen

반면 NumberBaseballServer.exeTargetType.Server로 빌드된 전용 서버다. 실행 자체가 서버이므로 다음처럼 맵과 포트만 지정하면 된다.

NumberBaseballServer.exe /Game/NumberBaseball/Maps/L_Chatting -log -port=7777

전용 서버 명령에 ?listen을 붙여도 일부 구성에서 동작할 수 있지만 의미상 중복이다. 리슨 서버 실행법과 전용 서버 실행법을 구분해 두면 배포 스크립트가 명확해진다.

클라이언트와 서버를 각각 Cook하고 Package한다

Project Launcher의 Custom Profile을 사용하면 Build, Cook, Package, Deploy, Run 단계를 세밀하게 설정할 수 있다. 최종 배포 패키지를 만들 때는 자동 실행까지 켜 둘 필요가 없다.

항목 클라이언트 프로필 서버 프로필
Build Target NumberBaseballClient NumberBaseballServer
Platform WindowsClient WindowsServer
Configuration 개발 테스트는 Development, 배포 후보는 Shipping 검토 초기 운영 검증은 Development, 최종 운영은 Shipping 검토
Cook 클라이언트가 사용하는 맵과 시각·음향 에셋 서버 게임 규칙에 필요한 맵과 데이터
Deploy 패키지 생성만 할 때 Do Not Deploy EC2 업로드는 별도 배포 과정으로 처리 가능
Run 자동 테스트 때만 활성화 패키징 완료 후 자동 실행이 불필요하면 비활성화

Project Launcher가 자동 실행까지 수행하도록 설정되어 있으면 패키징 도중 클라이언트나 서버가 뜬다. 실행 창을 닫아야 패키징이 완료되는 정상 규칙은 아니다. 배포 산출물만 필요하다면 Launch 단계에서 실행하지 않도록 Profile을 구성한다.

결과 폴더 이름은 엔진 버전과 Profile 설정에 따라 달라질 수 있다. Saved/StagedBuilds/WindowsClientWindowsServer는 흔한 Staging 경로이지만, Package Project에서 직접 지정한 출력 디렉터리를 사용할 수도 있다. 폴더 이름을 고정 가정하기보다 UAT 로그의 StageDirectoryArchiveDirectory를 확인하는 편이 정확하다.

Development와 Shipping의 차이

Configuration 장점 사용 시점
Development 로그와 디버깅 기능을 활용하면서 최적화된 실행을 테스트하기 쉽다. 로컬 패키징 검증과 초기 EC2 배포
Shipping 디버깅 기능을 줄인 최종 배포용 구성이다. 충분한 테스트와 운영 로깅 정책을 마련한 릴리스

처음부터 Shipping으로만 테스트하면 콘솔과 일부 로그가 제한되어 원인을 찾기 어렵다. Development Server로 네트워크, 맵, 런타임 의존성, 방화벽을 먼저 검증한 뒤 Shipping으로 전환하는 편이 효율적이다.

패키징 결과는 폴더 전체로 검증한다

패키지를 다른 위치에 복사한 뒤 개발 프로젝트 경로와 에디터가 없어도 실행되는지 확인한다. 이 테스트는 우연히 로컬 Cook 결과나 엔진 DLL을 참조하는 문제를 잡아낸다.

WindowsServer/
  NumberBaseballServer.exe 또는 실행용 루트 파일
  Engine/
  NumberBaseball/
    Binaries/
    Content/Paks 또는 IoStore 컨테이너
    Config/

서버 패키지를 배포할 때는 이 구조를 유지해야 한다. .exe만 바탕화면으로 꺼내거나 일부 DLL만 선택해 전송하지 않는다.

로컬에서 먼저 전용 서버를 검증한다

클라우드에 올리기 전에 같은 PC에서 패키지의 실행과 접속을 검증하면 AWS 문제와 게임 문제를 분리할 수 있다.

.\NumberBaseballServer.exe `
  /Game/NumberBaseball/Maps/L_Chatting `
  -log `
  -port=7777 `
  -unattended `
  -NoSound

다른 터미널에서 클라이언트를 실행한다.

.\NumberBaseballClient.exe `
  127.0.0.1:7777 `
  -windowed `
  -ResX=1280 `
  -ResY=720 `
  -log

로그에서 다음 항목을 확인한다.

  • 올바른 게임 맵이 로드되었는가?
  • IpNetDriver가 의도한 UDP 포트에서 Listen하는가?
  • 클라이언트 접속 때 PreLogin, Login, PostLogin 흐름이 완료되는가?
  • 서버에서 Widget이나 LocalPlayer를 찾는 오류가 발생하지 않는가?
  • 패키지를 다른 경로로 옮겨도 에셋 누락 없이 실행되는가?

7777은 언리얼 전용 서버의 일반적인 기본 포트지만 절대값이 아니다. 동일한 머신에서 여러 서버를 실행하거나 -port를 지정하면 다른 포트를 사용할 수 있다. 로그에 출력된 실제 포트와 클라이언트 접속 주소를 일치시켜야 한다.

AWS EC2 배포 구조

EC2 Windows 인스턴스에 전용 서버를 배포하면 네트워크 요청은 여러 계층을 통과한다.

외부 클라이언트
  -> EC2 Public IPv4 또는 DNS
  -> VPC Route / Internet Gateway
  -> Security Group의 UDP 7777 Inbound Rule
  -> Windows Defender Firewall의 UDP 7777 Inbound Rule
  -> NumberBaseballServer의 IpNetDriver

Security Group만 열거나 Windows Firewall만 열어서는 접속되지 않는다. 서버 프로세스가 Listen하는 포트와 프로토콜까지 네 요소가 모두 일치해야 한다.

Region과 Instance Type을 고르는 기준

Region은 관리자가 편한 위치보다 실제 플레이어와 가까운 위치를 우선한다. 한국 사용자 대상 테스트라면 서울 Region이 합리적이지만, 다른 지역 사용자가 주 대상이면 지연 시간을 측정해 선택해야 한다.

전용 서버는 렌더링하지 않으므로 일반적으로 GPU 인스턴스가 필요하지 않다. 먼저 작은 범용 인스턴스로 기능을 검증하고, 서버 Tick 시간, CPU 사용률, 메모리, 동시 접속자 수를 측정해 확장한다.

자원 영향을 주는 요소 확인 지표
vCPU 게임 Tick, 물리, AI, RPC 처리, 복제 대상 수 프로세스 CPU, Game Thread 시간, Tick 지연
Memory 맵 크기, 로드된 에셋, 플레이어 수, 캐시 Working Set, Commit Size, 메모리 증가 추세
Network 복제 빈도, Actor 수, RPC, 동시 접속자 Network In/Out, 패킷 손실, 지연 시간
Storage 서버 패키지, 로그, 크래시 덤프 EBS 여유 공간과 로그 증가량

Security Group은 용도별 최소 포트만 연다

Windows EC2 테스트에 필요한 대표적인 Inbound Rule은 다음과 같다.

용도 프로토콜과 포트 Source 권장값
Remote Desktop 관리 TCP 3389 관리자 PC의 현재 공인 IP 한 개 또는 회사 VPN 대역
언리얼 게임 접속 UDP 7777 제한 테스트는 참여자 IP, 공개 테스트는 필요한 범위만 허용

RDP의 Source를 0.0.0.0/0으로 열면 인터넷 전체가 로그인 포트에 접근할 수 있다. AWS도 RDP와 SSH는 필요한 특정 IP만 허용할 것을 권장한다. 게임 UDP 포트는 공개 매치 특성상 넓게 열 수 있지만, 관리 포트와 같은 기준을 적용하면 안 된다.

Unreal의 기본 IpNetDriver 게임 트래픽은 UDP이므로 Custom TCP 7777만 추가해서는 접속되지 않는다. 클라이언트 접속 포트와 서버의 -port 값을 변경했다면 Security Group도 같은 UDP 포트로 바꾼다.

Windows Defender Firewall도 별도로 연다

Security Group을 통과한 패킷도 Windows Guest OS의 Firewall에서 차단될 수 있다. 관리자 PowerShell에서 서버 프로그램 또는 UDP 포트에 대한 Inbound Rule을 만든다.

New-NetFirewallRule `
  -DisplayName "NumberBaseball Server UDP 7777" `
  -Direction Inbound `
  -Action Allow `
  -Protocol UDP `
  -LocalPort 7777

프로그램 경로까지 제한하는 규칙은 같은 포트를 다른 프로세스가 사용하는 위험을 줄일 수 있다. 패키지 경로를 바꾸면 프로그램 기반 규칙도 갱신해야 한다.

방화벽 전체를 비활성화해서 접속을 해결하는 방식은 원인 진단에는 잠깐 도움이 될 수 있어도 운영 설정으로 남겨서는 안 된다. 필요한 프로토콜과 포트만 규칙으로 추가한다.

Windows 인스턴스에 패키지를 배포한다

간단한 실습에서는 RDP의 로컬 드라이브 공유나 압축 파일 복사로 서버 패키지를 전송할 수 있다. 규모가 커지면 S3, Systems Manager, CI/CD Artifact를 사용해 버전과 무결성을 관리하는 편이 낫다.

로컬 WindowsServer 패키지
  -> ZIP 압축
  -> EC2에 전송
  -> 짧고 고정된 경로에 압축 해제
  -> 런타임 설치
  -> 서버 명령 실행

예시 배포 경로는 다음처럼 단순하게 둘 수 있다.

C:\GameServer\NumberBaseball\

개발 PC에서 만든 바로 가기는 절대 경로를 포함할 수 있어 EC2에서 깨진다. 서버에서는 새 바로 가기나 실행 스크립트를 만들거나 PowerShell에서 직접 실행한다.

패키지가 Visual C++ Runtime 같은 전제 조건을 요구하면 소스 엔진의 다음 설치 프로그램을 신뢰할 수 있는 경로에서 함께 배포할 수 있다.

Engine\Extras\Redist\en-us\UEPrereqSetup_x64.exe

오류 창을 무조건 닫고 진행하기보다 누락된 런타임과 버전을 로그로 확인하고 설치해야 한다. 서버를 재배포할 때마다 설치할 필요는 없지만 새 Windows 인스턴스에는 초기 프로비저닝 단계가 필요하다.

EC2에서 서버를 실행하고 확인한다

Set-Location 'C:\GameServer\NumberBaseball'

.\NumberBaseballServer.exe `
  /Game/NumberBaseball/Maps/L_Chatting `
  -log `
  -port=7777 `
  -unattended `
  -NoSound

서버가 실제로 UDP 포트를 열었는지 확인한다.

Get-NetUDPEndpoint -LocalPort 7777

실행 프로세스를 확인한다.

Get-Process NumberBaseballServer

언리얼 로그는 일반적으로 패키지의 프로젝트 디렉터리 아래 Saved\Logs에서 확인할 수 있다. 콘솔의 마지막 한 줄만 보는 대신 맵 로드, NetDriver 생성, Bind 실패, 패키지 에셋 누락을 검색한다.

LogNet: Created socket for bind address
LogNet: GameNetDriver IpNetDriver listening on port 7777

포트가 이미 사용 중이면 서버가 7778 같은 다음 포트를 임의로 사용할 것이라고 기대하지 말고, 어떤 프로세스가 점유했는지 확인한 뒤 각 서버 인스턴스에 고유한 -port를 명시한다. Security Group과 클라이언트 주소도 같은 값으로 맞춘다.

외부 클라이언트에서 접속한다

EC2 인스턴스의 Public IPv4를 확인한 뒤 클라이언트에 주소:포트를 전달한다.

NumberBaseballClient.exe 203.0.113.10:7777 -windowed -ResX=1280 -ResY=720 -log

203.0.113.10은 문서용 예시 주소이므로 실제 실행 시 EC2의 Public IPv4 또는 DNS로 교체한다. Title UI에서 주소를 입력받는 구조라면 같은 문자열을 ClientTravel()에 전달한다.

외부 접속 실패는 다음 순서로 범위를 좁힌다.

1. 서버 프로세스가 실행 중인가?
2. 올바른 게임 맵을 열었는가?
3. Get-NetUDPEndpoint에 실제 포트가 보이는가?
4. Windows Firewall이 해당 UDP 포트를 허용하는가?
5. EC2 Security Group이 같은 UDP 포트를 허용하는가?
6. 클라이언트가 Private IPv4가 아닌 Public IPv4를 사용하는가?
7. 서버 로그에 접속 시도 또는 Handshake 오류가 남는가?
8. 클라이언트와 서버 빌드의 프로젝트 버전이 호환되는가?

로컬 127.0.0.1 접속이 실패하면 AWS 설정이 원인이 아니다. 반대로 EC2 내부에서 서버가 Listen하지만 외부에서만 실패한다면 Security Group, Windows Firewall, Public IP, VPC 경로를 우선 확인한다.

Stop, Terminate, Public IP와 비용을 구분한다

AWS 자원 정리는 단순히 원격 데스크톱 창을 닫는 것으로 끝나지 않는다.

동작 인스턴스 상태 데이터와 비용 영향
Reboot 같은 인스턴스를 재부팅 컴퓨팅 과금이 계속되고 일반적으로 Public IPv4가 유지된다.
Stop 나중에 다시 시작 가능 인스턴스 사용 요금은 멈추지만 EBS와 일부 네트워크 자원 비용은 남을 수 있다.
Start 중지된 인스턴스를 재개 일반 Public IPv4는 바뀔 수 있어 클라이언트 주소를 다시 확인해야 한다.
Terminate 인스턴스를 영구 삭제 다시 시작할 수 없으며 Root EBS는 기본 설정상 삭제되지만 별도 Volume은 남을 수 있다.

On-Demand 인스턴스는 running 시간에 컴퓨팅 요금이 발생한다. Stop 상태에서는 인스턴스 사용 요금이 중단되지만 EBS 저장 공간과 연결된 Elastic IP 등은 별도 과금될 수 있다. 현재 AWS는 Public IPv4 주소에도 비용을 부과하므로 Free Tier라는 이유만으로 비용이 항상 0원이라고 가정하면 안 된다.

Stop 후 Start하면 자동 할당 Public IPv4가 바뀌는 것이 일반적이다. 주소를 고정하려고 Elastic IP를 사용하면 관리가 편하지만 그 주소 자체의 비용과 미사용 상태를 확인해야 한다. 짧은 실습이라면 재시작 후 새 주소를 확인하는 방식이 더 단순할 수 있다.

Terminate는 복구 가능한 중지가 아니다. 로그나 패키지를 보관해야 한다면 먼저 S3나 로컬 저장소로 복사하고, 연결된 EBS Volume의 Delete on termination 설정을 확인한다. 인스턴스를 종료한 뒤에도 남은 EBS Volume, Snapshot, Elastic IP가 없는지 각 서비스 화면과 Billing에서 검토한다.

운영 환경에서는 RDP로 수동 실행하지 않는다

RDP 세션에서 콘솔 창을 직접 띄우는 방식은 첫 배포 검증에는 편하지만 세션 종료, 재부팅, 프로세스 크래시에 취약하다. 운영 단계에서는 다음 기능을 추가한다.

  • Windows Service나 작업 스케줄러로 부팅 시 서버를 시작한다.
  • 서버 실행 스크립트와 포트를 형상 관리한다.
  • 프로세스 종료를 감지해 제한적으로 재시작한다.
  • 로그를 CloudWatch Logs나 중앙 저장소로 전송한다.
  • CloudWatch Alarm과 AWS Budgets로 장애와 비용을 알린다.
  • 패키지 버전, 엔진 커밋, Git 커밋, 배포 시각을 기록한다.
  • RDP 대신 Systems Manager Session Manager 사용 가능성을 검토한다.
  • IAM 권한과 Security Group을 최소 권한으로 유지한다.

자동 재시작은 무한 크래시 루프를 만들 수 있으므로 횟수 제한과 Backoff가 필요하다. 새 버전 배포 전에는 이전 패키지를 별도 디렉터리에 보관해 빠르게 Rollback할 수 있게 한다.

배포 버전 호환성을 서버가 검사해야 한다

클라이언트와 서버가 서로 다른 패키지면 프로퍼티 레이아웃, RPC, 맵, 게임 규칙이 달라질 수 있다. 단순히 접속이 성립한다고 호환된다고 볼 수 없다.

Client Build ID
  -> URL Option 또는 초기 Handshake에 포함
  -> Server가 허용 버전과 비교
  -> 불일치 시 PreLogin에서 명시적인 오류로 거절

운영 배포에서는 최소한 다음 정보를 로그에 남긴다.

Project Version
Git Commit
Engine Version / Source Commit
Target Configuration
Map Name
Listen Port
Deployment Timestamp

이 정보가 있어야 클라이언트가 접속하지 못할 때 방화벽 문제와 버전 불일치를 구분할 수 있다.

전체 배포 체크리스트

구간 검증 항목
Engine UE 5.5에 맞는 소스 브랜치, Visual Studio 구성요소, Setup과 GenerateProjectFiles 완료
Project 소스 엔진 연결, C++ 빌드 성공, Client와 Server Target 발견
Maps Game Default Map, Server Default Map, Cook 대상 맵 설정
Package WindowsClient와 WindowsServer 결과를 별도 생성하고 폴더 전체 실행 검증
Local Test 서버 맵 로드, UDP 7777 Listen, 두 클라이언트 접속, 종료와 재접속 확인
EC2 플레이어와 가까운 Region, 적절한 Instance Type, 충분한 EBS 선택
Network Security Group UDP 7777, Windows Firewall UDP 7777, RDP TCP 3389 Source 제한
Runtime Prerequisite 설치, 명시적인 맵과 포트, Saved/Logs 확인
Client EC2 Public IPv4와 실제 서버 포트로 접속, 버전 호환성 확인
Operations 자동 시작, 모니터링, 로그 수집, Rollback, 비용 알림
Cleanup Stop과 Terminate 정책 결정, EBS·Snapshot·Elastic IP·Public IPv4 비용 확인

자주 발생하는 문제

증상 가능한 원인과 확인 지점
Server targets are not currently supported 프로젝트가 Launcher 바이너리 엔진을 사용 중인지 확인하고 소스 빌드 엔진으로 전환한다.
Development Server 구성이 보이지 않는다. Server.Target.cs의 파일명, 클래스명, 생성자명과 프로젝트 파일 재생성을 확인한다.
서버 실행 파일은 있지만 맵을 열지 못한다. 맵 경로, Server Default Map, List of Maps to Include, Cook 로그를 확인한다.
서버를 실행하면 아무 창도 보이지 않는다. 헤드리스 실행은 정상일 수 있다. -log, 프로세스, Saved/Logs와 UDP Endpoint를 확인한다.
로컬에서는 접속되지만 EC2에서는 실패한다. Security Group의 프로토콜이 UDP인지, Windows Firewall, Public IPv4, 실제 Listen 포트를 확인한다.
RDP 접속이 되지 않는다. 인스턴스 상태 검사, Public IPv4, TCP 3389 Rule, Source의 현재 관리자 공인 IP를 확인한다.
EC2 Stop 후 클라이언트가 접속하지 못한다. Start 뒤 Public IPv4가 변경되었는지 확인하고 주소 설정을 갱신한다.
서버 실행 직후 런타임 DLL 오류가 난다. 패키지 폴더 일부만 복사했는지와 UE Prerequisite 설치 여부를 확인한다.
원격 서버에서 UI 또는 LocalPlayer 오류가 난다. 서버 전용 경로에서 Widget, Viewport, Local Controller 로직이 실행되는지 확인한다.
인스턴스를 중지했는데 비용이 계속 보인다. EBS, Snapshot, Elastic IP, Public IPv4 등 인스턴스 외 자원을 확인한다.