0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【UE5】学生時代、PlayFabを手作業で組んでいた自分に教えたい。対戦ゲームのサーバー環境はすべてTerraformで書ける

0
Last updated at Posted at 2026-10-03

はじめに

個人開発の対戦ゲームのAWSインフラをすべてTerraformで構築しました。
この記事ではその構成と設計判断をまとめます。

対象のゲーム「The Suitcase Agents」の特徴は次のとおりです。

・個人開発
・開発期間は3か月
・UE5製
・基本プレイ無料
・対戦TPS
・1部屋は最大10人
・同時接続は最大100人を想定
・カジュアルとランクはGameLiftの専用サーバー
・カスタムはSteamのリッスンサーバー

カジュアル、ランク、カスタムの振り分けは前の記事で解説しています。

筆者のインフラ経験は次のとおりです。

・専門学校時代はAzure PlayFabの管理画面から手作業で構築
・IaCを知ったのは就職後
・AWSは今回が初めて

この記事は対戦ゲームのサーバーを手作業で構築している学生や個人開発者に向けて書いています。

なお今のプレイヤー数では専用サーバーのコストが見合いません。
そのため今後はリッスンサーバーだけの方式へ切り替える予定です。
この記事の構成は切り替え前のものです。

構成図

suitcase-article-2-aws.png

Terraformで管理しているAWSの範囲を1枚にまとめました。
構成は部屋API、GameLift、監視と予算、キルスイッチの4つに分かれます。

部屋API

Lambda

部屋APIは部屋の検索と作成を受け持つAPIです。
部屋APIはLambda上で動いています。
クライアントはAPI Gateway経由で部屋APIを呼びます。
呼び出しを受けるとまずSteamの認証チケットで本人を確認します。
次にGameLiftに部屋を作ります。
試合が終わると専用サーバーが結果を送ります。
部屋APIはその結果をSteam Web APIでSteamに書き込みます。
結果ごとのIDをDynamoDBに保存しています。
同じ結果が2回届いても二重に数えないためです。
Steamのキーと専用サーバー用のトークンはSecrets Managerに保存しています。

GameLift

GameLift Fleet

専用サーバーはGameLiftのFleetで動かしています。
Fleetはap-northeast-1とus-east-2に1つずつあります。
プレイヤーはpingが小さいほうのFleetにつながります。
部屋APIはFleetを直接指定せずAliasという別名で指定します。
そのためFleetを作り直しても部屋APIの変更は不要です。
Aliasはカジュアル用とランク用の2つです。
プレイヤーが増えたらランク用だけを別のFleetに切り出せます。
新しいビルドはスクリプトでS3にアップロードしてGameLiftに登録します。
その後TerraformでFleetを作り直します。

監視と予算

AWS Budgets

予算は月250ドルです。
請求額、APIのエラー、Fleetの台数にはCloudWatch Alarmを設定しています。
監視の詳細は別の記事で解説します。

キルスイッチ

AWSには使った分の請求を途中で止める上限がありません。
そのためどこにいてもサーバーを止められるキルスイッチを作りました。

キルスイッチが動く条件は次のとおりです。

・実際の請求が予算の80%にあたる200ドルを超えたとき
・DDoSなどの緊急時にスマートフォンのブラウザからAmazon SNSにメッセージを送ったとき

どちらの場合もAmazon SNS経由で停止用のLambdaが起動します。
停止用のLambdaの処理は次のとおりです。

  1. 部屋APIが新しい部屋を作らないようにSSM Parameter Storeのフラグを設定
  2. 4つのAliasからそれぞれのFleetを特定
  3. 各FleetのDesired、Min、Maxを0に変更

確実に止めるための工夫は次のとおりです。

・EC2の課金が止まるのはFleetを0台にしたとき
・フラグの設定に失敗してもFleetは必ず停止
・停止対象は2つのRegionのカジュアル用とランク用の計4つ
・フラグが立つと部屋APIは部屋の作成を503エラーで拒否
・部屋APIがフラグを読み直す間隔は60秒
・同じSNSトピックに届くCloudWatch Alarmの通知では起動しない判定
・手動で送るメッセージは予算通知と判定される文面

CloudWatch Alarmの通知を除外する判定は2026年7月にAlarmの通知で誤って停止したことを受けて入れました。

この構成にした理由

Region

Regionはap-northeast-1とus-east-2の2つです。
アジア、北米、EUのプレイヤーをカバーできる想定です。
各Regionを選んだ理由は次のとおりです。

・ap-northeast-1は筆者のいる日本のRegion
・ap-northeast-1はテストのイテレーションが最速
・us-east-2は北米をカバー
・us-east-2はEUからも届く範囲と想定

Lambda

クライアントからGameLiftを直接呼ぶとAWSの認証情報をゲームに入れて配ることになります。
ゲームを解析されると認証情報を抜き取られるおそれがあります。
そのためGameLiftの公式も間にバックエンドを挟む構成を推奨しています。
Lambdaは呼ばれたときだけ料金がかかります。
サーバーの管理も不要です。
そのためバックエンドにはLambdaを選びました。
API GatewayとLambdaの設定は次のとおりです。

項目 設定
API Gateway HTTP API
レート制限 毎秒10リクエスト
バースト 25リクエスト
Lambdaのランタイム Python 3.12
Lambdaのメモリ 512MB
Lambdaのタイムアウト 30秒
設定の渡し方 すべてLambdaの環境変数
SteamのPublisher Web API Keyの保存先 Secrets Manager

