Amazon GameLiftでゲームサーバーのローリングアップデートをやってみた

2023.12.21

こんにちは、ゲームソリューション部の入井です。

マルチプレイサービスを提供するゲームの運営で悩ましいものの一つが、ゲームのアップデートです。不具合の修正や新コンテンツの提供など、何らかの理由でアップデートは必ず行う機会がありますが、その作業中はユーザーがアップデート対象のサーバーへアクセスできないようにする必要があります。

事前にメンテナンスと称してサービスを停止する時間帯をユーザーへ告知し、その時間内にアップデート作業を済ませるというのが一般的な方法だと思われます。

ただ、これには以下のような欠点もあります。

  • メンテナンス時間中はユーザーが遊べなくなってしまう
    • 平日日中等に行う場合がほとんどですが、それでも影響を受けるユーザーは少なからずいます。
  • 何らかの理由でアップデートが予定時間内に終わらなかった場合、時間を延長するか日を改めてメンテナンスの実施が必要
    • ユーザーからの心証も悪くなります。

上記のようなことを考えると、可能であればサービス停止時間は最小限に抑えたいものです。

そこで、今回はサービスを停止せずにゲームサーバーをアップデートする方法の1つとして、Amazon GameLiftにおけるゲームサーバーのローリングアップデートを試してみたので、その手順についてご紹介します。

なお、あくまでGameLiftにおけるローリングアップデートのため、今回の方法が使えるゲームはシューターや格闘ゲームなど1回のマルチプレイが短い時間で区切られる形式のゲームとなります。MMORPGのようなサーバーの長時間稼働が前提のゲームの場合、GameLiftが上手くマッチせずまた別のアプローチが必要になるためご注意ください。

今回のローリングアップデートの概要

具体的な手順の説明に入る前に、まずは今回の検証内容の概要について書いていきます。

ローリングアップデートとは、一般的には以下のような意味を持つ言葉です。

ローリングアップデート(rolling update)とは、ソフトウェアの更新、入れ替えの方法の一つで、稼働中のシステムを完全には停止させずに徐々に新しいものに入れ替えていく方式。

ローリングアップデート(ローリングアップグレード)とは - 意味をわかりやすく - IT用語辞典 e-Words

また、ゲーム業界向けのWell-Architected Frameworkに以下のように記載があります。

GAMEOPS_BP05: プレイヤーへの影響を最小限に抑えるデプロイ戦略を採用します。

プレイヤーがゲームをプレイできないダウンタイムを最小限に抑えるような、ゲームのソフトウェアとインフラストラクチャのデプロイ戦略を組み込む必要があります。

(中略)

ローリング置換: デプロイのローリング置換の主な目的は、ゲームをシャットダウンしたり、プレイヤーに影響を与えたりすることなく、リソースを実行することです。実行するアップグレードや変更には下位互換性があること、およびシステムの以前のバージョンに隣接して動作することが重要です。

このデプロイでは、その名前が示すように、サーバーインスタンスが、更新バージョンを実行するインスタンスに段階的に入れ替わり (置換またはロールアウト) ます。

AWS Well-Architected Framework - 準備 - ゲーム業界レンズ

「ローリング置換」と、使われている言葉は微妙に異なりますが、意味合い的にはローリングアップデートと同じです。このドキュメントには、GameLiftでローリングアップデートを実現する方法も紹介されています。今回の検証は、この内容を参考に実施しました。

前提条件として、以下の図のような構成でGameLiftを利用しているとします。

プレイヤーのゲーム参加リクエストをキューが受け付け、キューはレイテンシー情報を元に適切なロケーション(リージョン)のインスタンスにプレイヤーを割り当てています。

この状態から、新しいバージョンのゲームサーバーをローリングアップデートでリリースした後の状態が以下の図です。

新バージョンのリリース完了後、キューの設定編集でゲームセッションの割り当て先として新バージョンのフリートを追加し、その優先順位を旧バージョンより高くしています。これにより、新しくゲームへの参加リクエストを送ってきたプレイヤーに対し、キューは新バージョンのゲームサーバーが使われているフリートを割り当てます。

