Skip to main content

Command Palette

Search for a command to run...

Run & Build GameServer(Rust)- GKE+Agones Platform

Published
10 min readView as Markdown
J

Having total 16+ years of experience, 10+ as a Java/J2EE Server Side Backend Developer, 6+ years on Cloud(AWS/GCP).

Agones is a library for hosting, running and scaling dedicated game servers on Kubernetes. It is an open source platform, for deploying, hosting, scaling, and orchestrating dedicated game servers for large scale multiplayer games, built on top of the industry standard, distributed system platform Kubernetes. It replaces bespoke or proprietary cluster management and game server scaling solutions with an open source solution that can be utilised and communally developed — so that you can focus on the important aspects of building a multiplayer game, rather than developing the infrastructure to support it.

Built with both Cloud and on-premises infrastructure in mind, Agones can adjust its strategies as needed for Fleet management, autoscaling, and more to ensure the resources being used to host dedicated game servers are cost optimal for the environment that they are in.

This blog post would cover about how to use the Agones Rust SDK in a simple Rust game server. We would be leveraging managed kubernetes offering from Google Cloud Platform(Google Kubernetes Engine) with Agones installed on top of that.

Below are some of the prerequisites before you can start building and running a simple game server using Rust SDK for Agones.

  1. Docker

  2. Agones installed on GKE

  3. kubectl properly configured

  4. A local copy of the Agones repository

  5. A repository for Docker images, such as Docker Hub or GC Container Registry

Follow these steps to create a Google Kubernetes Engine (GKE) cluster for your Agones install.

Before you begin

Take the following steps to enable the Kubernetes Engine API:

  1. Visit the Kubernetes Engine page in the Google Cloud Platform Console.

  2. Create or select a project.

  3. Wait for the API and related services to be enabled. This can take several minutes.

  4. Enable billing for your project.

Choosing a shell

We can use either Google Cloud Shell or a local shell.

Google Cloud Shell is a shell environment for managing resources hosted on Google Cloud Platform (GCP). Cloud Shell comes preinstalled with the gcloud and kubectl command-line tools. gcloud provides the primary command-line interface for GCP, and kubectl provides the command-line interface for running commands against Kubernetes clusters.

If you prefer using your local shell, you must install the gcloud and kubectl command-line tools in your environment.

Cloud shell

To launch Cloud Shell, perform the following steps:

  1. Go to Google Cloud Platform Console.

  2. From the top-right corner of the console, click the Activate Google Cloud Shell button.

3. A Cloud Shell session opens inside a frame at the bottom of the console. Use this shell to run gcloud and kubectl commands.

4. Set a compute zone in your geographical region with the following command. An example compute zone is us-west1-a.

gcloud config set compute/zone [COMPUTE_ZONE]

Creating the firewall

We need a firewall to allow UDP traffic to nodes tagged as game-server via ports 7000-8000. These firewall rules apply to cluster nodes you will create in the next section.

gcloud compute firewall-rules create game-server-firewall \
  --allow udp:7000-8000 \
  --target-tags game-server \
  --description "Firewall to allow game server udp traffic"

Creating the cluster

A cluster consists of at least one control plane machine and multiple worker machines called nodes. In Google Kubernetes Engine, nodes are Compute Engine virtual machine instances that run the Kubernetes processes necessary to make them part of the cluster.

gcloud container clusters create [CLUSTER_NAME] --cluster-version=1.23 \
  --tags=game-server \
  --scopes=gke-default \
  --num-nodes=4 \
  --no-enable-autoupgrade \ 
  --enable-image-streaming \
  --machine-type=e2-standard-4

(Optional) Creating a dedicated node pool

Create a dedicated node pool for the Agones resources to be installed in. If you skip this step, the Agones controllers will share the default node pool with your game servers, which is fine for experimentation but not recommended for a production deployment.

gcloud container node-pools create agones-system \
  --cluster=[CLUSTER_NAME] \
  --no-enable-autoupgrade \
  --node-taints agones.dev/agones-system=true:NoExecute \
  --node-labels agones.dev/agones-system=true \
  --num-nodes=1

(Optional) Creating a metrics node pool

Create a node pool for Metrics if you want to monitor the Agones system using Prometheus with Grafana or Cloud Logging and Monitoring.

gcloud container node-pools create agones-metrics \
  --cluster=[CLUSTER_NAME] \
  --no-enable-autoupgrade \
  --node-taints agones.dev/agones-metrics=true:NoExecute \
  --node-labels agones.dev/agones-metrics=true \
  --num-nodes=1