Spot Instances

Fleetはコストを優先してSpot Instancesで動かしています。
Spot InstancesはAWSの空いているサーバーを安く借りる仕組みです。
ただし途中で止められることがあります。

それでもSpot Instancesを選んだ理由は次のとおりです。

・基本プレイ無料で課金要素なし
・途中で止まってもプレイヤーにある程度は許容してもらえると判断
・リリース前はプレイヤー数が不明

実際には中断はあまり起きていません。

台数の見積もりは次のとおりです。

・c5a.large1台あたり12部屋を想定
・1台あたり最大120人
・同時接続100人なら各Regionに1台で足りる計算

スケールと運用の状況は次のとおりです。

・台数を増やせばそのぶん部屋も増える設計
・スケールは台数の追加で対応する想定
・台数の上限を上げるにはAWSサポートへの申請が必要
・申請はまだ未提出
・プレイヤーの急増はない見込み
・現在は各Regionで1台ずつ稼働

Steam

Steam

本人確認、カスタムのマッチング、成績の保存はSteamの機能に任せています。
それぞれに対応するSteamの機能は次のとおりです。

・本人確認はSteamの認証チケット
・カスタムのマッチングはSteamのロビー
・成績の保存先はSteamのStats

Statsの項目はカジュアル、ランク、プレイヤーのレベルの3つに分けています。
ランクポイントの計算はすべてバックエンドの部屋APIで行います。
クライアントはポイントを表示するだけです。
ポイントが反映されるまでの流れは次のとおりです。

  1. 試合後に専用サーバーが順位とキル数を部屋APIへ送信
  2. 部屋APIがポイントを計算
  3. 部屋APIが計算結果をSteamに書き込み

ポイントの増減は次のとおりです。

・初期値は1000
・8人戦の1位は+50
・8人戦の8位は-40
・キル1つにつき+3
・人数が少ない試合ほど順位による増減は縮小

プレイヤーが集まらず出番は少なかったです。
それでもランクの仕組みはここまで設計しました。

EAC

Easy Anti-Cheat

チート対策としてEAC(Easy Anti-Cheat)を導入しています。
EACまわりの運用は次のとおりです。

・専用サーバーのFleetでEACを有効化
・EOSはクライアントIDの登録だけに使用
・BANしたプレイヤーはそのSteamアカウントではアクセス不可

すべてTerraformで作った理由

すべてTerraformで作った理由は次の5つです。

・設定内容をコードで記録
・変更の差分をGitで管理
・.tfファイルはAIにとって画面操作より扱いが容易
・構成図からAIがコードを生成
・AWSの全サービスをAIのチャットから操作可能

ファイル構成

Terraformでは構成を.tfファイルに書きます。
terraform applyを実行するとTerraformがその内容どおりの環境をAWSに作ります。
ファイルは次のように役割ごとに分けています。

_backend
_backend/
├── terraform_setup/          # 最初に1回だけ作るもの
│   └── iam_gamelift_build_access.tf
├── terraform_stg/            # 本体
│   ├── api_lambda.tf         # API Gateway、Lambda、IAM
│   ├── gamelift_jp.tf        # ap-northeast-1のGameLift
│   ├── gamelift_na.tf        # us-east-2のGameLift
│   ├── data_store.tf         # DynamoDB、Secrets Manager、SSM Parameter
│   ├── monitoring_budget.tf  # アラーム、予算
│   ├── budget_guardrail.tf   # 予算超過時に止めるLambda
│   └── locals.tf / variables.tf / outputs.tf / providers.tf
├── _lambda/                  # Lambdaの中身
└── _build_scripts/           # ビルド、アップロード、applyのシェル
    ├── awssetup.sh           # ビルドをS3に上げてGameLiftに登録
    └── terraform_stg_ordered_apply.sh  # Regionの順にapply

terraform_setupには最初に1回だけ作るものを置いています。
GameLiftがビルドを読むためのIAM Roleなどが該当します。
普段のデプロイではまずawssetup.shでビルドをS3にアップロードします。
次に本体のterraform_stgをapplyします。
terraform_stg_ordered_apply.shはap-northeast-1から順にapplyします。
途中で失敗した場合はそこで処理を止めます。
Regionを増やすときはgamelift_xx.tfを1つ追加するだけです。

Moduleを使わない理由

TerraformのModuleは使っていません。
Moduleは同じ構成を部品として使い回す仕組みです。
しかしこの構成ではメリットを感じられませんでした。
AIにコードを書かせる今なら似たプロジェクトの構成を持ってきて直すほうが速いです。
環境ごとに完結した素直な書き方なら命名規則もそろいます。
そのほうがAIにとっても書きやすいです。

stgだけで運用している理由

環境はstgしか作っていません。
本来は確認用のstgと本番用のprodを分けます。
しかしGameLiftだけで毎月50〜60ドルかかります。
2環境ぶんの費用は個人開発では負担できません。
そのためstgをそのまま本番環境として運用しています。

おわりに

学生時代の筆者はPlayFabの管理画面を頑張って手で操作してインフラを作っていました。
今はすべてTerraformのようなIaCで記述しています。
AI時代の今はゲームの専門学生であれば誰でもサーバー開発ができるようになっていそうです。
学生時代の筆者のように対戦ゲームの公平なサーバーを外部サービスで運用してみたい方の参考になれば嬉しいです。

この構成で動いているゲームはこちらです。
よろしければ遊んでみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?