Amazon Web Services ブログ

大規模なゲームサーバを最大90%安いコストで運用する方法

Fortnite: Battle Royale, Warframe, そしてApex Legendsなど成功している多くのビデオゲームでは、プレイヤーがゲームの一部に無料でアクセスできる”Free-to-Play”モデルを採用しています。このようなゲームは、もはや低品質なものではなく、プレミアムな品質を必要とします。ビジネスモデルはコストの制約を受けていますが、そのような状況に対してAmazon EC2 スポットインスタンスは実行可能な低コストのコンピューティングオプションを提供します。カジュアルなマルチプレイヤーゲームはもちろん、マルチプレイヤーゲームサーバのワークロードを実行するAmazon EKSコンテナのオーケストレーションではプレイヤーへの影響を最小限に抑え、コストを最適化するメカニズムが必要となりますが、そのような場合にはスポットインスタンスの利用がフィットします。

スポットインスタンスはAWSクラウドの利用可能な予備のコンピュートキャパシティを提供することで、オンデマンドに比べて大幅な割引価格でご利用が可能です。スポットインスタンスによるコストを最適化によって、これまでと同じ予算でアプリーケーションのスループットを最大10倍にまでスケールできます。スポットインスタンスはフォールトトレラント(障害許容)なワークロードに最適です。マルチプレイヤーゲームサーバも例外ではありません。ゲームサーバの状態は、リアルタイムなプレイヤーの入力によって更新され、サーバは一時的な状態を保持します。ゲームサーバのワークロードは使い捨てになることが多く、スポットインスタンスを利用することでコンピュートにかかるコストを最大90%削減できます。このブログでは、スポットインスタンスを効果的に使用するためのゲームサーバワークロードの設計方法について説明します。

ゲームサーバワークロードの特徴

簡単に言えば、マルチプレイヤーゲームサーバは現在のキャラクターの位置と状態(主にアニメーション)を更新するためにほとんどの時間を費やします。残りの時間は、戦闘に関するアクションや移動、その他ゲームイベントに関連する画像の更新に費やされます。具体的にゲームサーバのCPUは、クライアントの位置情報を受信し、ゲームステートを計算し、その情報を特定の複数のクライアントに送信するという処理のためにネットワークI/Oを利用します。以上のことから、ゲームサーバのワークロードはカジュアルなマルチプレイヤーゲームには汎用インスタンスタイプ、ハードコアなマルチプレイヤーゲームにはコンピューティング最適化インスタンスタイプが適しています。

AWSはコンピューティング最適化(C4およびC5)や汎用(M5)をはじめとした多種多様なインスタンスタイプにおいてスポットインスタンスを提供しています。キャパシティはアベイラビリティゾーン内のインスタンスタイプごとに別々に変動するため、幅広いインスタンスタイプを使用することで同じ価格でより多くのコンピューティング能力を得ることができます。スポットインスタンスのベストプラクティスに関する情報はAmazon EC2 Spot Instancesの開始方法をご覧ください。

専用のゲームサーバを実行するためのソリューションの1つとして、Amazon GameLiftがあります。GameLiftはAWSの各リージョンでAmazon GameLift FleetIQやスポットインスタンスをデプロイします。FleetIQはプレイヤーのレイテンシ、インスタンス料金、そしてスポットインスタンスの中断(※)を気にしなくてもいいように中断レート(Interuption Rate)をベースとして新しいセッションをゲームサーバに振り分けます。より詳細な情報はAWS Game Techブログに掲載されている”Reduce Cost by up to 90% with Amazon GameLift FleetIQ and Spot Instances“をご覧ください。

(※)スポットインスタンスは空きリソースを提供している特性上、インスタンス需要に応じて中断されることがあります。詳しくはスポットインスタンスの中断をご覧ください。

その他のケースとして、マルチプレイヤーゲームサーバの展開にKubernetesやSwarm, Amazon ECSなどのコンテナベースのオーケストレーションによるゲームサーバのデプロイメントパターンを利用することができます。これらのシステムは大量のゲームサーバとしてデプロイされた複数のリージョンにまたがるDockerコンテナを管理します。本ブログの残りのパートではコンテナ化されたゲームサーバソリューションにフォーカスします。コンテナは軽量で、高速に起動し、ベースとなるインスタンスの使用率が向上するという特性があるため、ゲームサーバのワークロードに適しています。

なぜAmazon EC2 スポットインスタンスを利用するのか?

スポットインスタンスは、使い捨てのゲームサーバワークロードを実行するための選択肢のひとつです。中断2分前に通知を受け取ることで、中断に備えることができます。インスタンスメタデータAmazon CloudWatchによる通知処理の例を2つご紹介します。詳しくはこのブログの後半の「中断のハンドリング」および「ゲームサーバを冗長化するためには?」を参照してください。

