Amazon Web Services 한국 블로그

AWS에서 최대 90% 저렴한 컴퓨팅 비용으로 대규모 게임 서버 실행하기

Fortnite, Battle Royale, Warframe 및 Apex Legends와 같은 성공한 많은 비디오 게임은 플레이어가 게임의 일부에 무료로 액세스할 수 있는 부분 유료화 모델을 사용합니다. 이러한 무료 게임은 더 이상 낮은 품질로 제공되지 않으며 유료 게임과 동일한 품질이 요구됩니다. Amazon EC2 스팟 인스턴스는 비용 제약이 많은 이러한 비즈니스 모델에 저렴하면서도 현실적인 컴퓨팅 옵션을 제공합니다. 캐주얼 멀티플레이어 게임에는 당연히 스팟 제공이 적합합니다. 스팟 인스턴스 제공은 플레이어 영향을 최소화하고 멀티플레이어 게임 서버 워크로드를 실행할 때의 비용을 최소화하는 메커니즘과 Amazon EKS 컨테이너의 오케스트레이션을 활용하므로 캐주얼 멀티플레이어 게임은 물론 하드코어 멀티플레이어 게임에도 적합합니다.

스팟 인스턴스는 AWS 클라우드에서 사용 가능한 예비 컴퓨팅 용량을 온디맨드 인스턴스에 비해 대폭 할인된 요금으로 제공됩니다. 스팟 인스턴스를 사용하면 비용을 최적화하는 동시에 동일한 예산으로 애플리케이션의 처리량을 최대 10배까지 확장할 수 있습니다. 스팟 인스턴스는 내결함성이 있는 워크로드에 가장 적합합니다. 멀티플레이어 게임 서버도 예외가 아닙니다. 게임 서버 상태는 실시간으로 플레이어 입력을 사용하여 업데이트되므로 서버 상태가 일시적입니다. 수명이 짧은 게임 서버 워크로드에 스팟 인스턴스를 활용하면 컴퓨팅 비용을 최대 90% 절감할 수 있습니다. 이 블로그에서는 중단을 처리하고 스팟 인스턴스를 효과적으로 사용하는 게임 서버 워크로드의 설계 방법을 설명합니다.

게임 서버 워크로드의 특징

간단히 말해 멀티플레이어 게임 서버에서 대부분의 시간은 현재 캐릭터의 위치와 상태(대부분 애니메이션)를 업데이트하는 데 사용됩니다. 나머지 시간은 전투 동작, 이동 및 기타 게임 관련 이벤트 결과로 이미지를 업데이트하는 데 사용됩니다. 좀 더 구체적으로 말하자면 게임 서버의 CPU는 클라이언트 위치를 수신하고, 새로운 게임 상태를 계산하고, 게임 상태를 다시 클라이언트로 멀티캐스팅하는 과정에서 수많은 네트워크 I/O 작업을 처리합니다. 이러한 이유로 게임 서버 워크로드에는 캐주얼 멀티플레이어 게임의 경우 범용 인스턴스 유형이 적합하고, 하드코어 멀티플레이어 게임의 경우 컴퓨팅 최적화 인스턴스 유형이 적합합니다.

AWS는 Amazon EC2 스팟 인스턴스를 통해 컴퓨팅 최적화(C5 및 C4) 인스턴스 유형과 범용(M5) 인스턴스 유형의 다양한 옵션을 제공합니다. 가용 영역의 각 인스턴스 유형에 대해 독립적으로 용량이 변동되므로 다양한 인스턴스 유형을 사용하는 경우 동일한 요금으로 더 많은 컴퓨팅 용량을 활용할 수 있습니다. 스팟 인스턴스 모범 사례에 대한 자세한 내용은 Amazon EC2 스팟 인스턴스 시작하기를 참조하십시오.