Setting up cluster credentials

Finally, let’s tell gcloud that we are speaking with this cluster, and get auth credentials for kubectl to use.

gcloud config set container/cluster [CLUSTER_NAME]
gcloud container clusters get-credentials [CLUSTER_NAME]

Install Agones using Helm

Install Agones on a Kubernetes cluster using the Helm package manager.

Prerequisites

Helm 3

To install the chart with the release name my-release using our stable helm repository:

helm repo add agones https://agones.dev/chart/stable
helm repo update
helm install my-release --namespace agones-system --create-namespace agones/agones

When running in production, Agones should be scheduled on a dedicated pool of nodes, distinct from where Game Servers are scheduled for better isolation and resiliency. By default Agones prefers to be scheduled on nodes labeled with agones.dev/agones-system=true and tolerates node taint agones.dev/agones-system=true:NoExecute. If no dedicated nodes are available, Agones will run on regular nodes, but that’s not recommended for production use.

Namespaces

By default Agones is configured to work with game servers deployed in the default namespace. If you are planning to use another namespace you can configure Agones via the parameter gameservers.namespaces.

For example to use default and xbox namespaces:

kubectl create namespace xbox
helm install my-release agones/agones --set "gameservers.namespaces={default,xbox}" --namespace agones-system

If you want to add a new namespace afterward upgrade your release:

kubectl create namespace ps4
helm upgrade my-release agones/agones --reuse-values --set "gameservers.namespaces={default,xbox,ps4}" --namespace agones-system

Uninstalling the Chart

To uninstall/delete the my-release deployment:

helm uninstall my-release --namespace=agones-systemRBAC

By default, agones.rbacEnabled is set to true. This enables RBAC support in Agones and must be true if RBAC is enabled in your cluster.

The chart will take care of creating the required service accounts and roles for Agones.

helm install my-release --namespace agones-system \
  --set gameservers.minPort=1000,gameservers.maxPort=5000 agones

The above command will deploy Agones controllers to agones-system namespace. Additionally Agones will use a dynamic GameServers' port allocation range of 1000-5000. Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,

helm install my-release --namespace agones-system -f values.yaml agones/agones

Helm test

Check the Agones installation by running the following command:

helm test my-release --cleanup
RUNNING: agones-test
PASSED: agones-test

This test would create a GameServer resource and delete it afterwards.

Controller TLS Certificates

By default agones chart generates tls certificates used by the admission controller, while this is handy, it requires the agones controller to restart on each helm upgrade command.

Manual

For most use cases the controller would have required a restart anyway (eg: controller image updated). However if you really need to avoid restarts we suggest that you turn off tls automatic generation (agones.controller.generateTLS to false) and provide your own certificates (certs/server.crt,certs/server.key).

Cert-Manager

Another approach is to use cert-manager.io solution for cluster level certificate management.

In order to use the cert-manager solution, first install cert-manager on the cluster. Then, configure an Issuer/ClusterIssuer resource and last configure a Certificate resource to manage controller Secret. Make sure to configure the Certificate based on your system’s requirements, including the validity duration.

Here is an example of using a self-signed ClusterIssuer for configuring controller Secret where secret name is my-release-cert or {{ template "agones.fullname" . }}-cert:

#!/bin/bash
# Create a self-signed ClusterIssuer
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned
spec:
  selfSigned: {}
EOF
# Create a Certificate with IP for the my-release-cert )
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-release-cert
  namespace: agones-system
spec:
  dnsNames:
    - agones-controller-service.agones-system.svc
  secretName: my-release-cert
  issuerRef:
    name: selfsigned
    kind: ClusterIssuer
EOF

After the certificates are generated, we will want to inject caBundle into controller webhook and disable controller secret creation by setting the following:

helm install my-release \
  --set agones.controller.disableSecret=true \
  --set agones.controller.customCertSecretPath[0].key='ca.crt',customCertSecretPath[0].path='ca.crt'
  --set agones.controller.customCertSecretPath[1].key='tls.crt',customCertSecretPath[1].path='server.crt'
  --set agones.controller.customCertSecretPath[2].key='tls.key',customCertSecretPath[2].path='server.key'
  --set agones.controller.allocationApiService.annotations={'cert-manager.io/inject-ca-from': 'agones-system/my-release-cert'} \
  --set agones.controller.allocationApiService.disableCaBundle=true \
  --set agones.controller.validatingWebhook.annotations={'cert-manager.io/inject-ca-from': 'agones-system/my-release-cert'} \
  --set agones.controller.validatingWebhook.disableCaBundle=true \
  --set agones.controller.mutatingWebhook.annotations={'cert-manager.io/inject-ca-from': 'agones-system/my-release-cert'} \
  --set agones.controller.mutatingWebhook.disableCaBundle=true \
  --namespace agones-system --create-namespace  \
  agones/agones