スポットインスタンスは汎用およびコンピューティング最適化(C4およびC5)などゲームサーバのワークロードに適合するさまざまなEC2インスタンスタイプを提供します。また、スポットインスタンスは低い中断レートを提供します。スポットインスタンスアドバイザーは過去の履歴に基づいて、中断レートの低いインスタンスタイプを選択するのに役立ちます。

中断のハンドリング

スポットインスタンスを使用する場合、中断によるプレイヤーへの影響を回避することが重要です。ここでは、GitHub上の”Spotable Game Server“で公開されているリファレンスアーキテクチャとサンプルコードによって、プレイヤー影響を避けるための戦略についてご紹介します。具体的には、Amazon EKSにおける、”kubectl drain”コマンドによるNode Drainageを例として挙げます。これにより、ノードのアンスケジューリング(解除)が可能となり、プレイヤーエクスペリエンスに影響を及ぼす可能性がある終了期間(terminationGracePeriodSeconds)にあるノード上で実行されているpodを削除します。その結果、podは正常に終了するシグナルがゲーム内に送信されている間も可動を続けます。

Node Drainage(ノードのドレイニング)

Node DrainageにはAmazon CloudWatchまたはインスタンスメタデータからスポットインスタンスの中断に関する情報を取得するためにDaemonSetとして実行されるエージェントpodが必要です。今回はインスタンスメタデータによる通知を利用します。以下はNode Drainageによる終了イベントのハンドリングに関する説明です。GitHub上のYAMLファイルのデプロイ例もご参考ください。

  1. “terminationGracePeriodSeconds”のデフォルトを120秒としてゲームサーバpodを起動します。GitHub上のYAMLファイルもご参考ください。
  2. オンデマンドインスタンスとスポットインスタンスを混在したインスタンスポリシーを利用してWorker Node Poolをプロビジョニングします。こちらのポリシーは最低価格によるスポットインスタンスのアロケーション戦略を利用します。GitHub上のAWS CloudFormationテンプレートをご参考ください。
  3. 各ノードのインスタンスライフサイクルやオンデマンド/スポットの区別に関するラベリングのためにAmazon EKS bootstrap tool(推奨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. すべてのノードにデプロイされたDaemonSetはインスタンスメタデータのエンドポイントから終了ステータスを取得します。終了通知を受け取ったら、”kubectl drain node’コマンドが実行され、ゲームサーバpodに対してSIGTERMのシグナルが送信されます。これらのコマンドの詳細はGitHub上の”the batch file“をご覧ください。
  5. ゲームサーバはその後120秒間実行され、ゲームはプレイヤーに対し終了通知を受け取ったことを通知することができます。
  6. “unschedulable”にマークされたノードには新しいゲームサーバはスケジュールされません。
  7. マッチメイキングシステムなどの外部システムに対する通知が送信され、利用可能なゲームサーバのインベントリ情報が更新されます。

Kubernetes利用におけるの最適化戦略

このセクションでは、プロビジョニングされたWorker Node上でゲームサーバの最適な配置に関する、kubernetesの仕様で推奨される戦略をいくつかご紹介します。

  • 基本的に単一のスポットインスタンスAuto ScalingグループをWorker Nodeとして使用します。複数のAuto Scalingグループ利用に対応するためには、Kubernetes nodeSelectorを使用してスポットインスタンスベースのAuto Scalingグループ内のノード上でゲームサーバのスケジューリングを制御します。
nodeSelector:
     lifecycle: spot
        title: your game 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には対応しておりません)

このセクションでは、ネットワークトランスポートレイヤーでUDPを利用した、Amazon EKSのpodとしてデプロイされたゲームサーバを冗長化するためのソリューションを提案します。スポットインスタンスをUDPロードバランサとして利用していますが、必ずスポットインスタンスである必要はありません。

以下の図はAmazon EKS上でUDPロードバランサを実装するためのリファレンスアーキテクチャとなります。マルチプレイヤーのゲームサービスをシュミレートするコンポーネントセットのAmazon EKSクラスターのセットが必要です。例えば、現在稼働中のゲームサーバのステータスや配置を収集するゲームサーバのインベントリなどが該当します。

新しいゲームサーバの情報はAmazon SQSにレポートされ、Amazon DynamoDBのテーブル内に永続化されます。プレイヤーの割当が必要な場合、マッチメイキングサービスはDynamoDBテーブルを使用するゲームサーバインベントリのAPIエンドポイントに対してプレイヤーに最適なサーバを照会します。

