This post details how I built a real-time Minecraft Server dashboard using AWS Amplify and AWS Appsync to cost-effectively manage Minecraft Servers deployed on Amazon Elastic Compute Cloud (EC2). I haven't spent more than $20 a month in total for two EC2 instances, one for each of my sons to play online with their friends.
Go to this GitHub repository if you want to skip all the reading and deploy this solution in your AWS Account. However, be aware that this is an Open-source solution, and there is no guarantee that it will work as expected. Use at your own risk and support by making improvements if you have time.
If you're not familiar with Minecraft, the real fun is when you set-up a server to play with your friends or even enable players all over the world to play in a unique sandbox Minecraft world that you created using special plugins and add-ons, or basic vanilla. For this to work, your server needs a public IPv4 and proper specs (CPU and Memory) to host your games. If you don't have a spare machine at home, you always have the option to spin up an EC2 instance to explore your wonderful worlds. However, if you want to share access with others to start the server and, more importantly, automatically stop the EC2 instance when the server is idle to reduce cost. The conventional way requires the use of the AWS console, which is not always convenient.
The Dashboard collects and displays data from Amazon CloudWatch Metrics, Amazon Cost Explorer and Amazon CloudTrail. AWS AppSync is a fully managed service that allows me to deploy Serverless GraphQL backends in the AWS cloud. These are the features I am benefiting from AWS AppSync:
- Real-time update by using GraphQL subscription directives, which is based on the serverless WebSockets connections.
- Ability to connect with any DataSource or trigger any event with AWS AppSync resolvers powered by AWS Lambda.
- Integration with Amazon Cognito that sends the authentication token along with the call so I can validate and process the JWT token.
It all started a few months ago when my sons asked me to create a Minecraft server for them to play with their friends. I saw an opportunity for them to learn how to manage Linux servers and solidify fundamental IT concepts like storage, networking, and security. Also, watching them struggle to get Java working with all their Minecraft plugins reminded me when I first started in IT. I don't miss this time.
On the other hand, I gave myself a personal project to create a web dashboard for them that would allow my sons to manage multiple servers and grant permission to who they trust to start and stop the servers. The Dashboard back-end takes care of stop the servers when they are idle; thanks to the Amazon CloudWatch alarm that can automatically stop the server when it reaches a pre-established threshold.
The user authentication is done via Amazon Cognito, and it is federated with Google, as I did not want to manage the user’s password. During the application deployment, you must define the admin’s user email. The admin can start/stop any server, but more importantly, they grant other users permission to start/stop a server and edit the Minecraft’s server initialization bash command executed via AWS System Manager at the server’s start.
Lastly, AWS Amplify provides libraries and declarative interfaces that enable seamless integration with the front-end application when accessing AWS Services. For example, the Minecraft Dashboard heavily relies on the GraphQL integration and schema annotations that AWS Amplify offers.
The picture below shows the Dashboard user interface.
Setting up the Minecraft Server
First and foremost, there are plenty of tutorials available that demonstrates how you can get a Minecraft server installed into an Amazon EC2 instance. THIS IS NOT THE SCOPE of this post.
Important points for you to know when you are creating you EC2 Instance Minecraft Server:
- Make sure you are using an AMI that contains the AWS Systems Manager Agent (SSM Agent) preinstalled. Amazon Machine Images (AMIs) managed by AWS all have it by default.
- When creating the instance, make sure that your security group only gives access to port 25565 to the world. If you need ssh to the instance don’t forget to authorize only your IP address or leverage SSM Session Manager.
- The solution creates and automatically associates the EC2AWSMinecraftProfile Role to the EC2 Instance . If you have attached any other role, it will be replaced by clicking at the FIX IT button. In other words, let this server only be a Minecraft server.
- There is an option at the UI to provide the directory where the Minecraft binaries were installed and inform the command line that will start Minecraft for you. Otherwise, you have either do it manually or pre-configure it at instance level.
- Every time the server is initialized using the Dashboard an AWS Cloudwatch Alarm is configured. By default, it stops the server if the CPU utilization is below 10% for 35 datapoints (1 min) representing 35 minutes. This should be changed in the server configuration UI as there is a risk of the instance never being stopped if the threshold does not correspond to a value that indicates the server is idle.
- The Minecraft Dashboard solution identifies any Amazon EC2 instance that contains the tag App: minecraft that runs in the same region the application was deployed. I also encourage you to define the tag Name, as Dashboard automatically displays it.
- Always start the server from the Dashboard by clicking at the power icon. This process verifies and implements many of the items described above.
The architecture diagram below explains how the information flows in the system.
User Interaction flow (red)
- The user connects to the application using their Google account
- The App invokes ListServers and GetCost AWS Lambda Functions AppSync resolvers’ queries. ListServers returns all servers’ settings of instances tagged with App: Minecraft. The GetCost AWS Lambda sends us back the total cost and hours associated with all instances that contain the tag App: Minecraft. The total cost is displayed at the top bar left corner.
- When the user clicks on the power button icon, it calls the TriggerAction AWS Lambda, verifying if the user is authorized using Amazon Cognito groups to invoke an AWS Step Functions Workflow that initialize the instance. Details of the step-function can be found in this post as well.
- It verifies if there is an Amazon Cognito group named with the EC2 instanceId. If the group does not exist, and the user who invoked is the pre-defined Administrator, then the group gets created. This group is used to grant permission to other users to start/stop the server.
- It reads and writes from Amazon DynamoDb the information about the EC2 Alarm to stop the server and the command to start the Minecraft server using the Execute the RunCommand.
- During an EC2 Instance Start, the workflow awaits both systemStatus and instanceStatus to be ok to move to the next step to execute an AWS System Manager RunCommand to start the Minecraft Server.
Event flow (green)
- When an EC2 Instance State-change Notification event occurs, it triggers the EventResponse AWS Lambda, validating the event. As part of starting the server, an Amazon CloudWatch alarm stop actions are created and associated with the instance. The mutation changeServerState saves the state into AppSync.
- If this is the first Minecraft server instance brought online, a scheduled event rule that runs every 1 min is enabled during the process. Every minute the scheduled event invoke the EventResponse Lambda that collects the Amazon CloudWatch Metric to send it back to the Dashboard web app using the putServerMetric GraphQL mutation.
- Both EC2 instance state change and metrics are sent to AppSync as mutation
- The front-end application is configured with two subscribers: one that listens to the EC2 Instance State Change events and another for Amazon CloudWatch Events Scheduled. As AppSync processes the mutation for each of these events mentioned before, the correspondent subscriber picks up the information and updates it on the Dashboard in real-time.
There are two tables: one for auditing who is accessing the site and capturing their actions, and another table to store the EC2 instance’s alarm configuration and Minecraft command initialization. Aside from these two tables, the solution leverages the AWS APIs to discover and gather information about the Minecraft servers.
The AppSync Schema
One of the most powerful Amplify features is the ability to expand the GraphQL schema to automatically create Amazon DynamoDB database tables, making it easy to configure authorization rules and define your custom business logic using resolvers. For instance, the schema excerpt below shows the authentication annotations @aws_cognito_user_pools and @aws_iam. It configured AppSync only to allow users validated by Cognito, and mutation that contains the IAM annotation will only authorize interaction from the AWS Lambdas using IAM Roles.
Requesting Mapping Templates
AWS AppSync resolvers use mapping templates written in Apache Velocity Template Language (VTL). It translates GraphQL requests from clients to your data source, and it reverses the process to translate the data source response back into a GraphQL response. VTL gives you the power to manipulate both the request and the response in the standard request/response flow of a web application. When using it with AWS Lambda, all you need is to define the payload that is going to be sent. For example, the putServerMetric mutation Resolver cloudformation is configured as following:
The intent of putServerMetric mutation is to update the CPU, Memory, Networking and TCP connections information at the UI. All these information comes from Amazon CloudWatch Metrics and don’t need to be committed in any database. The chart at the UI gets dynamically updated when a GraphQL mutation occurs because there is a GraphQL subscription associated to it. You can go back to the Schema definition and check the onPutServerMetric subscription.
The excerpt code below is from the AWS Lambda that is executed every minute to collect the instances metrics and send it to AWS AppSync. It leverages the AWS4Auth library that enabled AWS Sig v4 for authentication. The variables have to follow the same name defined at the Request Mapping template defined at the CloudFormation resolvers, so we can build the AppSync payload and post it to the AppSync endpoint.
At the front end, a function invokes the onPutServerMetric GraphQL subscription that create an websocket API with the Appsync endpoint that listens to any putServerMetric mutation to provide real-time information in a chart. Below the snipped of code that does it.
Automatically configuring the server
The last step of the AWS Step-Functions when a server is started via the web Dashboard is the ConfigServer Lambda Function. This AWS Function logic can be broken down in the following steps:
- IAM Role validation: It verifies if the EC2 instance is associated with the application IAM Role; if not, it disassociates the current IAM Role and attachs the one the solution installed.
- CloudWatch Agent enablement: Using AWS Systems Manager Run Command APIs, this step installs the CloudWatch agent if needed, and configures the agent based on the SSM Parameter Store that contains the following agent configuration. It sends CPU, Memory and Networking information to Cloudwatch metrics every 60 seconds.
3. Install the script to collect TCP Connection at port 25565: Not the best mechanism to track Minecraft users. I successfully got user logging information using Amazon CloudWatch Insights, but I noticed that the logs where not consistent depending on the Minecraft version. Using SSM the following script is executed to install a cron job to send metric information about estabilish TCP connection. It also installs zip and nettools. The first is used to extract the aws-cli when installing, and the second is to use netstat.
Putting this all together was fun. I envision future features, like launching Minecraft Server’s instances in multiple regions, enabling users to create and manage them independently, and Minecraft mods and plug-ins installations using AWS Systems Manager Run-Command. Perhaps open my own Minecraft hosting service. Nah! Just joking.
Thanks for reading.