レイテンシによって割り当て先のインスタンスを変えているのは先ほどと同じですが、それも新バージョンのフリート内で行う形になっており、旧バージョンフリートへは追加でプレイヤーを割り当てないようにしています。

一方で、旧バージョンのフリートは残されたままなので、新バージョンのリリース前から旧バージョンのゲームサーバーで遊んでいたプレイヤー達は、ゲームが中断されることなく引き続きマルチプレイを継続できています。

旧バージョンのフリートには新規プレイヤーは入ってこないため、時間が経つにつれプレイヤーは減っていきます。そして、ゲームが全て終了した段階で旧バージョンのフリートを終了させれば、ダウンタイム無しでのゲームサーバーアップデートは完了となります。

なお、この図ではサーバーアップデートのみに焦点を当てていますが、実際はゲームクライアントのアップデートも同時に行われるパターンが多いと思われます。その場合、新バージョンのフリートへ割り当てられたクライアントに対し、バージョン検証を行う手順の追加が必要となります。

検証手順

ここからは、先ほど概要を説明したローリングアップデートについて、具体的な実現手順を書いていきます。

前提条件として、今回の作業は単純化のため以下のような形で行っています。

  • ゲームクライアントは用意せずAWS CLIからGameLiftとやりとり
  • FlexMtachは使用せず直接キューへリクエスト実行

ビルドの作成

最初にフリートで使用するゲームサーバービルドを用意します。

アップデートを実施するため、以下の画像のようにVersion1と2で2つのビルドを作成します。

今回は、マネジメントコンソール上で作成できるサンプルゲームのビルドを使用しました。

こちらで作成したビルドは、名前を変更することで複数作成できるようになっています。V1とV2で中身が同一のビルドになってしまいますが、今回はあくまでGameLift上でサーバーを立ち上げるだけで、実際のゲーム関係の通信は行わないため問題ありません。

フリートの作成

続いて、フリートを作成します。こちらについてもV1とV2で2つ作成します。

以下のように名前はそれぞれのVersionで見分けがつくようにし、使用するビルドは先ほど作成しものをVersionによって使い分けます。名前と使用ビルド以外の設定については、どちらのフリートも同じ内容にします。

今回の検証では、プレイヤーのレイテンシによるインスタンスの振り分けの違いも試すので、ロケーションとしてはap-northeast-1だけでなくus-east-1も選択します。これにより、フリート内でそれぞれのリージョンのインスタンスが起動できるようになります。

キューの作成

フリートの作成が完了したら、キューを作成します。全ての参加リクエストが同じキューを通るようにするので、作るのは1つだけです。

送信先フリートの設定は、まずはアップデート前の挙動を確認したいのでV1のフリートのみを割り当てます。

セッション配置条件の優先順位としては、以下のように送信先順序設定の優先度を一番高くします。これにより、レイテンシー条件が悪い場合であっても手動で設定したフリートの順序を優先してゲームセッションを配置するようになります。

アップデート適用前のプレイヤー割り当て

ビルド、フリート、キューの準備が完了したので、早速プレイヤーからのゲーム参加リクエストがどのように振り分けされるのかを試してみます。

最初に、アップデート前の挙動を確認します。start-game-session-placementを実行し、キューに対しゲームセッション作成のリクエストを送ります。

aws gamelift start-game-session-placement --placement-id g1 \
     --cli-input-json file://start-game-session-ap-northeast-1.json

リクエストのパラメータは、以下のJSONの内容となっています。

{
        "GameSessionQueueName": "SampleCustomGameQueue",
        "MaximumPlayerSessionCount": 1,
        "DesiredPlayerSessions": [
                {
                        "PlayerId": "player1"
                }
        ],
        "PlayerLatencies": [
                {
                        "LatencyInMilliseconds": 10,
                        "PlayerId": "player1",
                        "RegionIdentifier": "ap-northeast-1"
                },
                {
                        "LatencyInMilliseconds": 150,
                        "PlayerId": "player1",
                        "RegionIdentifier": "us-east-1"
                }
        ]
}