고객이 전용 게임 서버를 실행할 때 Amazon GameLift 솔루션을 사용할 수 있습니다. 이 솔루션은 단일 AWS 리전에 Amazon GameLift FleetIQ 플릿과 스팟 인스턴스를 배포합니다. FleetIQ는 플레이어 지연 시간, 인스턴스 요금 및 스팟 인스턴스 요금에 따라 게임 서버에 새 세션을 배치하므로 스팟 인스턴스 중단을 염려하지 않아도 됩니다. 자세한 내용은 AWS 게임 기술 블로그에서 Amazon GameLift FleetIQ 및 스팟 인스턴스로 최대 90%의 비용 절감을 참조하십시오.

다른 경우 멀티플레이어 게임 서버 배포에 컨테이너 기반 오케스트레이션(예: Kubernetes, Swarm 및 Amazon ECS) 같은 게임 서버 배포 패턴을 사용할 수 있습니다. 이러한 시스템은 여러 리전에 걸쳐 Docker 컨테이너로 배포되는 많은 수의 게임 서버를 관리합니다. 이 블로그의 나머지 부분에서는 이 컨테이너식 게임 서버 솔루션을 중점적으로 다룹니다. 컨테이너는 경량이고 빠르게 시작되며 기반 인스턴스에 대한 활용도가 높기 때문에 게임 서버 워크로드에 적합합니다.

게임에서 EC2 스팟 인스턴스를 사용해야 하는 이유

스팟 인스턴스는 중단을 잘 처리할 수 있도록 2분 알림이 기본적으로 제공되므로 수명이 짧은 게임 서버 워크로드를 실행하기에 적합합니다. 게임 서버에서는 이 2분 종료 알림을 통해 중단 시 조치를 취할 수 있습니다. 인스턴스 메타데이터Amazon CloudWatch를 통한 알림 처리를 보여주는 두 가지 예가 있습니다. 자세한 내용은 이 블로그의 뒷부분에 나오는 “중단 처리” 및 “게임 서버 이중화” 부분을 참조하십시오.

또한 스팟 인스턴스는 게임 서버 워크로드에 적합한 다양한 EC2 인스턴스 유형(예: 범용 및 컴퓨팅 최적화(C4 및 C5))을 제공합니다. 마지막으로 스팟 인스턴스는 낮은 종료율을 제공합니다. 스팟 인스턴스 어드바이저를 활용하면 기록상 중단율이 낮은 인스턴스 유형을 쉽게 결정할 수 있습니다.

중단 처리하기

스팟 인스턴스를 사용할 때는 플레이어에 영향을 미치지 않는 것이 중요합니다. 제안된 참조 아키텍처에 적용되는 플레이어 영향 방지 전략과 코드 예제를 GitHub의 Spotable Game Server에서 확인할 수 있습니다. 구체적으로 Amazon EKS의 경우 kubectl drain 명령을 통해 노드를 드레이닝해야 합니다. 이렇게 하면 노드를 예약할 수 없게 되고 노드에서 현재 실행되어 플레이어 경험에 영향을 줄 수 있는 Pod가 정상 종료 기간(terminationGracePeriodSeconds)을 통해 제거됩니다. 따라서 Pod는 종료 신호가 게임에 전송되는 동안에도 정상적으로 계속해서 실행됩니다.

노드 드레이닝(drainage)