本ソリューションは以下のメインとなるコンポーネントを含んでいます。

  • ゲームサーバ(GitHub上のmockup-udp-serverを参照)。こちらは接続されたプレイヤーからゲームの状態の差分を受け取り、疑似計算を元に更新された状態を他のプレイヤーに送信するシンプルなUDPソケットサーバです。こちらは専用ゲームサーバでUDPベースのロードバランシングのシュミレーションを目的としたシングルスレッドサーバです。ネットワーク最適化のために”hostNetwork: true”が設定されたKubernetesのpodであり、単一のコンテナとしてデプロイされます。
  • ロードバランサ(udp-lb)。こちらはコンテナ化されたストリームモジュールをロードしたNGINXサーバです。ロードバランサのアップストリームセットはDynamoDBテーブル(game-server-status-by-endpoint)上の専用ゲームサーバのステータスをベースに初期化時に設定されます。利用可能なロードバランサインスタンスもマッチメイキングなどのコアゲームサービスから利用されるためにDynamoDB(lb-status-by-endpoint)に保存されています。
  • Kubernetesクラスタにデプロイされたゲームサーバとロードバランサインスタンスの初期化や終了の状態を取得するためのAmazon SQSのキュー。
  • ゲームサーバとロードバランサのインベントリに関するクラスタの状態を保持するDynamoDBテーブル。
  • 利用可能なゲームサーバとロードバランサの更新リストを提供するAWS Lambda(game-server-inventory-api-lambda)をベースとしたAPI。本APIはロードバランサの設定に必要なアップストリームターゲットとなるゲームサーバを設定するために”/get-available-gs”というオペレーションをサポートする。また、利用可能なゲームサーバインベントリからすでに要求されているゲームサーバにラベルを付与するための”/set-gs-busy/{endpoint}”もサポート。
  • Amazon SQSをトリガとし、DynamoDBテーブルへデータを保存するLambdaファンクション(game-server-status-poller-lambda)

スケジューリングのメカニズム

同じロードバランサにあるゲームエンドポイントが同時に中断される可能性を低減する必要があります。したがって、同じホスト上におけるゲームサーバのスケジューリングを防止しなくてはいけません。この例では、pod affinity/anti-affinityポリシーが適用された”advanced scheduling in Kubernetes“を利用します。

次のように、podAffinityセクションでmockup-grp1とmockup-grp2の2つのラベルを定義します。

      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-grp-1の値を持つpodはkey: “app” mockup-grp-2と同じノードにスケジューリングされません。

ロードバランサのpod(udp-lb)がスケジューリングされると、異なるノードで稼働する2つのゲームサーバpodが稼働しているかの確認についてgame-server-inventory-apiエンドポイントに対して照会されます。このリクエストの条件が満たされない場合、利用可能な2つのゲームサーバの準備が整うまでロードバランサpodはクラッシュループに陥ります。

試してみよう!

本記事では、スポットインスタンスを使ったAmazon EKSクラスタを構築する方法について2つの例をご紹介しました。最初の例であるSpotable Game Serverは、クラスタを作成、スポットインスタンスとコンテナ化されたゲームサーバをデプロイします。2つめの例であるGame Server Glutamateでは、ゲームサーバのワークロードを強化し、スポットインスタンスの中断を処理するためのメカニズムとしてゲームサーバの冗長性を実現しました。

まとめ

マルチプレイヤーゲームサーバは数分から数時間の間で持続する短命なプロセスで実装されています。USおよびEUリージョンで現在観測されているスポットインスタンスの平均稼働時間は数時間から数日の間であることから、スポットインスタンスはゲームサーバのワークロードに適していると言えます。Amazon GameLift FleetIQはスポットインスタンスをネイティブかつシームレスにサポートし、Amazon EKSはプレイヤーエクスペリエンスを中断する可能性を最小限に抑えるメカニズムを備えています。これらにより、スポットインスタンスはカジュアルなマルチプレイヤーゲームサーバだけでなく、ハードコアゲームサーバにとっても魅力的なオプションとなります。マルチプレイヤーゲームサーバにスポットインスタンスを使用するゲームスタジオでは、コンピューティングコストを最大90%節約しており、彼らだけでなくプレイヤーにとってもメリットがあります。ぜひゲームサーバにスポットインスタンスをご活用ください。


この記事はAWSのソリューションアーキテクトであるYahav Biran, Chad Schmutzer, and Jeremy Cowanの寄稿です。

原文:https://thinkwithwp.com/jp/blogs/compute/running-your-game-servers-at-scale-for-up-to-90-lower-compute-cost/

翻訳:ゲームソリューションアーキテクト 吉田