複数人参加が必要な検証ではないので、MaximumPlayerSessionCountは1に設定しています。 DesiredPlayerSessionsでゲームセッションと同時にプレイヤーセッションも作成するようにしています。

また、PlayerLatenciesで参加するプレイヤーのリージョンごとのレイテンシ情報を送っています。このプレイヤーは日本からアクセスしている設定なので、ap-northeast-1のレイテンシは低くus-east-1のレイテンシは高くしています。手動でレイテンシの値を設定してしまっていますが、本来はping等で計測した結果を入れる必要があります。このパラメータを送ることで、キューはセッション配置の際にプレイヤーのレイテンシ条件も考慮に入れるようになります。

続いて、追加で別のゲーム参加リクエストを送ります。

aws gamelift start-game-session-placement --placement-id g2 \
 --cli-input-json file://start-game-session-us-east-1.json

こちらのリクエストのパラメータは、最初のものとはレイテンシ情報を変えています。このプレイヤーはアメリカからアクセスしている設定で、先ほどとは逆にap-northeast-1のレイテンシは高くus-east-1のレイテンシは低くしています。

{
        "GameSessionQueueName": "SampleCustomGameQueue",
        "MaximumPlayerSessionCount": 1,
        "DesiredPlayerSessions": [
                {
                        "PlayerId": "player2"
                }
        ],
        "PlayerLatencies": [
                {
                        "LatencyInMilliseconds": 150,
                        "PlayerId": "player2",
                        "RegionIdentifier": "ap-northeast-1"
                },
                {
                        "LatencyInMilliseconds": 10,
                        "PlayerId": "player2",
                        "RegionIdentifier": "us-east-1"
                }
        ]
}

これらのリクエストを送った後、describe-game-sessionsでV1フリートの全てのゲームセッション情報を一覧化した結果が以下の内容です。

{
    "GameSessions": [
        {
            "GameSessionId": "arn:aws:gamelift:ap-northeast-1::gamesession/fleet-c72a44f3-f0ec-4127-86a9-844d0d811747/g1",
            "FleetId": "fleet-c72a44f3-f0ec-4127-86a9-844d0d811747",
            "FleetArn": "arn:aws:gamelift:ap-northeast-1:11111111111:fleet/fleet-c72a44f3-f0ec-4127-86a9-844d0d811747",
            "CreationTime": "2023-12-20T11:34:23.954000+09:00",
            "CurrentPlayerSessionCount": 0,
            "MaximumPlayerSessionCount": 1,
            "Status": "ACTIVE",
            "GameProperties": [],
            "IpAddress": "3.112.249.158",
            "DnsName": "ec2-3-112-249-158.ap-northeast-1.compute.amazonaws.com",
            "Port": 33435,
            "PlayerSessionCreationPolicy": "ACCEPT_ALL",
            "Location": "ap-northeast-1"
        },
        {
            "GameSessionId": "arn:aws:gamelift:ap-northeast-1::gamesession/fleet-c72a44f3-f0ec-4127-86a9-844d0d811747/us-east-1/g2",
            "FleetId": "fleet-c72a44f3-f0ec-4127-86a9-844d0d811747",
            "FleetArn": "arn:aws:gamelift:ap-northeast-1:11111111111:fleet/fleet-c72a44f3-f0ec-4127-86a9-844d0d811747",
            "CreationTime": "2023-12-20T11:37:37.580000+09:00",
            "CurrentPlayerSessionCount": 0,
            "MaximumPlayerSessionCount": 1,
            "Status": "ACTIVE",
            "GameProperties": [],
            "IpAddress": "18.215.248.98",
            "DnsName": "ec2-18-215-248-98.compute-1.amazonaws.com",
            "Port": 33435,
            "PlayerSessionCreationPolicy": "ACCEPT_ALL",
            "Location": "us-east-1"
        }
    ]
}

2つのゲームセッションが立ち上がっており、どちらもフリートIDは同じですがIPアドレスが異なり別々のインスタンスが使用されています。片方のインスタンスのロケーションはap-northeast-1で、もう片方はus-east-1となっています。

