
서버는 개발 환경(Visual Studio)에서 잘 돌아가는 것과 실제 운영 환경(Production)에서 잘 돌아가는 것은 전혀 다른 이야기입니다. 서버를 클라우드에 배포하고, 문제가 생겼을 때 원격으로 진단할 수 있어야 진정한 '서비스'가 됩니다.
이번 주에는 서버를 '배포 가능한 상태'로 만들고 '관측 가능하게' 만드는 작업을 진행합니다.
안녕하세요! 스튜디오 비를긋다입니다. 이번 챌린지의 주제는 'Docker 컨테이너화 및 구조적 로깅(Structured Logging) 구축' 입니다. "내 컴퓨터에서는 되는데?"라는 말을 없애기 위해 Docker를 사용하고, 텍스트 파일 뒤지기를 멈추기 위해 현대적인 로깅 시스템을 도입해 보겠습니다. 🐳
.NET 8 서버를 프로덕션 수준으로 배포하고 싶으신가요? Dockerfile 멀티 스테이지 빌드부터 Docker Compose 오케스트레이션, Serilog를 활용한 JSON 구조적 로깅 구축까지. 실무에 바로 적용 가능한 전체 소스 코드를 확인하세요.
상황 시나리오:
'아르카디아의 그림자'의 출시가 임박했습니다. 운영팀에서는 "서버를 어떻게 배포하나요? exe 파일을 복사하면 되나요?"라고 묻고 있습니다. 또한, "서버가 다운되면 로그는 어디서 보나요? 텍스트 파일은 검색하기 너무 힘들어요."라는 요청도 들어왔습니다.
당신의 임무는 GameServer와 ChatServer를 Docker 이미지로 만들어 어디서든 동일하게 실행되도록 하고, Serilog를 도입하여 로그를 JSON 형식으로 남겨 기계가 분석할 수 있도록(Machine-readable) 만드는 것입니다.
핵심 요구사항:
Console.WriteLine을 모두 제거하고, Serilog 라이브러리를 도입합니다. 로그는 단순 텍스트가 아닌 JSON 형식으로 출력되어야 합니다.GameServer와 ChatServer 프로젝트를 빌드하고 실행할 수 있는 최적화된 Dockerfile을 각각 작성합니다. (.NET 8 SDK 및 Runtime 이미지 활용)docker-compose.yml 파일을 작성하여, 명령 한 번으로 게임 서버, 채팅 서버, (그리고 지난주에 만든) Redis 서비스 레지스트리가 동시에 실행되고 서로 통신할 수 있도록 네트워크를 구성합니다.appsettings.json 또는 Docker 환경 변수로 주입받도록 수정합니다.사전 준비:
Serilog, Serilog.AspNetCore, Serilog.Sinks.Console, Serilog.Formatting.Compactusing Serilog;
using Serilog.Formatting.Compact;
// 1. Serilog 초기화 (JSON 포맷터 사용)
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(new RenderedCompactJsonFormatter()) // 사람이 읽기 좋은 Compact JSON 형식
.CreateLogger();
try
{
Log.Information("Starting GameServer...");
var builder = WebApplication.CreateBuilder(args);
// 2. .NET 호스트에 Serilog 주입
builder.Host.UseSerilog();
// ... 기존 서비스 등록 코드 ...
var app = builder.Build();
// 3. 기존 Console.WriteLine 대체 예시
// 기존: Console.WriteLine($"Player {playerId} logged in.");
// 변경: 구조적 로깅 (PlayerId가 검색 가능한 필드로 저장됨)
Log.Information("Player {PlayerId} logged in from {IpAddress}", playerId, ipAddress);
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "GameServer terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
멀티 스테이지 빌드(Multi-stage build)를 사용하여 이미지 크기를 최소화합니다.
# 1. 빌드 스테이지 (SDK 포함)
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# 프로젝트 파일 복사 및 복원 (캐시 효율성을 위해)
COPY ["GameServer/GameServer.csproj", "GameServer/"]
COPY ["Protos/chat.proto", "Protos/"]
RUN dotnet restore "GameServer/GameServer.csproj"
# 전체 소스 복사 및 빌드
COPY . .
WORKDIR "/src/GameServer"
RUN dotnet publish "GameServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
# 2. 실행 스테이지 (Runtime만 포함 - 가벼움)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "GameServer.dll"]
(ChatServer의 Dockerfile도 위와 유사하게 작성합니다.)
version: '3.8'
services:
# Redis (서비스 레지스트리 용)
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- game-network
# 채팅 서버
chat-server:
build:
context: .
dockerfile: ChatServer/Dockerfile
environment:
- ASPNETCORE_URLS=http://+:5000
- RedisAddress=redis:6379
ports:
- "5000:5000"
networks:
- game-network
depends_on:
- redis
# 게임 서버
game-server:
build:
context: .
dockerfile: GameServer/Dockerfile
environment:
- ASPNETCORE_URLS=http://+:7777
# Docker 내부 DNS를 사용하여 'chat-server' 호스트명으로 접근
- ChatServerUrl=http://chat-server:5000
ports:
- "7777:7777"
networks:
- game-network
depends_on:
- chat-server
networks:
game-network:
driver: bridge
docker-compose up 명령어 하나만 입력하면 Redis, DB, 채팅 서버, 게임 서버가 모두 실행되는 완벽한 로컬 개발 환경이 구축됩니다.PlayerId == 12345 같은 쿼리로 특정 유저의 로그만 필터링하는 것이 가능해집니다.docker-compose up 실행 시 수동 개입 없이 모든 서버가 정상적으로 뜨고 서로 연결되는가?PlayerId 같은 문맥 정보(Context)가 JSON 필드로 잘 포함되어 있는가?JSON 로그를 눈으로 읽기는 힘듭니다. 로그를 수집하고 시각화해 주는 도구를 컨테이너로 띄워 연동해 보세요.
과제: Seq를 이용한 중앙 집중식 로그 모니터링 대시보드 구축 📊
요구사항:
docker-compose.yml에 Seq 서비스를 추가합니다. (이미지: datalust/seq)Serilog.Sinks.Seq 패키지를 추가합니다.Program.cs의 로거 설정에 .WriteTo.Seq("http://seq:5341")를 추가합니다. (Docker 내부 주소 사용)http://localhost:8081)에 접속하여 게임 서버와 채팅 서버의 로그가 한곳에 모이는 것을 확인합니다.LogContext를 사용하여, 특정 요청 처리 구간(Scope) 내에서 발생하는 모든 로그에 자동으로 CorrelationId(요청 추적 ID)를 붙여보세요.// Program.cs 예시
var seqUrl = Environment.GetEnvironmentVariable("SeqUrl") ?? "http://localhost:5341";
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console() // 콘솔에도 남기고
.WriteTo.Seq(seqUrl) // Seq로도 전송
.CreateLogger();
version: '3.8'
services:
# 1. Redis (서비스 레지스트리 용)
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- game-network
# 2. Seq (중앙 집중식 로그 서버) - [새로 추가됨]
seq:
image: datalust/seq
environment:
- ACCEPT_EULA=Y # 필수: 라이선스 동의
ports:
- "5341:5341" # 로그 수집 포트 (Ingestion)
- "8081:80" # 대시보드 UI 포트 (브라우저 접속용)
volumes:
- seq-data:/data # 로그 데이터를 영구 저장하기 위한 볼륨
networks:
- game-network
# 3. 채팅 서버
chat-server:
build:
context: .
dockerfile: ChatServer/Dockerfile
environment:
- ASPNETCORE_URLS=http://+:5000
- RedisAddress=redis:6379
# Seq 서버 주소 (Docker 네트워크 내부 DNS 이름 'seq' 사용)
- SeqUrl=http://seq:5341
ports:
- "5000:5000"
networks:
- game-network
depends_on:
- redis
- seq # Seq가 먼저 켜져야 함
# 4. 게임 서버
game-server:
build:
context: .
dockerfile: GameServer/Dockerfile
environment:
- ASPNETCORE_URLS=http://+:7777
- ChatServerUrl=http://chat-server:5000
# Seq 서버 주소 주입
- SeqUrl=http://seq:5341
ports:
- "7777:7777"
networks:
- game-network
depends_on:
- chat-server
- seq
networks:
game-network:
driver: bridge
volumes:
seq-data: # Seq 데이터 저장을 위한 로컬 볼륨 정의
이제 여러분의 서버는 클라우드 환경 어디에 던져놔도 생존할 수 있는 현대적인 애플리케이션의 모습을 갖췄습니다. Docker의 바다로 항해를 시작해 보세요! 🚢
Getting Started
Simple .NET logging with fully-structured events. Contribute to serilog/serilog development by creating an account on GitHub.
github.com
Home
Docker Documentation is the official Docker library of resources, manuals, and guides to help you containerize applications.
docs.docker.com
GitHub - datalust/serilog-sinks-seq: A Serilog sink that writes events to the Seq structured log server
A Serilog sink that writes events to the Seq structured log server - datalust/serilog-sinks-seq
github.com
| 8.주간 C# 서버 챌린지: 유닛 테스트 및 통합 테스트 (1) | 2025.11.01 |
|---|---|
| 7.주간 C# 서버 챌린지: 분산 마이크로서비스 (0) | 2025.10.22 |
| 6.주간 C# 서버 챌린지: MemoryPack으로 패킷 처리 시스템 구축하기 (0) | 2025.10.11 |
| 5.주간 C# 서버 챌린지: MMORPG 서버 개발의 핵심, C# 비동기 소켓 프로그래밍 (0) | 2025.10.02 |
| 4.주간 C# 서버 챌린지: 비동기 I/O와 리포지토리 패턴으로 플레이어 데이터 저장하기 (0) | 2025.09.24 |