Game Server Hosting on AWS Fargate
AWS offers various options for hosting session-based games. Whether you’d like to leverage a managed service like Amazon GameLift, build your own solution using Amazon EC2, or use a container service such as Amazon Elastic Kubernetes Service (EKS) or Amazon Elastic Container Service (ECS), AWS provides secure, resizable capacity to operate your game with low latency and cost.
Containers are a great option for hosting game servers. They are lightweight, standalone, and provide complete control to easily package up game server software (binaries) and deploy them in a consistent way across multiple environments. But for many, having to provision and maintain server instances can be cumbersome. AWS Fargate is a serverless compute engine that works with both ECS and EKS, enabling you to focus on your game without having to manage the underlying infrastructure.
To help you explore game server hosting with AWS Fargate, we’ve created an example solution – now available on Github. This easy to use example includes AWS CloudFormation templates and a step-by-step deployment guide, enabling you to quickly implement a serverless, containerized game server solution on AWS.
The following diagram presents the architecture that you can quickly deploy using the solution’s implementation guide and accompanying AWS CloudFormation templates and deployment scripts:
This solution uses AWS Fargate to host game server tasks on ECS and a serverless backend service to manage the game servers. For the purpose of this example, the game client and server are developed with Unity engine.
To deploy the solution you will need an AWS Account and AWS CLI with access keys configured and you need to also install AWS SAM CLI, Docker, Python 3.7 and Unity 2019. See the Preliminary Setup in the repository for detailed instructions. You will also include some dependencies in the client and server code that are downloaded as part of the setup.
Deployment scripts are available for both Bash and Powershell. This allows you to deploy the solution on the OS and tooling of your choice. See Deployment with Bash Scripts and Deployment with Powershell Scripts for full step-by-step details.
Deployment steps overview
- First, deploy the base infrastructure consisting of a Virtual Private Cloud (VPC) and an ECS Cluster to host the Fargate Tasks. The CloudFormation Stack Exports for Subnet and Security Group ID:s will be referenced by the other Stacks to use to the correct VPC resources.
- Then, deploy the ElastiCache Redis CloudFormation Stack. ElastiCache is hosted in the private subnets of the VPC, and Security Groups allows access only from the AWS Lambda function Security Groups.
- Next, build the game server Linux binary in Unity and run a script to build a Docker image with the binary and upload that to Elastic Container Registry (ECR). The script will then create or update a CloudFormation Stack defining the Task Definition using this newly built container image for hosting our game server Tasks.
- Now we can deploy the backend resources using Serverless Application Model (SAM). This deploys all the Lambda functions as well as Amazon API Gateway that is used by the game client to request a session. The scaler function immediately starts the minimum required amount of Tasks using the Task Definition created in the previous step.
- Once the backend is up, you will deploy a CloudFormation stack that creates a Cognito Identity Pool for player identities that allows players to access the API Gateway with signed requests.
- The last step is to configure the Cognito and API Gateway endpoints to the Unity client and run two clients to see players connecting to the same game session.
The resources of the solution are hosted in a Virtual Private Cloud (VPC) with public and private subnets. All the VPC resources are deployed using a CloudFormation template as the first step of the deployment instructions.
Two Public Subnets are configured in two Availability Zones for high availability, both with 8192 IP addresses (CIDR blocks 10.0.0.0/19 and 10.0.32.0/19) to allow scaling to many game servers. Only the game servers are hosted in these public subnets.
Private subnets are also configured across two Availability Zones and each host 256 IP addresses (CIDR blocks 10.0.64.0/24 and 10.0.65.0/24). Only ElastiCache Redis and backend Lambda functions are hosted in the private subnets. As the resources in private subnets don’t have any internet access, VPC Endpoints for CloudFormation and ECS APIs are configured to allow the Lambda functions to access the resources they need.
The following diagram presents the key components of the networking infrastructure.
Fargate tasks and scaling
The solution hosts 10 game server containers on each Fargate task. Using this approach, you can easily scale the resources used by a single game server based on demand. Scaling game server fleets is significantly faster than running a single game server on each task. This approach also allows ‘micro-sized’ game servers, which are perfect for turn-based and 1-1 games.
Game server tasks are provisioned by a scaler Lambda function that runs continuously. This function monitors the percentage of available game sessions, starting new tasks when the amount goes below your defined threshold. There is no need for downscaling. Once a task has hosted a certain amount of sessions, it will terminate and the scaler will spin up replacements as needed. This ensures that any memory leaks, or other issues you may have don’t escalate easily as servers are quickly replaced.
Each game server container hosts 3 game sessions before terminating and you can change this behavior in the game server code. The amount of game servers in different states are also published as Amazon CloudWatch Metrics, allowing you to view this information, and create CloudWatch alarms as needed.
The following diagram shows some of the CloudWatch Metrics on game servers generated by the scaler function.
Game server data
Game server data is stored in Amazon ElastiCache for Redis. This provides fast, in-memory access to the data from the backend Lambda functions. Game servers report their status to Redis via a Lambda function. Both the player session placement and scaler services use this information to place players into sessions and scale the game server fleet. Using Redis helps increase the speed and scalability of thousands of running game servers, as well as querying game server status and counting game servers in different states.
A game server is always in one of the following states:
- Available: The game server is available for new game sessions.
- Available Priority: The game server is available, and it is running on a Task that has already hosted game sessions. These game servers are prioritized when placing sessions to make sure Tasks are used effectively and replaced once they have hosted maximum amount of sessions (default configuration is 3 session per game server that equals 30 sessions per Task).
- Active: There is already a player on the server and it should be prioritized over all other game servers when searching for game sessions.
- Full: The game server is already at the maximum amount of players and should not be used for placement.
ElastiCache Redis is deployed to the private subnets of the VPC and cannot be accessed publicly. Both ElastiCache Redis and the Lambda functions use the same Security Group that allows inbound traffic to the Redis port 6379 from itself. This way we can ensure that Redis is only accessed from the Lambda functions.
The frontend service is hosted with Amazon API Gateway (https://aws.amazon.com/api-gateway/), backed up with a Lambda function. Game clients call the API securely using an Amazon Cognito identity, enabling you to identify the user. You can also utilize that information for extended features you might like to add, such as player data management. Players are matched to game sessions with a simple logic of prioritizing sessions where there are existing players (with a maximum of two players per session in the example configuration).
There is a prioritization of game servers hosted on tasks that have already hosted sessions before. The purpose of this is to make sure any task is effectively used and terminated, with new servers being used only after the prioritized tasks have completed.
Game server and client
The game itself is developed with Unity engine. The server is built as a headless Unity Linux build. This is subsequently built into a Docker container image and uploaded to Amazon Elastic Container Registry as part of a deployment to update the task definition CloudFormation stack. The server communicates with the backend by directly calling Lambda functions with the AWS SDK to update the server status, and to check if the whole Task is done when it has hosted the maximum number of sessions.
Clients can be run on any operating system, including mobile platforms. In the “game,” two players connect to a session and move around with their characters in a world. A client connects to the game session with a TCP connection using the IP and port received from the serverless backend. ECS also supports UDP protocol and that can be configured in the Task definition. The client uses the AWS SDK as well, to request a Cognito identity and credentials. The requests to the API are then signed with these credentials.
Leveraging containers for game servers allows you have a consistent and lightweight way to run your game servers across different environments. You can reduce your operational effort by leveraging AWS Fargate to run your game server containers without worrying about the underlying infrastructure.
This example shared how to get started with game servers on AWS Fargate that you can easily extend based on your requirements. You can find detailed instructions on setting up your development environment and deploying the solution either with Bash scripts or Powershell scripts in the repository, along with details on all the different components of the solution.
Visit AWS Game Tech to learn more about how you can build your games faster, and operate smarter with AWS.