キューが、それぞれのプレイヤーのレイテンシ状態に基づいて適切なインスタンスにセッションの割り当てを行ったのが分かります。

アップデート適用後のプレイヤー割り当て

続いて、新バージョンのフリートを加えた状態で検証します。

キューの送信先設定に、以下のようにV2のフリートを追加します。ローリングアップデートを実現するため、V1フリートは残したままですが順番をV2の下にしています。セッション配置の優先順位設定でこのフリートの順序設定が最優先で考慮されるようにしているので、新規プレイヤーどのような場合もV2へ割り当てられるようになります。

キューの設定完了後、以下のように再度日本プレイヤーとアメリカプレイヤーの参加リクエスト(placementIdはそれぞれg3, g4)を送ります。

aws gamelift start-game-session-placement --placement-id g3 \
     --cli-input-json file://start-game-session-ap-northeast-1.json

aws gamelift start-game-session-placement --placement-id g4 \
 --cli-input-json file://start-game-session-us-east-1.json

describe-game-sessionsでV2フリートの現在のセッションリストを出力すると、以下のようになりました。

{
    "GameSessions": [
        {
            "GameSessionId": "arn:aws:gamelift:ap-northeast-1::gamesession/fleet-fc515aa0-788a-401c-9794-4f19c6b666ab/g3",
            "FleetId": "fleet-fc515aa0-788a-401c-9794-4f19c6b666ab",
            "FleetArn": "arn:aws:gamelift:ap-northeast-1:11111111111:fleet/fleet-fc515aa0-788a-401c-9794-4f19c6b666ab",
            "CreationTime": "2023-12-20T11:45:17.261000+09:00",
            "CurrentPlayerSessionCount": 0,
            "MaximumPlayerSessionCount": 1,
            "Status": "ACTIVE",
            "GameProperties": [],
            "IpAddress": "54.168.239.48",
            "DnsName": "ec2-54-168-239-48.ap-northeast-1.compute.amazonaws.com",
            "Port": 33435,
            "PlayerSessionCreationPolicy": "ACCEPT_ALL",
            "Location": "ap-northeast-1"
        },
        {
            "GameSessionId": "arn:aws:gamelift:ap-northeast-1::gamesession/fleet-fc515aa0-788a-401c-9794-4f19c6b666ab/us-east-1/g4",
            "FleetId": "fleet-fc515aa0-788a-401c-9794-4f19c6b666ab",
            "FleetArn": "arn:aws:gamelift:ap-northeast-1:11111111111:fleet/fleet-fc515aa0-788a-401c-9794-4f19c6b666ab",
            "CreationTime": "2023-12-20T11:45:37.165000+09:00",
            "CurrentPlayerSessionCount": 0,
            "MaximumPlayerSessionCount": 1,
            "Status": "ACTIVE",
            "GameProperties": [],
            "IpAddress": "54.227.26.6",
            "DnsName": "ec2-54-227-26-6.compute-1.amazonaws.com",
            "Port": 33435,
            "PlayerSessionCreationPolicy": "ACCEPT_ALL",
            "Location": "us-east-1"
        }
    ]
}

V2フリート内に2つのゲームセッションが作成されており、それぞれ日本向けアメリカ向けの別インスタンスが使用されています。バージョンによるフリートの振り分けが行われた後、更にレイテンシによるインスタンスの振り分けが行われたことが分かります。

また、この状態でdescribe-game-sessionsをV1フリートに対して実行したところ、先ほど作成したのと同じゲームセッションが引き続き稼働していました。

以上の結果から、この方法によりローリングアップデートが無事に行われ、V1フリートで遊んでいたプレイヤー達に影響を与えることなく新規プレイヤーをV2へ割り当てられたことが検証できました。

まとめ

Amazon GameLiftにおけるローリングアップデートについて、その概要と具体的な実施手順をご紹介しました。

ゲームのマルチプレイ運営において、少しでもメンテナンス時間を減らしたいと考えている方は、このようなローリングアップデートの導入を検討してみてはいかがでしょうか。