노드 드레이닝에는 모든 스팟 인스턴스 호스트에서 DaemonSet로 실행되어 Amazon CloudWatch 또는 인스턴스 메타데이터에서 잠재적 스팟 중단을 가져오는 에이전트 Pod가 필요합니다. 여기서는 인스턴스 메타데이터 알림을 사용합니다. 다음은 노드 드레이닝을 통한 종료 이벤트의 처리 방법에 대한 설명입니다.

  1. 120초의 기본값을 사용하여 게임 서버 Pod를 시작합니다(terminationGracePeriodSeconds). GitHub의 YAML 파일 배포를 예제로 참조하십시오.
  2. 온디맨드와 스팟 인스턴스가 혼합된 인스턴스 정책을 사용하여 작업자 노드 풀을 프로비저닝합니다. 여기에는 최저 요금의 스팟 인스턴스 할당 전략이 사용됩니다. 예를 들어 GitHub의 AWS CloudFormation 템플릿을 참조하십시오.
  3. Amazon EKS 부트스트랩 도구(권장되는 AMI의 /etc/eks/bootstrap.sh)를 사용하여 각 노드에 인스턴스 수명 주기 레이블(온디맨드 또는 스팟)을 지정합니다. 예를 들면 다음과 같습니다.
    • 온디맨드: “–kubelet-extra-args –node labels=lifecycle=ondemand,title=minecraft,region=uswest2”
    • 스팟: “–kubelet-extra-args –node-labels=lifecycle=spot,title=minecraft,region=uswest2”
  4. 모든 노드에 배포된 데몬 세트가 인스턴스 메타데이터 엔드포인트에서 종료 상태를 가져옵니다. 종료 알림이 도착하면 `kubectl drain node` 명령을 실행하고 SIGTERM 신호가 게임 서버 Pod로 전송됩니다. GitHub의 배치 파일에서 이러한 명령을 확인할 수 있습니다.
  5. 게임 서버는 다음 120초 동안 계속해서 실행되므로 예정된 종료를 플레이어에게 알릴 수 있습니다.
  6. 종료가 예정된 노드는 예약 불가로 표시되므로 새 게임 서버가 예약되지 않습니다.
  7. 매칭 시스템 같은 외부 시스템으로 알림이 전송되고 사용 가능한 게임 서버의 현재 목록이 업데이트 됩니다.

Kubernetes 사양에 대한 최적화 전략

이 섹션에서는 프로비저닝된 작업자 노드의 게임 서버 배치를 최적화하기 위해 Kubernetes 사양에 권장되는 몇 가지 전략을 설명합니다.

  • 단일 스팟 인스턴스 Auto Scaling 그룹을 작업자 노드로 사용합니다. 다수의 Auto Scaling 그룹이 사용될 수 있도록 Kubernetes nodeSelector를 사용하여 스팟 인스턴스 기반 Auto Scaling 그룹의 노드에 대한 게임 서버 예약을 제어합니다.
    nodeSelector:
         lifecycle: spot
            title: 게임 제목
  • 수명 주기 레이블은 노드를 생성할 때 다음 섹션의 AWS CloudFormation 템플릿을 통해 입력됩니다.
    BootstrapArgumentsForSpotFleet:
    	Description: Sets Node Labels to set lifecycle as Ec2Spot
    	    Default: "--kubelet-extra-args --node-labels=lifecycle=spot,title=minecraft,region=uswest2"
    	
    	    Type: String
  • 경우에 따라 수신되는 플레이어 작업을 UDP로 처리하고 플레이어의 중단을 마스킹해야 할 수 있습니다. 여기서 게임 서버 할당자(Kubernetes 스케줄러)는 둘 이상의 게임 서버를 UDP 로드 밸런서 뒤의 대상 업스트림 서버로 예약합니다. 이 로드 밸런서는 수신되는 모든 패킷을 게임 서버 세트로 멀티캐스팅합니다. 스케줄러가 노드가 종료되는 게임 서버를  종료하면 원활하게 이관됩니다. 자세한 내용은 이 블로그의 뒷부분에 나오는 “게임 서버 이중화”를 참조하십시오.

참조 아키텍처

다음 아키텍처는 멀티플레이어 게임 서버의 Amazon EKS 클러스터에 포함되는 온디맨드 및 스팟 인스턴스의 혼합 인스턴스를 설명합니다. 단일 VPC 내에서 제어 플레인 노드 풀(마스터 호스트 및 스토리지 호스트)은 고가용성을 제공해야 하므로 온디맨드 인스턴스로 실행합니다. 게임 서버 호스트/노드는 스팟 인스턴스와 온디맨드 인스턴스를 혼합하여 사용합니다. 제어 플레인인 API 서버는 미리 구성된 허용 목록이 포함된 Amazon Elastic Load Balancing Application Load Balancer를 통해 액세스됩니다.