Run the simple gameserver

First, run the pre-built version of the simple gameserver and take note of the name that was created:

kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.27.0/examples/rust-simple/gameserver.yaml
GAMESERVER_NAME=$(kubectl get gs -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')

The game server sets up the Agones SDK, calls sdk.ready() to inform Agones that it is ready to serve traffic, prints a message every 10 seconds, and then calls sdk.shutdown() after a minute to indicate that the gameserver is going to exit.

You can follow along with the lifecycle of the gameserver by running

kubectl logs ${GAMESERVER_NAME} rust-simple -f

which should produce output similar to

Rust Game Server has started!
Creating SDK instance
Setting a label
Starting to watch GameServer updates...
Health ping sent
Setting an annotation
Marking server as ready...
...marked Ready
Getting GameServer details...
GameServer name: rust-simple-txsc6
Running for 0 seconds
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Scheduled
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Scheduled
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: RequestReady
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Ready
Health ping sent
Health ping sent
Health ping sent
Health ping sent
Health ping sent
Running for 10 seconds
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Ready
...
Shutting down after 60 seconds...
...marked for Shutdown
Running for 60 seconds
Health ping sent
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Shutdown
GameServer Update, name: rust-simple-txsc6
GameServer Update, state: Shutdown
...

If everything goes as expected, the gameserver will exit automatically after about a minute.

In some cases, the gameserver goes into an unhealthy state, in which case it will be restarted indefinitely. If this happens, you can manually remove it by running

kubectl delete gs ${GAMESERVER_NAME}

Build a simple gameserver

Change directories to your local agones/examples/rust-simple directory. To experiment with the SDK, open up main.rs in your favorite editor and change the interval at which the gameserver calls sdk.health() from 2 seconds to 20 seconds by modifying the line in the thread assigned to let _health to be

thread::sleep(Duration::from_secs(20));

Next build a new docker image by running

cd examples/rust-simple
REPOSITORY=<your-repository> # e.g. gcr.io/agones-images
make build-image REPOSITORY=${REPOSITORY}

The multi-stage Dockerfile will pull down all of the dependencies needed to build the image. Note that it is normal for this to take several minutes to complete.

Once the container has been built, push it to your repository

docker push ${REPOSITORY}/rust-simple-server:0.4

Run the customized gameserver

Now it is time to deploy your newly created gameserver container into your Agones cluster.

First, you need to edit examples/rust-simple/gameserver.yaml to point to your new image:

containers:
- name: rust-simple
  image: $(REPOSITORY)/rust-simple-server:0.4
  imagePullPolicy: Always

Then, deploy your gameserver

kubectl create -f gameserver.yaml
GAMESERVER_NAME=$(kubectl get gs -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')

Again, follow along with the lifecycle of the gameserver by running

kubectl logs ${GAMESERVER_NAME} rust-simple -f

which should produce output similar to

Rust Game Server has started!
Creating SDK instance
Setting a label
Starting to watch GameServer updates...
Health ping sent
Setting an annotation
Marking server as ready...
...marked Ready
Getting GameServer details...
GameServer name: rust-simple-z6lz8
Running for 0 seconds
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Scheduled
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: RequestReady
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: RequestReady
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Ready
Running for 10 seconds
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Ready
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Unhealthy
Health ping sent
Running for 20 seconds
Running for 30 seconds
Health ping sent
Running for 40 seconds
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Unhealthy
Running for 50 seconds
Health ping sent
Shutting down after 60 seconds...
...marked for Shutdown
Running for 60 seconds
Running for 70 seconds
GameServer Update, name: rust-simple-z6lz8
GameServer Update, state: Unhealthy
Health ping sent
Running for 80 seconds
Running for 90 seconds
Health ping sent
Rust Game Server finished.

with the slower healthcheck interval, the gameserver gets automatically marked an Unhealthy by Agones.

To finish, clean up the gameserver by manually removing it

kubectl delete gs ${GAMESERVER_NAME}

More from this blog

Jasbir Singh's blog

31 posts