게임 서버 이중화

게임 서버는 세션 기반 워크로드입니다. 그러나 이중화 없이 단일의 전용 게임 서버 인스턴스로 실행되는 것이 일반적입니다. TCP를 전송 네트워크 계층으로 사용하는 게임 서버의 경우 AWS가 제공하는 Network Load Balancer를 사용하여 플레이어 트래픽을 여러 게임 서버 대상에 분산할 수 있습니다. 현재 UDP를 사용하는 게임 서버에는 이처럼 고가용성 게임 서버를 위한 이중화를 추가하는 로드 밸런서 솔루션이 없습니다.

이 섹션에서는 컨테이너식 Amazon EKS Pod로 배포된 게임 서버에서 UDP를 네트워크 전송 계층으로 사용하고 고가용성을 유지해야 하는 사례에 대한 솔루션을 제안합니다. 여기서는 스팟 인스턴스 때문에 UDP 로드 밸런서를 사용하지만 스팟 인스턴스를 사용하는 경우에만 선택할 수 있는 것은 아닙니다.

다음 다이어그램은 Amazon EKS 기반의 UDP 로드 밸런서 구현에 대한 아키텍처 예를 보여줍니다. 앞서 제안된 Amazon EKS 클러스터 설정과 멀티플레이어 게임 서비스를 지원하는 아키텍처의 시뮬레이션을 위한 구성 요소 세트가 필요합니다. 예를 들어 실행 중인 게임 서버, 게임 서버의 상태 및 할당 배치를 캡처하는 게임 서버 인벤토리가 여기에 포함됩니다.Amazon EKS 클러스터는 왼쪽에 있고 UDP 로드 밸런서 시스템은 오른쪽에 있습니다. 새 게임 서버는 Amazon DynamoDB 테이블로 유지되는 Amazon SQS 대기열로 보고됩니다. 플레이어 할당이 필요한 경우에는 매칭 서비스가 DynamoDB 테이블을 사용하는 게임 서버 인벤토리를 통해 API 엔드포인트에 최적의 가용 게임 서버를 쿼리합니다.

이 솔루션에는 다음과 같은 주요 구성 요소가 포함됩니다.

  • 게임 서버(GitHub의 mockup-udp-server 참조). 연결된 플레이어로부터 게임 상태의 델타를 수신한 후 의사 계산에 따라 업데이트된 상태를 플레이어에게 멀티캐스팅하는 단순한 UDP 소켓 서버입니다. 이 서버는 전용 게임 서버에서 UDP 기반 로드 밸런싱의 성공 가능성을 입증하기 위한 목적의 단일 스레드 서버입니다. 여기에 제시된 모델은 이 구현 외에도 적용이 가능합니다. 네트워크 최적화에 hostNetwork: true를 사용하는 단일 컨테이너 Kubernetes Pod로 배포됩니다.
  • 로드 밸런서(udp-lb). stream 모듈과 함께 로드되는 컨테이너화된 NGINX 서버입니다. 로드 밸런스 업스트림 세트는 DynamoDB 테이블(game-server-status-by-endpoint)에 저장된 전용 게임 서버 상태에 따라 초기화 시 구성됩니다. DynamoDB 테이블 lb-status-by-endpoint에는 사용 가능한 로드 밸런서 인스턴스도 저장되며 이러한 인스턴스는 매칭 서비스 같은 핵심 게임 서비스에 사용됩니다.
  • Kubernetes 클러스터에 배포된 게임 서버 및 로드 밸런서 인스턴스의 초기화 및 종료를 캡처하는 Amazon SQS 대기열.
  • 게임 서버 및 로드 밸런서 인벤토리와 관련된 클러스터 상태를 유지하는 DynamoDB 테이블.
  • 게임 서버 및 로드 밸런서에 사용 가능한 리소스의 업데이트된 목록을 제공하는 AWS Lambda 기반 API 작업(game-server-inventory-api-lambda). 이 작업은 로드 밸런서에서 업스트림 대상 게임 서버를 설정하는 데 필요한 /get-available-gs를 지원합니다. 또한 사용 가능한 게임 서버 인벤토리에서 이미 클레임된 게임 서버에 레이블을 지정하는 /set-gs-busy/{endpoint}를 지원합니다.
  • Amazon SQS 대기열을 통해 트리거되고 DynamoDB 테이블을 입력하는 Lambda 함수(game-server-status-poller-lambda).

예약 메커니즘

이 예제의 목표는 동일한 로드 밸런서 게임 엔드포인트를 제공하는 게임 서버두 대가 동시에 중단될 가능성을 줄이는 것입니다. 따라서 동일한 호스트의 동일한 게임 서버(mockup-UDP-server)가 예약되지 않도록 해야 합니다. 이 예제에서는 Pod 선호도/반선호도 정책이 적용되는 advanced scheduling in Kubernetes를 사용합니다.

다음과 같이 mockup-grp1 및 mockup-grp2 소프트 레이블 2개를 podAffinity 섹션에 정의합니다.

      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                      - mockup-grp1
              topologyKey: "kubernetes.io/hostname"

requiredDuringSchedulingIgnoredDuringExecution은 Pod 예약 시 후속 규칙이 충족되어야 함을 스케줄러에 알려줍니다. 이 규칙은 topologyKey: “kubernetes.io/hostname”으로 인해 값이 key: “app” mockup-grp1인 Pod를 key: “app” mockup-grp2인 Pod와 동일한 노드에 예약할 수 없음을 명시합니다.

로드 밸런서 Pod(udp-lb)가 예약되면 game-server-inventory-api 엔드포인트에 서로 다른 노드에서 실행되는 2개의 게임 서버 Pod를 쿼리합니다. 이 요청이 이행되지 않는 경우 로드 밸런서 Pod는 사용 가능한 게임 서버 2개가 준비될 때까지 충돌 루프로 전환됩니다.

시험 사용

스팟 인스턴스를 사용하는 Amazon EKS 클러스터의 구축 방법이 안내된 예제 2개를 제공하였습니다. 첫 번째 예제인 Spotable Game Server에서는 클러스터를 생성하고 스팟 인스턴스를 배포한 후 게임 서버를 도커화하고 배포합니다. 두 번째 예제인 Game Server Glutamate에서는 게임 서버 워크로드를 향상하고 스팟 인스턴스 중단 처리용 메커니즘으로 이중화를 활성화합니다.

결론

멀티플레이어 게임 서버에는 몇 분에서 몇 시간까지 지속되는 비교적 짧은 수명의 프로세스가 있습니다. 현재 미국 및 EU 리전에서 관찰되는 스팟 인스턴스의 평균 수명은 몇 시간에서 며칠 사이입니다. 따라서 스팟 인스턴스는 게임 서버에 적합합니다. Amazon GameLift FleetIQ는 스팟 인스턴스에 대한 기본적이고 원활한 지원을 제공하며 Amazon EKS는 플레이어 경험이 중단될 가능성을 최소화하는 메커니즘을 제공합니다. 그러므로 스팟 인스턴스는 캐주얼 멀티플레이어 게임 서버는 물론 하드코어 게임 서버에도 매우 적합한 옵션입니다. 스팟 인스턴스를 멀티플레이어 게임 서버에 사용하는 경우 컴퓨팅 비용의 최대 90%를 절감할 수 있으므로 게임 스튜디오와 플레이어 모두에게 이롭습니다.

– Yahav Biran, Chad Schmutzer, Jeremy Cowan;

이 게시물은 AWS Compute 블로그에 기고한  Running your game servers at scale for up to 90% lower compute cost의 한국어 번역입니다. AWS 테크니컬 어카운트 매니저인 김순근님이 감수하였습니다.