1 - Feature Stages

This page provides a description of the various stages that Code Blind features can be in, and the relative maturity and support level expected for each level.

Supported Versions

Code Blind versions are expressed as x.y.z, where x is the major version, y is the minor version, and z is the patch version following Semantic Versioning terminology.

Code Blind Features

A feature within Code Blind can be in Alpha, Beta or Stable stage.

Feature Gates

Alpha and Beta features can be enabled or disabled through the agones.featureGates configuration option that can be found in the Helm configuration documentation.

The current set of alpha and beta feature gates:

Feature NameGateDefaultStageSince
Allocated GameServers are notified on relevant Fleet UpdatesFleetAllocationOverflowEnabledBeta1.37.0
CountsAndListsCountsAndListsDisabledAlpha1.37.0
GameServer player capacity filtering on GameServerAllocationsPlayerAllocationFilterDisabledAlpha1.14.0
Player TrackingPlayerTrackingDisabledAlpha1.6.0
DisableResyncOnSDKServerDisableResyncOnSDKServerDisabledAlpha1.37.0
Example Gate (not in use)ExampleDisabledNone0.13.0

Description of Stages

Alpha

An Alpha feature means:

  • Disabled by default.
  • Might be buggy. Enabling the feature may expose bugs.
  • Support for this feature may be dropped at any time without notice.
  • The API may change in incompatible ways in a later software release without notice.
  • Recommended for use only in short-lived testing clusters, due to increased risk of bugs and lack of long-term support.

Beta

A Beta feature means:

  • Enabled by default, but able to be disabled through a feature gate.
  • The feature is well tested. Enabling the feature is considered safe.
  • Support for the overall feature will not be dropped, though details may change.
  • The schema and/or semantics of objects may change in incompatible ways in a subsequent beta or stable releases. When this happens, we will provide instructions for migrating to the next version. This may require deleting, editing, and re-creating API objects. The editing process may require some thought. This may require downtime for applications that rely on the feature.
  • Recommended for only non-business-critical uses because of potential for incompatible changes in subsequent releases. If you have multiple clusters that can be upgraded independently, you may be able to relax this restriction.

Stable

A Stable feature means:

  • The feature is enabled and the corresponding feature gate no longer exists.
  • Stable versions of features will appear in released software for many subsequent versions.

Feature Stage Indicators

There are a variety of features with Code Blind, how can we determine what stage each feature is in?

Below are indicators for each type of functionality that can be used to determine the feature stage for a given aspect of Code Blind.

Custom Resource Definitions (CRDs)

This refers to Kubernetes resource for Code Blind, such as GameServer, Fleet and GameServerAllocation.

New CRDs

For new resources, the stage of the resource will be indicated by the apiVersion of the resource.

For example: apiVersion: "agones.dev/v1" is a stable resource, apiVersion: "agones.dev/v1beta1" is a beta stage resource, and apiVersion: "agones.dev/v1alpha1" is an alpha stage resource.

New CRD attributes

When alpha and beta attributes are added to an existing stable Code Blind CRD, we will follow the Kubernetes Adding Unstable Features to Stable Versions Guide to optimise on the least amount of breaking changes for users as attributes progress through feature stages.

alpha and beta attributes will be added to the existing CRD as optional and documented with their feature stage. Attempting to populate these alpha and beta attributes on an Code Blind CRD will return a validation error if their accompanying Feature Flag is not enabled.

alpha and beta attributes can be subject to change of name and structure, and will result in breaking changes before moving to a stable stage. These changes will be outlined in release notes and feature documentation.

Code Blind Game Server SDK

Any alpha or beta Game Server SDK functionality will be a subpackage of the sdk package. For example , functionality found in a sdk.alphav1 package should be considered at the alpha feature stage.

Only experimental functionality will be found in any alpha and beta SDK packages, and as such may change as development occurs.

As SDK features move to through feature stages towards stable, the previous version of the SDK API will remain for at least one release to enable easy migration to the more stable feature stage (i.e. from alpha -> beta, beta -> stable)

Any other SDK functionality not marked as alpha or beta is assumed to be stable.

REST & gRPC APIs

REST and gRPC API will have versioned paths where appropriate to indicate their feature stage.

For example, a REST API with a prefix of v1alpha1 is an alpha stage feature: http://api.example.com/v1alpha1/exampleaction.

Similar to the SDK, any alpha or beta gRPC functionality will be a subpackage of the main API package. For example, functionality found in a api.alphav1 package should be considered at the alpha feature stage.

2 - Best Practices

Best practices for running Code Blind in production.

Overview

Running Code Blind in production takes consideration, from planning your launch to figuring out the best course of action for cluster and Code Blind upgrades. On this page, we’ve collected some general best practices. We also have cloud specific pages for:

If you are interested in submitting best practices for your cloud prodiver / on-prem, please contribute!

Separation of Code Blind from GameServer nodes

When running in production, Code Blind should be scheduled on a dedicated pool of nodes, distinct from where Game Servers are scheduled for better isolation and resiliency. By default Code Blind prefers to be scheduled on nodes labeled with agones.dev/agones-system=true and tolerates the node taint agones.dev/agones-system=true:NoExecute. If no dedicated nodes are available, Code Blind will run on regular nodes. See taints and tolerations for more information about Kubernetes taints and tolerations.

If you are collecting Metrics using our standard Prometheus installation, see the installation guide for instructions on configuring a separate node pool for the agones.dev/agones-metrics=true taint.

See Creating a Cluster for initial set up on your cloud provider.

Redundant Clusters

Allocate Across Clusters

Code Blind supports Multi-cluster Allocation, allowing you to allocate from a set of clusters, versus a single point of potential failure. There are several other options for multi-cluster allocation:

Spread

You should consider spreading your game servers in two ways:

  • Across geographic fault domains (GCP regions, AWS availability zones, separate datacenters, etc.): This is desirable for geographic fault isolation, but also for optimizing client latency to the game server.
  • Within a fault domain: Kubernetes Clusters are single points of failure. A single misconfigured RBAC rule, an overloaded Kubernetes Control Plane, etc. can prevent new game server allocations, or worse, disrupt existing sessions. Running multiple clusters within a fault domain also allows for easier upgrades.

2.1 - Google Kubernetes Engine Best Practices

Best practices for running Code Blind on Google Kubernetes Engine (GKE).

Overview

On this page, we’ve collected several Google Kubernetes Engine (GKE) best practices.

Release Channels

Why?

We recommned using Release Channels for all GKE clusters. Using Release Channels has several advantages:

  • Google automatically manages the version and upgrade cadence for your Kubernetes Control Plane and its nodes.
  • Clusters on a Release Channel are allowed to use the No minor upgrades and No minor or node upgrades scope of maintenance exclusions - in other words, enrolling a cluster in a Release Channel gives you more control over node upgrades.
  • Clusters enrolled in rapid channel have access to the newest Kubernetes version first. Code Blind strives to support the newest release in rapid channel to allow you to test the newest Kubernetes soon after it’s available in GKE.

What channel should I use?

We recommend the regular channel, which offers a balance between stability and freshness. See this guide for more discussion.

If you need to disallow minor version upgrades for more than 6 months, consider choosing the freshest Kubernetes version possible: Choosing the freshest version on rapid or regular will extend the amount of time before your cluster reaches end of life.

What versions are available on a given channel?

You can query the versions available across different channels using gcloud:

gcloud container get-server-config \
  --region=[COMPUTE_REGION] \
  --flatten="channels" \
  --format="yaml(channels)"

Replace the following:

Managing Game Server Disruption on GKE

If your game session length is less than an hour, use the eviction API to configure your game servers appropriately - see Controlling Disruption.

For sessions longer than an hour, there are currently two possible approaches to manage disruption:

  • (GKE Standard/Autopilot) Blue/green deployment at the cluster level: If you are using an automated deployment process, you can:

    • create a new, green cluster within a release channel e.g. every week,
    • use maintenance exclusions to prevent node upgrades for 30d, and
    • scale the Fleet on the old, blue cluster down to 0, and
    • use multi-cluster allocation on Code Blind, which will then direct new allocations to the new green cluster (since blue has 0 desired), then
    • delete the old, blue cluster when the Fleet successfully scales down.
  • (GKE Standard only) Use node pool blue/green upgrades

3 - Code Blind Game Server Client SDKs

The SDKs are integration points for game servers with Code Blind itself.

Overview

The client SDKs are required for a game server to work with Code Blind.

The current supported SDKs are:

You can also find some externally supported SDKs in our Third Party Content.

The SDKs are relatively thin wrappers around gRPC generated clients, or an implementation of the REST API (exposed via grpc-gateway), where gRPC client generation and compilation isn’t well supported.

They connect to a small process that Code Blind coordinates to run alongside the Game Server in a Kubernetes Pod. This means that more languages can be supported in the future with minimal effort (but pull requests are welcome! 😊 ).

There is also local development tooling for working against the SDK locally, without having to spin up an entire Kubernetes infrastructure.

Connecting to the SDK Server

Starting with Code Blind 1.1.0, the port that the SDK Server listens on for incoming gRPC or HTTP requests is configurable. This provides flexibility in cases where the default port conflicts with a port that is needed by the game server.

Code Blind will automatically set the following environment variables on all game server containers:

  • AGONES_SDK_GRPC_PORT: The port where the gRPC server is listening (defaults to 9357)
  • AGONES_SDK_HTTP_PORT: The port where the grpc-gateway is listening (defaults to 9358)

The SDKs will automatically discover and connect to the gRPC port specified in the environment variable.

If your game server requires using a REST client, it is advised to use the port from the environment variable, otherwise your game server will not be able to contact the SDK server if it is configured to use a non-default port.

Function Reference

While each of the SDKs are canonical to their languages, they all have the following functions that implement the core responsibilities of the SDK.

For language specific documentation, have a look at the respective source (linked above), and the examples.

Calling any of state changing functions mentioned below does not guarantee that GameServer Custom Resource object would actually change its state right after the call. For instance, it could be moved to the Shutdown state elsewhere (for example, when a fleet scales down), which leads to no changes in GameServer object. You can verify the result of this call by waiting for the desired state in a callback to WatchGameServer() function.

Functions which changes GameServer state or settings are:

  1. Ready()
  2. Shutdown()
  3. SetLabel()
  4. SetAnnotation()
  5. Allocate()
  6. Reserve()
  7. Alpha().SetCapacity()
  8. Alpha().PlayerConnect()
  9. Alpha().PlayerDisconnect()
  10. Alpha().SetCounterCount()
  11. Alpha().IncrementCounter()
  12. Alpha().DecrementCounter()
  13. Alpha().SetCounterCapacity()
  14. Alpha().AppendListValue()
  15. Alpha().DeleteListValue()
  16. Alpha().SetListCapacity()

Lifecycle Management

Ready()

This tells Code Blind that the Game Server is ready to take player connections. Once a Game Server has specified that it is Ready, then the Kubernetes GameServer record will be moved to the Ready state, and the details for its public address and connection port will be populated.

While Code Blind prefers that Shutdown() is run once a game has completed to delete the GameServer instance, if you want or need to move an Allocated GameServer back to Ready to be reused, you can call this SDK method again to do this.

Health()

This sends a single ping to designate that the Game Server is alive and healthy. Failure to send pings within the configured thresholds will result in the GameServer being marked as Unhealthy.

See the gameserver.yaml for all health checking configurations.

Reserve(seconds)

With some matchmaking scenarios and systems it is important to be able to ensure that a GameServer is unable to be deleted, but doesn’t trigger a FleetAutoscaler scale up. This is where Reserve(seconds) is useful.

Reserve(seconds) will move the GameServer into the Reserved state for the specified number of seconds (0 is forever), and then it will be moved back to Ready state. While in Reserved state, the GameServer will not be deleted on scale down or Fleet update, and also it could not be Allocated using GameServerAllocation.

This is often used when a game server process must register itself with an external system, such as a matchmaker, that requires it to designate itself as available for a game session for a certain period. Once a game session has started, it should call SDK.Allocate() to designate that players are currently active on it.

Calling other state changing SDK commands such as Ready or Allocate will turn off the timer to reset the GameServer back to the Ready state or to promote it to an Allocated state accordingly.

Allocate()

With some matchmakers and game matching strategies, it can be important for game servers to mark themselves as Allocated. For those scenarios, this SDK functionality exists.

There is a chance that GameServer does not actually become Allocated after this call. Please refer to the general note in Function Reference above.

The agones.dev/last-allocated annotation will be set on the GameServer to an RFC3339 formatted timestamp of the time of allocation, even if the GameServer was already in an Allocated state.

Note that if using SDK.Allocate() in combination with GameServerAllocations, it’s possible for the agones.dev/last-allocated timestamp to move backwards if clocks are not synchronized between the Code Blind controller and the GameServer pod.

Shutdown()

This tells Code Blind to shut down the currently running game server. The GameServer state will be set Shutdown and the backing Pod will be Terminated.

It’s worth reading the Termination of Pods Kubernetes documentation, to understand the termination process, and the related configuration options.

As a rule of thumb, implement a graceful shutdown in your game sever process when it receives the TERM signal from Kubernetes when the backing Pod goes into Termination state.

Be aware that if you use a variation of System.exit(0) after calling SDK.Shutdown(), your game server container may restart for a brief period, inline with our Health Checking policies.

If the SDK server receives a TERM signal before calling SDK.Shutdown(), the SDK server will stay alive for the period of the terminationGracePeriodSeconds until SDK.Shutdown() has been called.

Configuration Retrieval

GameServer()

This returns most of the backing GameServer configuration and Status. This can be useful for instances where you may want to know Health check configuration, or the IP and Port the GameServer is currently allocated to.

Since the GameServer contains an entire PodTemplate the returned object is limited to that configuration that was deemed useful. If there are areas that you feel are missing, please file an issue or pull request.

The easiest way to see what is exposed, is to check the sdk.proto, specifically at the message GameServer.

For language specific documentation, have a look at the respective source (linked above), and the examples.

WatchGameServer(function(gameserver){…})

This executes the passed in callback with the current GameServer details whenever the underlying GameServer configuration is updated. This can be useful to track GameServer > Status > State changes, metadata changes, such as labels and annotations, and more.

In combination with this SDK, manipulating Annotations and Labels can also be a useful way to communicate information through to running game server processes from outside those processes. This is especially useful when combined with GameServerAllocation applied metadata.

Since the GameServer contains an entire PodTemplate the returned object is limited to that configuration that was deemed useful. If there are areas that you feel are missing, please file an issue or pull request.

The easiest way to see what is exposed, is to check the sdk.proto, specifically at the message GameServer.

For language specific documentation, have a look at the respective source (linked above), and the examples.

Metadata Management

SetLabel(key, value)

This will set a Label value on the backing GameServer record that is stored in Kubernetes.

To maintain isolation, the key value is automatically prefixed with the value “agones.dev/sdk-”. This is done for two main reasons:

  • The prefix allows the developer to always know if they are accessing or reading a value that could have come, or may be changed by the client SDK. Much like private vs public scope in a programming language, the Code Blind SDK only gives you access to write to part of the set of labels and annotations that exist on a GameServer.
  • The prefix allows for a smaller attack surface if the GameServer container gets compromised. Since the game container is generally externally exposed, and the Code Blind project doesn’t control the binary that is run within it, limiting exposure if the game server becomes compromised is worth the extra development friction that comes with having this prefix in place.

Setting GameServer labels can be useful if you want information from your running game server process to be observable or searchable through the Kubernetes API.

SetAnnotation(key, value)

This will set an Annotation value on the backing GameServer record that is stored in Kubernetes.

To maintain isolation, the key value is automatically prefixed with “agones.dev/sdk-” for the same reasons as in SetLabel(…) above. The isolation is also important as Code Blind uses annotations on the GameServer as part of its internal processing.

Setting GameServer annotations can be useful if you want information from your running game server process to be observable through the Kubernetes API.

Counters And Lists

The Counters and Lists features in the SDK offer a flexible configuration for tracking various entities like players, rooms, and sessions.

Declared keys and default values for Counters and Lists are specified in GameServer.Spec.Counters and GameServer.Spec.Lists respectively.

Modified Counter and List values and capacities will be updated in GameServer.Status.Counters and GameServer.Status.Lists respectively.

Counters

All functions will return an error if the specified key is not predefined in the GameServer.Spec.Counters resource configuration.

Note: For Counters, the default setting for the capacity is preset to 1000. It is recommended to avoid configuring the capacity to max(int64), as doing so could cause problems with JSON Patch operations.

Alpha().GetCounterCount(key)

This function retrieves either the GameServer.Status.Counters[key].Count or the SDK awaiting-batch value for a given key, whichever is most up to date.

Alpha().SetCounterCount(key, amount)

This function sets the value of GameServer.Status.Counters[key].Count for the given key to the passed in amount. This operation overwrites any previous values and the new value cannot exceed the Counter’s capacity.

Alpha().IncrementCounter(key, amount)

This function increments GameServer.Status.Counters[key].Count for the given key by the passed in non-negative amount. The function returns an error if the Counter is already at capacity (at time of operation), indicating no increment will occur.

Alpha().DecrementCounter(key, amount)

This function decreases GameServer.Status.Counters[key].Count for the given key by the passed in non-negative amount. It returns an error if the Counter’s count is already at zero.

Alpha().SetCounterCapacity(key, amount)

This function sets the maximum GameServer.Status.Counters[key].Capacity for the given key by the passed in non-negative amount. A capacity value of 0 indicates no capacity limit.

Alpha().GetCounterCapacity(key)

This function retrieves either the GameServer.Status.Counters[key].Capacity or the SDK awaiting-batch value for the given key, whichever is most up to date.

Lists

All functions will return an error if the specified key is not predefined in the GameServer.Spec.Lists resource configuration.

Alpha().AppendListValue(key, value)

This function appends the specified string value to the List in GameServer.Status.Lists[key].Values.

An error is returned if the string already exists in the list or if the list is at capacity.

Alpha().DeleteListValue(key, value)

This function removes the specified string value from the List in GameServer.Status.Lists[key].Values.

An error is returned if the string does not exist in the list.

Alpha().SetListCapacity(key, amount)

This function sets the maximum capacity for the List at GameServer.Status.Lists[key].Capacity.

The capacity value is required to be between 0 and 1000.

Alpha().GetListCapacity(key)

This function retrieves either the GameServer.Status.Lists[key].Capacity or the SDK awaiting-batch value for the given key, whichever is most up to date.

Alpha().GetListValues(key)

This function retrieves either the GameServer.Status.Lists[key].Values or the SDK awaiting-batch values array for the given key, whichever is most up to date.

Alpha().ListContains(key, value)

Convenience function, which returns if the specific string value exists in the results of Alpha().GetListValues(key).

Alpha().GetListLength(key)

Convenience function, which retrieves the length of the results of Alpha().GetListValues(key).

Player Tracking

Alpha().PlayerConnect(playerID)

This function increases the SDK’s stored player count by one, and appends this playerID to GameServer.Status.Players.IDs.

GameServer.Status.Players.Count and GameServer.Status.Players.IDs are then set to update the player count and id list a second from now, unless there is already an update pending, in which case the update joins that batch operation.

PlayerConnect() returns true and adds the playerID to the list of playerIDs if this playerID was not already in the list of connected playerIDs.

If the playerID exists within the list of connected playerIDs, PlayerConnect() will return false, and the list of connected playerIDs will be left unchanged.

An error will be returned if the playerID was not already in the list of connected playerIDs but the player capacity for the server has been reached. The playerID will not be added to the list of playerIDs.

Alpha().PlayerDisconnect(playerID)

This function decreases the SDK’s stored player count by one, and removes the playerID from GameServer.Status.Players.IDs.

GameServer.Status.Players.Count and GameServer.Status.Players.IDs are then set to update the player count and id list a second from now, unless there is already an update pending, in which case the update joins that batch operation.

PlayerDisconnect() will return true and remove the supplied playerID from the list of connected playerIDs if the playerID value exists within the list.

If the playerID was not in the list of connected playerIDs, the call will return false, and the connected playerID list will be left unchanged.

Alpha().SetPlayerCapacity(count)

Update the GameServer.Status.Players.Capacity value with a new capacity.

Alpha().GetPlayerCapacity()

This function retrieves the current player capacity. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Alpha().GetPlayerCount()

This function retrieves the current player count. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Alpha().IsPlayerConnected(playerID)

This function returns if the playerID is currently connected to the GameServer. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Alpha().GetConnectedPlayers()

This function returns the list of the currently connected player ids. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Writing your own SDK

If there isn’t an SDK for the language and platform you are looking for, you have several options:

gRPC Client Generation

If client generation is well supported by gRPC, then generate client(s) from the proto files found in the proto/sdk, directory and look at the current sdks to see how the wrappers are implemented to make interaction with the SDK server simpler for the user.

REST API Implementation

If client generation is not well supported by gRPC, or if there are other complicating factors, implement the SDK through the REST HTTP+JSON interface. This could be written by hand, or potentially generated from the Swagger/OpenAPI Specifications.

Finally, if you build something that would be usable by the community, please submit a pull request!

SDK Conformance Test

There is a tool SDK server Conformance checker which will run Local SDK server and record all requests your client is performing.

In order to check that SDK is working properly you should write simple SDK test client which would use all methods of your SDK.

Also to test that SDK client is receiving valid Gameserver data, your binary should set the same Label value as creation timestamp which you will receive as a result of GameServer() call and Annotation value same as gameserver UID received by Watch gameserver callback.

Complete list of endpoints which should be called by your test client is the following:

ready,allocate,setlabel,setannotation,gameserver,health,shutdown,watch

In order to run this test SDK server locally use:

SECONDS=30 make run-sdk-conformance-local

Docker container would timeout in 30 seconds and give your the comparison of received requests and expected requests

For instance you could run Go SDK conformance test and see how the process goes:

SDK_FOLDER=go make run-sdk-conformance-test

In order to add test client for your SDK, write sdktest.sh and Dockerfile. Refer to Golang SDK Conformance testing directory structure.

Building the Tools

If you wish to build the binaries from source the make target build-agones-sdk-binary will compile the necessary binaries for all supported operating systems (64 bit windows, linux and osx).

You can find the binaries in the bin folder in `cmd/sdk-server` once compilation is complete.

See Developing, Testing and Building Code Blind for more details.

3.1 - Unreal Engine Game Server Client Plugin

This is the Unreal Engine Code Blind Game Server Client Plugin.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGameServer✔️
ConfigurationWatch✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Additional methods have been added for ease of use (both of which are enabled by default):

  • Connect
    • will call /gameserver till a succesful response is returned and then call /ready.
    • disabled by setting bDisableAutoConnect to true.
    • An event is broadcast with the GameServer data once the /gameserver call succeeds.
  • Health
    • calls /health endpoint on supplied rate
    • enabled by default with 10 second rate
    • disabled by default by setting HealthRateSeconds to 0.

Both of the above are automatically kicked off in the BeginPlay of the component.

Download

Download the source from the Releases Page or directly from GitHub.

Resources

Unreal is a game engine that is used by anyone from hobbyists all the way through to huge AAA Game Stuidos.

With this in mind there is a vast amount to learn to run a production game using Unreal, even before you get to learning how it integrates with Code Blind. If you want to kick the tires with a starter project you will probably be fine with one of the starter projects out of the box.

However as your Unreal/Code Blind project gets more advanced you will want to understand more about the engine itself and how it can be used to integrate with this project. There will be different ways of interacting via in Play In Editor (PIE) versus running as an actual dedicated game server packaged into a container.

There are few helpful links for latest Unreal Engine 5:

If you use Unreal Engine 4, There are few helpful links for it:

Getting Started

This is a SDK inspired by the REST API to the Code Blind sidecars that allows engineers to communicate with the sidecar from either C++ or Blueprints.

Getting the Code

Easiest way to get this code is to clone the repository and drop the entire plugin folder into your own Plugins folder. This runs the plugin as a Project plugin rather than an engine plugin.

We could however turn this into a marketplace plugin that can be retrived from the marketplace directly into the UE editor.

Using C++ (UE5/UE4)

  • Add Plugin (in your own .uproject file)
  "Plugins": [
    {
      "Enabled": true,
      "Name": "Code Blind"
    }
  ],
  • Add Plugin (in your own *.Build.cs)
PublicDependencyModuleNames.AddRange(
    new[]
    {
        "Code Blind",
    });
  • Add component in header
#include "AgonesComponent.h"

UPROPERTY(EditAnywhere, BlueprintReadWrite)
UAgonesComponent* AgonesSDK;
  • Initialize component in GameMode
#include "AgonesComponent.h"
#include "Classes.h"

ATestGameMode::ATestGameMode()
{
	AgonesSDK = CreateDefaultSubobject<UAgonesComponent>(TEXT("AgonesSDK"));
}
  • Use the Code Blind component to call PlayerReady
void APlatformGameSession::PostLogin(APlayerController* NewPlayer)
{
  // Empty brances are for callbacks on success and errror.
  AgonesSDK->PlayerConnect("netspeak-player", {}, {});
}

Using Blueprints (UE5)

  • Add Component to your Blueprint GameMode component

  • This will automatically call /health every 10 seconds and once /gameserver calls are succesful it will call /ready.

  • Accessing other functionality of Code Blind can be done via adding a node in Blueprints. actions

Using Blueprints (UE4)

  • Add Component to your Blueprint GameMode component

  • This will automatically call /health every 10 seconds and once /gameserver calls are succesful it will call /ready.

  • Accessing other functionality of Code Blind can be done via adding a node in Blueprints. actions

Configuration Options

A number of options can be altered via config files in Unreal these are supplied via Game configuration eg. DefaultGame.ini.

[/Script/Code Blind.AgonesComponent]
HttpPort=1337
HealthRateSeconds=5.0
bDisableAutoConnect=true

Unreal Hooks

Within the Unreal GameMode and GameSession exist a number of useful existing funtions that can be used to fit in with making calls out to Code Blind.

A few examples are:

  • RegisterServer to call SetLabel, SetPlayerCapacity
  • PostLogin to call PlayerConnect
  • NotifyLogout to call PlayerDisconnect

3.2 - Unity Game Server Client SDK

This is the Unity version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGameServer✔️
ConfigurationWatch✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Additional methods have been added for ease of use:

  • Connect

Installation

The client SDK code can be manually downloaded and added to your project hierarchy.

It can also be imported into your project via the Unity Package Manager (UPM). To do that, open your project’s manifest.json file, and add the following line to the dependencies section:

{
  "dependencies": {
        "com.googleforgames.agones": "https://github.com/googleforgames/agones.git?path=/sdks/unity",
...

If you want a specific release, the dependency can be pinned to that version. For example:

"com.googleforgames.agones": "https://github.com/googleforgames/agones.git?path=/sdks/unity#v1.38.0",

Download

Download the source directly from GitHub.

Prerequisites

  • Unity >= 2018.x (.NET 4.x)

Usage

Import this script to your unity project and attach it to GameObject.

To begin working with the SDK, get an instance of it.

var agones = agonesGameObject.GetComponent<Code Blind.AgonesSdk>();

To connect to the SDK server, either local or when running on Code Blind, run the async Connect() method. This will wait for up to 30 seconds if the SDK server has not yet started and the connection cannot be made, and will return false if there was an issue connecting.

bool ok = await agones.Connect();

To mark the game server as ready to receive player connections, call the async method Ready().

async void SomeMethod()
{
    bool ok = await agones.Ready();
}

To get the details on the backing GameServer call GameServer().

Will return null if there is an error in retrieving the GameServer record.

var gameserver = await agones.GameServer();

To mark the GameServer as Reserved for a duration call Reserve(TimeSpan duration).

ok = await agones.Reserve(duration);

To mark that the game session is completed and the game server should be shut down call Shutdown().

bool ok = await agones.Shutdown();

Similarly SetAnnotation(string key, string value) and SetLabel(string key, string value) are async methods that perform an action.

And there is no need to call Health(), it is automatically called.

To watch when the backing GameServer configuration changes call WatchGameServer(callback), where the delegate function callback will be executed every time the GameServer configuration changes.

agones.WatchGameServer(gameServer => Debug.Log($"Server - Watch {gameServer}"));

Player Tracking

To use alpha features use the AgonesAlphaSDK class.

var agones = agonesGameObject.GetComponent<Code Blind.AgonesAlphaSdk>();

Alpha: PlayerConnect

This method increases the SDK’s stored player count by one, and appends this playerID to GameServer.Status.Players.IDs. Returns true and adds the playerID to the list of playerIDs if the playerIDs was not already in the list of connected playerIDs.

bool ok = await agones.PlayerConnect(playerId);

Alpha: PlayerDisconnect

This function decreases the SDK’s stored player count by one, and removes the playerID from GameServer.Status.Players.IDs. Will return true and remove the supplied playerID from the list of connected playerIDs if the playerID value exists within the list.

bool ok = await agones.PlayerDisconnect(playerId);

Alpha: SetPlayerCapacity

Update the GameServer.Status.Players.Capacity value with a new capacity.

var capacity = 100;
bool ok = await agones.SetPlayerCapacity(capacity);

Alpha: GetPlayerCapacity

This function retrieves the current player capacity GameServer.Status.Players.Capacity. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

long capacity = await agones.GetPlayerCapacity();

Alpha: GetPlayerCount

Returns the current player count

long count = await agones.GetPlayerCount();

Alpha: IsPlayerConnected

This returns if the playerID is currently connected to the GameServer. This is always accurate, even if the value hasn’t been updated to the GameServer status yet.

bool isConnected = await agones.IsPlayerConnected(playerId);

Alpha: GetConnectedPlayers

This returns a list of the playerIDs that are currently connected to the GameServer.

List<string> players = await agones.GetConnectedPlayers();

Settings

The properties for the Unity Code Blind SDK can be found in the Inspector.

  • Health Interval Second
    • Interval of the server sending a health ping to the Code Blind sidecar. (default: 5)
  • Health Enabled
    • Whether the server sends a health ping to the Code Blind sidecar. (default: true)
  • Log Enabled
    • Debug Logging Enabled. Debug logging for development of this Plugin. (default: false)

3.3 - C++ Game Server Client SDK

This is the C++ version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGameServer✔️
ConfigurationWatchGameServer✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues

Installation

Download

Download the source from the Releases Page or directly from GitHub.

Building the Libraries from source

CMake is used to build SDK for all supported platforms (Linux/Window/macOS).

Prerequisites

  • CMake >= 3.15.0
  • Git
  • C++17 compiler

Dependencies

Code Blind SDK only depends on the gRPC library.

If CMake cannot find gRPC with find_package(), it downloads and builds gRPC. There are some extra prerequisites for OpenSSL on Windows, see documentation:

  • Perl
  • NASM

Note that OpenSSL is not used in Code Blind SDK, but it is required to have a successful build of gRPC.

Options

Following options are available:

  • AGONES_THIRDPARTY_INSTALL_PATH (default is CMAKE_INSTALL_PREFIX) - installation path for Code Blind prerequisites (used only if gRPC and Protobuf are not found by find_package)
  • AGONES_ZLIB_STATIC (default is ON) - use static version of zlib for gRPC

(Windows only):

  • AGONES_BUILD_THIRDPARTY_DEBUG (default is OFF) - build both debug and release versions of SDK’s prerequisites. Option is not used if you already have built gRPC.
  • AGONES_OPENSSL_CONFIG_STRING (default is VC-WIN64A) - arguments to configure OpenSSL build (documentation). Used only if OpenSSL and gRPC is built by Code Blind.

Linux / MacOS

mkdir -p .build
cd .build
cmake .. -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=./install
cmake --build . --target install

Windows

Building with Visual Studio:

md .build
cd .build
cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=./install
cmake --build . --config Release --target install

Building with NMake

md .build
cd .build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./install
cmake --build . --target install

CMAKE_INSTALL_PREFIX may be skipped if it is OK to install Code Blind SDK to a default location (usually /usr/local or c:/Program Files/Code Blind).

CMake option -Wno-dev is specified to suppress CMP0048 deprecation warning for gRPC build.

If AGONES_ZLIB_STATIC is set to OFF, ensure that you have installed zlib. For Windows, it’s enough to copy zlib.dll near to gameserver executable. For Linux/Mac usually no actions are needed.

Usage

Using SDK

In CMake-based projects it’s enough to specify a folder where SDK is installed with CMAKE_PREFIX_PATH and use find_package(agones CONFIG REQUIRED) command. For example: cpp-simple. It may be useful to disable some protobuf warnings in your project.

Usage

The C++ SDK is specifically designed to be as simple as possible, and deliberately doesn’t include any kind of singleton management, or threading/asynchronous processing to allow developers to manage these aspects as they deem appropriate for their system.

We may consider these types of features in the future, depending on demand.

To begin working with the SDK, create an instance of it:

agones::SDK *sdk = new agones::SDK();

To connect to the SDK server, either local or when running on Code Blind, run the sdk->Connect() method. This will block for up to 30 seconds if the SDK server has not yet started and the connection cannot be made, and will return false if there was an issue connecting.

bool ok = sdk->Connect();

To send a health check call sdk->Health(). This is a synchronous request that will return false if it has failed in any way. Read GameServer Health Checking for more details on the game server health checking strategy.

bool ok = sdk->Health();

To mark the game server as ready to receive player connections, call sdk->Ready(). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

grpc::Status status = sdk->Ready();
if (!status.ok()) { ... }

To mark the game server as allocated, call sdk->Allocate(). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

grpc::Status status = sdk->Allocate();
if (!status.ok()) { ... }

To mark the game server as reserved, call sdk->Reserve(seconds). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

grpc::Status status = sdk->Reserve(std::chrono::seconds(N));
if (!status.ok()) { ... }

To mark that the game session is completed and the game server should be shut down call sdk->Shutdown(). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

grpc::Status status = sdk->Shutdown();
if (!status.ok()) { ... }

To set a Label on the backing GameServer call sdk->SetLabel(key, value). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

grpc::Status status = sdk->SetLabel("test-label", "test-value");
if (!status.ok()) { ... }

To set an Annotation on the backing GameServer call sdk->SetAnnotation(key, value). This will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

For more information you can also look at the gRPC Status reference.

status = sdk->SetAnnotation("test-annotation", "test value");
if (!status.ok()) { ... }

To get the details on the backing GameServer call sdk->GameServer(&gameserver), passing in a agones::dev::sdk::GameServer* to push the results of the GameServer configuration into.

This function will return a grpc::Status object, from which we can call status.ok() to determine if the function completed successfully.

agones::dev::sdk::GameServer gameserver;
grpc::Status status = sdk->GameServer(&gameserver);
if (!status.ok()) {...}

To get updates on the backing GameServer as they happen, call sdk->WatchGameServer([](const agones::dev::sdk::GameServer& gameserver){...}).

This will call the passed in std::function synchronously (this is a blocking function, so you may want to run it in its own thread) whenever the backing GameServer is updated.

sdk->WatchGameServer([](const agones::dev::sdk::GameServer& gameserver){
  std::cout << "GameServer Update:\n"                                 //
            << "\tname: " << gameserver.object_meta().name() << "\n"  //
            << "\tstate: " << gameserver.status().state() << "\n"
            << std::flush;
});

Next Steps

3.4 - Go Game Server Client SDK

This is the Go version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGameServer✔️
ConfigurationWatch✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount✔️
CountersSetCounterCount✔️
CountersIncrementCounter✔️
CountersDecrementCounter✔️
CountersSetCounterCapacity✔️
CountersGetCounterCapacity✔️
ListsAppendListValue✔️
ListsDeleteListValue✔️
ListsSetListCapacity✔️
ListsGetListCapacity✔️
ListsListContains✔️
ListsGetListLength✔️
ListsGetListValues✔️
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Installation

go get the source, directly from GitHub

Usage

Review the GoDoc for usage instructions

3.5 - C# Game Server Client SDK

This is the C# version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGetGameServer✔️
ConfigurationWatchGameServer✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Download

Download the source directly from GitHub.

Install using NuGet

  • Download the nuget package directly
  • Install the latest version using the Package Manager: Install-Package AgonesSDK
  • Install the latest version using the .NET CLI: dotnet add package AgonesSDK

To select a specific version, append --version, for example: --version 1.8.0 to either commands.

Prerequisites

  • .Net Standard 2.0 compliant framework.

Usage

Reference the SDK in your project & create a new instance of the SDK wrapper:

Initialization

To use the AgonesSDK, you will need to import the namespace by adding using Code Blind; at the beginning of your relevant files.

var agones = new AgonesSDK();

Connection

To connect to the SDK server, either locally or when running on Code Blind, run the ConnectAsync() method. This will wait for up to 30 seconds if the SDK server has not yet started and the connection cannot be made, and will return false if there was an issue connecting.

bool ok = await agones.ConnectAsync();

Ready

To mark the game server as ready to receive player connections, call the async method ReadyAsync().

async void SomeMethod()
{
    var status = await agones.ReadyAsync();
}

Health

To send Health pings, call the async method HealthAsync()

await agones.HealthAsync();

GetGameServer

To get the details on the backing GameServer call GetGameServerAsync().

Will return null if there is an error in retrieving the GameServer record.

var gameserver = await agones.GetGameServerAsync();

Reserve

To mark the GameServer as Reserved for a duration call ReserveAsync(long duration).

long duration = 30;
var status = await agones.ReserveAsync(duration);

ShutDown

To mark that the game session is completed and the game server should be shut down call ShutdownAsync().

var status = await agones.ShutdownAsync();

SetAnnotation & SetLabel

Similarly SetAnnotation(string key, string value) and SetLabel(string key, string value) are async methods that perform an action & return a Status object.

WatchGameServer

To watch when the backing GameServer configuration changes call WatchGameServer(callback), where the delegate function callback of type Action<GameServer> will be executed every time the GameServer configuration changes. This process is non-blocking internally.

agonesSDK.WatchGameServer((gameServer) => { Console.WriteLine($"Server - Watch {gameServer}");});

Player Tracking

Alpha: PlayerConnect

This method increases the SDK’s stored player count by one, and appends this playerID to GameServer.Status.Players.IDs. Returns true and adds the playerID to the list of playerIDs if the playerIDs was not already in the list of connected playerIDs.

bool ok = await agones.Alpha().PlayerConnectAsync(playerId);

Alpha: PlayerDisconnect

This function decreases the SDK’s stored player count by one, and removes the playerID from GameServer.Status.Players.IDs. Will return true and remove the supplied playerID from the list of connected playerIDs if the playerID value exists within the list.

bool ok = await agones.Alpha().PlayerDisconnectAsync(playerId);

Alpha: SetPlayerCapacity

Update the GameServer.Status.Players.Capacity value with a new capacity.

var capacity = 100;
var status = await agones.Alpha().SetPlayerCapacityAsync(capacity);

Alpha: GetPlayerCapacity

This function retrieves the current player capacity GameServer.Status.Players.Capacity. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

long cap = await agones.Alpha().GetPlayerCapacityAsync();

Alpha: GetPlayerCount

Returns the current player count

long count = await agones.Alpha().GetPlayerCountAsync();

Alpha: IsPlayerConnected

This returns if the playerID is currently connected to the GameServer. This is always accurate, even if the value hasn’t been updated to the GameServer status yet.

var playerId = "player1";
bool isConnected = await agones.Alpha().IsPlayerConnectedAsync(playerId);

Remarks

  • All requests other than ConnectAsync will wait for up to 15 seconds before giving up, time to wait can also be set in the constructor.
  • Default host & port are localhost:9357
  • Methods that do not return a data object such as GameServer will return a gRPC Grpc.Core.Status object. To check the state of the request, check Status.StatusCode & Status.Detail. Ex:
if(status.StatusCode == StatusCode.OK)
    //do stuff

3.6 - Node.js Game Server Client SDK

This is the Node.js version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGetGameServer✔️
ConfigurationWatchGameServer✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Prerequisites

  • Node.js >= 10.13.0

Usage

Add the agones dependency to your project:

npm install @google-cloud/agones-sdk

If you need to download the source, rather than install from NPM, you can find it on GitHub.

To begin working with the SDK, create an instance of it.

const AgonesSDK = require('@google-cloud/agones-sdk');

let agonesSDK = new AgonesSDK();

To connect to the SDK server, either local or when running on Code Blind, run the async method sdk.connect(), which will resolve once connected or reject on error or if no connection can be made after 30 seconds.

await agonesSDK.connect();

To send a health check ping call health(errorCallback). The error callback is optional and if provided will receive an error whenever emitted from the health check stream.

agonesSDK.health((error) => {
	console.error('error', error);
});

To mark the game server as ready to receive player connections, call the async method ready(). The result will be an empty object in this case.

let result = await agonesSDK.ready();

Similarly shutdown(), allocate(), setAnnotation(key, value) and setLabel(key, value) are async methods that perform an action and return an empty result.

To get details of the backing GameServer call the async method getGameServer(). The result will be an object representing GameServer defined in `sdk.proto`.

let result = await agonesSDK.getGameServer();

To get updates on the backing GameServer as they happen, call watchGameServer(callback, errorCallback). The callback will be called with a parameter matching the result of getGameServer(). The error callback is optional and if provided will receive an error whenever emitted from the watch stream.

agonesSDK.watchGameServer((result) => {
	console.log('watch', result);
}, (error) => {
	console.error('error', error);
});

To mark the game server as reserved for a period of time, call the async method reserve(seconds). The result will be an empty object.

For more information, please read the SDK Overview, check out agonesSDK.js and also look at the Node.js example.

3.7 - Rust Game Server Client SDK

This is the Rust version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGameServer✔️
ConfigurationWatch✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounterCount
CountersSetCounterCount
CountersIncrementCounter
CountersDecrementCounter
CountersSetCounterCapacity
CountersGetCounterCapacity
ListsAppendListValue
ListsDeleteListValue
ListsSetListCapacity
ListsGetListCapacity
ListsListContains
ListsGetListLength
ListsGetListValues
Player TrackingGetConnectedPlayers✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingGetPlayerCount✔️
Player TrackingIsPlayerConnected✔️
Player TrackingPlayerConnect✔️
Player TrackingPlayerDisconnect✔️
Player TrackingSetPlayerCapacity✔️

Prerequisites

Usage

Add this crate to dependencies section in your Cargo.toml.

Also note that the SDK is async only, so you will need an async runtime to execute the futures exposed by the SDK. It is recommended to use tokio as the SDK already depends on tokio due to its choice of gRPC library, tonic.

[dependencies]
agones = "1.34.0"
tokio = { version = "1.32.0", features = ["macros", "sync"] }

To begin working with the SDK, create an instance of it.

use std::time::Duration;

#[tokio::main]
async fn main() {
    let mut sdk = agones::Sdk::new(None /* default port */, None /* keep_alive */)
        .await
        .expect("failed to connect to SDK server");
}

To send health checks, call sdk.health_check, which will return a tokio::sync::mpsc::Sender::<()> which will send a health check every time a message is posted to the channel.

let health = sdk.health_check();
if health.send(()).await.is_err() {
    eprintln!("the health receiver was closed");
}

To mark the game session as ready call sdk.ready().

sdk.ready().await?;

To mark the game server as reserved for a period of time, call sdk.reserve(duration).

sdk.reserve(Duration::new(5, 0)).await?;

To mark that the game session is completed and the game server should be shut down call sdk.shutdown().

if let Err(e) = sdk.shutdown().await {
    eprintln!("Could not run Shutdown: {}", e);
}

To set a Label on the backing GameServer call sdk.set_label(key, value).

sdk.set_label("test-label", "test-value").await?;

To set an Annotation on the backing GameServer call sdk.set_annotation(key, value).

sdk.set_annotation("test-annotation", "test value").await?;

To get details of the backing GameServer call sdk.get_gameserver().

The function will return an instance of agones::types::GameServer including GameServer configuration info.

let gameserver = sdk.get_gameserver().await?;

To get updates on the backing GameServer as they happen, call sdk.watch_gameserver.

This will stream updates and endlessly until the stream is closed, so it is recommended to push this into its own async task.

let _watch = {
    // We need to clone the SDK as we are moving it to another task
    let mut watch_client = sdk.clone();
    // We use a simple oneshot to signal to the task when we want it to shutdown
    // and stop watching the gameserver update stream
    let (tx, mut rx) = tokio::sync::oneshot::channel::<()>();

    tokio::task::spawn(async move {
        println!("Starting to watch GameServer updates...");
        match watch_client.watch_gameserver().await {
            Err(e) => eprintln!("Failed to watch for GameServer updates: {}", e),
            Ok(mut stream) => loop {
                tokio::select! {
                    // We've received a new update, or the stream is shutting down
                    gs = stream.message() => {
                        match gs {
                            Ok(Some(gs)) => {
                                println!("GameServer Update, name: {}", gs.object_meta.unwrap().name);
                                println!("GameServer Update, state: {}", gs.status.unwrap().state);
                            }
                            Ok(None) => {
                                println!("Server closed the GameServer watch stream");
                                break;
                            }
                            Err(e) => {
                                eprintln!("GameServer Update stream encountered an error: {}", e);
                            }
                        }

                    }
                    // The watch is being dropped so we're going to shutdown the task
                    // and the watch stream
                    _ = &mut rx => {
                        println!("Shutting down GameServer watch loop");
                        break;
                    }
                }
            },
        }
    });

    tx
};

For more information, please read the SDK Overview, check out agones sdk implementation and also look at the Rust example.

3.8 - REST Game Server Client API

This is the REST version of the Code Blind Game Server Client SDK.

Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.

SDK Functionality

AreaActionImplemented
LifecycleReady✔️
LifecycleHealth✔️
LifecycleReserve✔️
LifecycleAllocate✔️
LifecycleShutdown✔️
ConfigurationGetGameServer✔️
ConfigurationWatchGameServer✔️
MetadataSetAnnotation✔️
MetadataSetLabel✔️
CountersGetCounter✔️
CountersUpdateCounter✔️
ListsGetList✔️
ListsUpdateList✔️
ListsAddListValue✔️
ListsRemoveListValue✔️
Player TrackingGetPlayerCapacity✔️
Player TrackingSetPlayerCapacity✔️
Player TrackingPlayerConnect✔️
Player TrackingGetConnectedPlayers✔️
Player TrackingIsPlayerConnected✔️
Player TrackingGetPlayerCount✔️
Player TrackingPlayerDisconnect✔️

The REST API can be accessed from http://localhost:${AGONES_SDK_HTTP_PORT}/ from the game server process. AGONES_SDK_HTTP_PORT is an environment variable automatically set for the game server process by Code Blind to support binding the REST API to a dynamic port. It is advised to use the environment variable rather than a hard coded port; otherwise your game server will not be able to contact the SDK server if it is configured to use a non-default port.

Generally the REST interface gets used if gRPC isn’t well supported for a given language or platform.

Generating clients

While you can hand write REST integrations, we also have a set of generated OpenAPI/Swagger definitions available. This means you can use OpenAPI/Swagger tooling to generate clients as well, if you need them.

For example, to create a cpp client for the stable sdk endpoints (to be run in the agones home directory):

docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i /local/sdks/swagger/sdk.swagger.json  -l cpprest -o /local/out/cpp

The same could be run for alpha.swagger.json and beta.swagger.json as required.

You can read more about OpenAPI/Swagger code generation in their Command Line Tool Documentation

Reference

Lifecycle Management

Ready

Call when the GameServer is ready to accept connections

  • Path: /ready
  • Method: POST
  • Body: {}
Example
curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/ready

Health

Send a Empty every d Duration to declare that this GameServer is healthy

  • Path: /health
  • Method: POST
  • Body: {}
Example
curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/health

Reserve

Move Gameserver into a Reserved state for a certain amount of seconds for the future allocation.

  • Path: /reserve
  • Method: POST
  • Body: {"seconds": "5"}
Example
curl -d '{"seconds": "5"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/reserve

Allocate

With some matchmakers and game matching strategies, it can be important for game servers to mark themselves as Allocated. For those scenarios, this SDK functionality exists.

Example
curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/allocate

Shutdown

Call when the GameServer session is over and it’s time to shut down

  • Path: /shutdown
  • Method: POST
  • Body: {}
Example
curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/shutdown

Configuration Retrieval

GameServer

Call when you want to retrieve the backing GameServer configuration details

  • Path: /gameserver
  • Method: GET
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/gameserver

Response:

{
    "object_meta": {
        "name": "local",
        "namespace": "default",
        "uid": "1234",
        "resource_version": "v1",
        "generation": "1",
        "creation_timestamp": "1531795395",
        "annotations": {
            "annotation": "true"
        },
        "labels": {
            "islocal": "true"
        }
    },
    "status": {
        "state": "Ready",
        "address": "127.0.0.1",
        "ports": [
            {
                "name": "default",
                "port": 7777
            }
        ]
    }
}

Watch GameServer

Call this when you want to get updates of when the backing GameServer configuration is updated.

These updates will come as newline delimited JSON, send on each update. To that end, you will want to keep the http connection open, and read lines from the result stream and and process as they come in.

curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/watch/gameserver

Response:

{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}}
{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}}
{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}}

The Watch GameServer stream is also exposed as a WebSocket endpoint on the same URL and port as the HTTP watch/gameserver API. This endpoint is provided as a convienence for streaming data to clients such as Unreal that support WebSocket but not HTTP streaming, and HTTP streaming should be used instead if possible.

An example command that uses the WebSocket endpoint instead of streaming over HTTP is:

curl -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: ExampleKey1234567890===" -H "Sec-WebSocket-Version: 13" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/watch/gameserver

The data returned from this endpoint is delimited by the boundaries of a WebSocket payload as defined by RFC 6455, section 5.2. When reading from this endpoint, if your WebSocket client does not automatically handle frame reassembly (e.g. Unreal), make sure to read to the end of the WebSocket payload (as defined by the FIN bit) before attempting to parse the data returned. This is transparent in most clients.

Metadata Management

Set Label

Apply a Label with the prefix “agones.dev/sdk-” to the backing GameServer metadata.

See the SDK SetLabel documentation for restrictions.

Example
curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:${AGONES_SDK_HTTP_PORT}/metadata/label

Set Annotation

Apply an Annotation with the prefix “agones.dev/sdk-” to the backing GameServer metadata

Example
curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:${AGONES_SDK_HTTP_PORT}/metadata/annotation

Counters and Lists

Counters

In all the Counter examples, we retrieve the counter under the key rooms as if it was previously defined in GameServer.Spec.counters[room].

For your own Counter REST requests, replace the value rooms with your own key in the path.

Alpha: GetCounter

This function retrieves a specified counter by its key, rooms, and returns its information.

Example
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/counters/rooms

Response:

{"name":"rooms", "count":"1", "capacity":"10"}
Alpha: UpdateCounter

This function updates the properties of the counter with the key rooms, such as its count and capacity, and returns the updated counter details.

Example
curl -d '{"count": "5", "capacity": "11"}' -H "Content-Type: application/json" -X PATCH http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/counters/rooms

Response:

{"name":"rooms", "count":"5", "capacity":"11"}

Lists

In all the List examples, we retrieve the list under the key players as if it was previously defined in GameServer.Spec.lists[players].

For your own List REST based requests, replace the value players with your own key in the path.

Alpha: GetList

This function retrieves the list’s properties with the key players, returns the list’s information.

Example
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/lists/players

Response:

{"name":"players", "capacity":"100", "values":["player0", "player1", "player2"]}     
Alpha: UpdateList

This function updates the list’s properties with the key players, such as its capacity and values, returns the updated list details. This will overwrite all existing List.Values with the update list request values. Use addValue or removeValue for modifying the List.Values field.

Example
curl -d '{"capacity": "120", "values": ["player3", "player4"]}' -H "Content-Type: application/json" -X PATCH http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/lists/players

Response:

{"name":"players", "capacity":"120", "values":["player3", "player4"]}
Alpha: AddListValue

This function adds a new value to a list with the key players and returns the list with this addition.

Example
curl -d '{"value": "player9"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/lists/players:addValue

Response:

{"name":"players", "capacity":"120", "values":["player3", "player4", "player9"]}
Alpha: RemoveListValue

This function removes a value from the list with the key players and returns updated list.

Example
curl -d '{"value": "player3"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/v1alpha1/lists/players:removeValue

Response:

{"name":"players", "capacity":"120", "values":["player4", "player9"]}

Player Tracking

Alpha: PlayerConnect

This function increases the SDK’s stored player count by one, and appends this playerID to GameServer.Status.Players.IDs.

Example
curl -d '{"playerID": "uzh7i"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/connect

Response:

{"bool":true}

Alpha: PlayerDisconnect

This function decreases the SDK’s stored player count by one, and removes the playerID from GameServer.Status.Players.IDs.

Example
curl -d '{"playerID": "uzh7i"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/disconnect

Response:

{"bool":true}

Alpha: SetPlayerCapacity

Update the GameServer.Status.Players.Capacity value with a new capacity.

Example
curl -d '{"count": 5}' -H "Content-Type: application/json" -X PUT http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/capacity

Alpha: GetPlayerCapacity

This function retrieves the current player capacity. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Example
curl -d '{}' -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/capacity

Response:

{"count":"5"}

Alpha: GetPlayerCount

This function retrieves the current player count. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Example
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/count

Response:

{"count":"2"}

Alpha: IsPlayerConnected

This function returns if the playerID is currently connected to the GameServer. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Example
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/connected/uzh7i

Response:

{"bool":true}

Alpha: GetConnectedPlayers

This function returns the list of the currently connected player ids. This is always accurate from what has been set through this SDK, even if the value has yet to be updated on the GameServer status resource.

Example
curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/alpha/player/connected

Response:

{"list":["uzh7i","3zh7i"]}

3.9 - Local Development

Working against the SDK without having to run a full kubernetes stack

When the game server is running on Code Blind, the SDK communicates over TCP to a small gRPC server that Code Blind coordinated to run in a container in the same network namespace as it - usually referred to in Kubernetes terms as a “sidecar”.

Therefore, when developing locally, we also need a process for the SDK to connect to!

To do this, we can run the same binary (the SDK Server) that runs inside Code Blind, but pass in a flag to run it in “local mode”. Local mode means that the sidecar binary will not try to connect to anything, and will just send log messages to stdout and persist local state in memory so that you can see exactly what the SDK in your game server is doing, and can confirm everything works.

Running the SDK Server

To run the SDK Server, you will need a copy of the binary. This can either be done by downloading a prebuilt binary or running from source code. This guide will focus on running from the prebuilt binary, but details about running from source code can be found below.

To run the prebuilt binary, for the latest release, you will need to download agonessdk-server-1.38.0.zip , and unzip it. You will find the executables for the SDK server, for each type of operating system.

  • MacOS
    • sdk-server.darwin.amd64
    • sdk-server.darwin.arm64
  • Linux
    • sdk-server.linux.amd64
    • sdk-server.linux.arm64
  • Windows
    • sdk-server.windows.amd64.exe

Running In “Local Mode”

To run in local mode, pass the flag --local to the executable.

For example:

./sdk-server.linux.amd64 --local

You should see output similar to the following:

{"ctlConf":{"Address":"localhost","IsLocal":true,"LocalFile":"","Delay":0,"Timeout":0,"Test":"","GRPCPort":9357,"HTTPPort":9358},"message":"Starting sdk sidecar","severity":"info","source":"main","time":"2019-10-30T21:44:37.973139+03:00","version":"1.1.0"}
{"grpcEndpoint":"localhost:9357","message":"Starting SDKServer grpc service...","severity":"info","source":"main","time":"2019-10-30T21:44:37.974585+03:00"}
{"httpEndpoint":"localhost:9358","message":"Starting SDKServer grpc-gateway...","severity":"info","source":"main","time":"2019-10-30T21:44:37.975086+03:00"}
{"message":"Ready request has been received!","severity":"info","time":"2019-10-30T21:45:47.031989+03:00"}
{"message":"gameserver update received","severity":"info","time":"2019-10-30T21:45:47.03225+03:00"}
{"message":"Shutdown request has been received!","severity":"info","time":"2019-10-30T21:46:18.179341+03:00"}
{"message":"gameserver update received","severity":"info","time":"2019-10-30T21:46:18.179459+03:00"}

Enabling Feature Gates

For development and testing purposes, you might want to enable specific features gates in the local SDK Server.

To do this, you can either set the FEATURE_GATES environment variable or use the --feature-gates command line parameter like so, with the same format as utilised when configuring it on a Helm install.

For example:

./sdk-server.linux.amd64 --local --feature-gates Example=true

or

FEATURE_GATES=Example=true ./sdk-server.linux.amd64 --local

Providing your own GameServer configuration for local development

By default, the local sdk-server will create a default GameServer configuration that is used for GameServer() and WatchGameServer() SDK calls. If you wish to provide your own configuration, as either yaml or json, this can be passed through as either --file or -f along with the --local flag.

If the GamerServer configuration file is changed while the local server is running, this will be picked up by the local server, and will change the current active configuration, as well as sending out events for WatchGameServer(). This is a useful way of testing functionality, such as changes of state from Ready to Allocated in your game server code.

For example:

wget https://raw.githubusercontent.com/googleforgames/agones/release-1.38.0/examples/simple-game-server/gameserver.yaml
./sdk-server.linux.amd64 --local -f ./gameserver.yaml
{"ctlConf":{"Address":"localhost","IsLocal":true,"LocalFile":"./gameserver.yaml","Delay":0,"Timeout":0,"Test":"","GRPCPort":9357,"HTTPPort":9358},"message":"Starting sdk sidecar","severity":"info","source":"main","time":"2019-10-30T21:47:45.742776+03:00","version":"1.1.0"}
{"filePath":"/Users/alexander.apalikov/Downloads/agonessdk-server-1.1.0/gameserver.yaml","message":"Reading GameServer configuration","severity":"info","time":"2019-10-30T21:47:45.743369+03:00"}
{"grpcEndpoint":"localhost:9357","message":"Starting SDKServer grpc service...","severity":"info","source":"main","time":"2019-10-30T21:47:45.759692+03:00"}
{"httpEndpoint":"localhost:9358","message":"Starting SDKServer grpc-gateway...","severity":"info","source":"main","time":"2019-10-30T21:47:45.760312+03:00"}

Changing State of a Local GameServer

Some SDK calls would change the GameServer state according to GameServer State Diagram. Also local SDK server would persist labels and annotations updates.

Here is a complete list of these commands: ready, allocate, setlabel, setannotation, shutdown, reserve.

For example call to Reserve() for 30 seconds would change the GameServer state to Reserve and if no call to Allocate() occurs it would return back to Ready state after this period.

All changes to the GameServer state could be observed and retrieved using Watch() or GameServer() methods using GameServer SDK.

Example of using HTTP gateway locally:

curl -X POST "http://localhost:9358/ready" -H "accept: application/json" -H "Content-Type: application/json" -d "{}"
{}
curl -GET "http://localhost:9358/gameserver" -H "accept: application/json"
{"object_meta":{"creation_timestamp":"-62135596800"},"spec":{"health":{}},"status":{"state":"Ready"}}
curl -X PUT "http://localhost:9358/metadata/label" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"key\": \"foo\", \"value\": \"bar\"}"
curl -GET "http://localhost:9358/gameserver" -H "accept: application/json"
{"object_meta":{"creation_timestamp":"-62135596800","labels":{"agones.dev/sdk-foo":"bar"}},"spec":{"health":{}},"status":{"state":"Ready"}}

Running Local Mode in a Container

Once you have your game server process in a container, you may also want to test the container build locally as well.

Since the production agones-sdk binary has the --local mode built in, you can also use the production container image locally as well!

Since the SDK and your game server container need to share a port on localhost, one of the easiest ways to do that is to have them both run using the host network, like so:

In one shell run:

docker run --network=host --rm us-docker.pkg.dev/agones-images/release/agones-sdk:1.38.0 --local

You should see a similar output to what you would if you were running the binary directly, i.e. outside a container.

Then in another shell, start your game server container:

docker run --network=host --rm <your image here>

If you want to mount a custom gameserver.yaml, this is also possible:

wget https://raw.githubusercontent.com/googleforgames/agones/release-1.38.0/examples/simple-game-server/gameserver.yaml
# required so that the `agones` user in the container can read the file
chmod o+r gameserver.yaml
docker run --network=host --rm -v $(pwd)/gameserver.yaml:/tmp/gameserver.yaml us-docker.pkg.dev/agones-images/release/agones-sdk:1.38.0 --local -f /tmp/gameserver.yaml

If you run Docker on a OS that doesn’t run Docker natively or in a VM, such as on Windows or macOS, you may want to to run the ClientSDK and your game server container together with Docker Compose. To do so, create a docker-compose.yaml file setup with a network overlay shared between them:

version: '3'
services:
  gameserver:
    build: . # <path to build context>
    ports:
      - "127.0.0.1:7777:7777/udp"

  sdk-server:
    image: "us-docker.pkg.dev/agones-images/release/agones-sdk:1.38.0"
    command: --local -f /gs_config
    network_mode: service:gameserver # <shared network between sdk and game server>
    configs:
      - gs_config

configs:
  gs_config:
    file: ./gameserver.yaml

Run docker-compose

docker-compose up --build

Running from source code instead of prebuilt binary

If you wish to run from source rather than pre-built binaries, that is an available alternative. You will need Go installed and will need to clone the Code Blind GitHub repo.

Disclaimer: Code Blind is run and tested with the version of Go specified by the GO_VERSION variable in the project’s build Dockerfile. Other versions are not supported, but may still work.

Your cloned repository is best switched to the latest specific release’s branch/tag. For example:

git clone https://github.com/googleforgames/agones.git
cd agones
git checkout release-1.38.0

With Go installed and the Code Blind repository cloned, the SDK Server can be run with the following command (from the Code Blind clone directory):

go run cmd/sdk-server/main.go --local

Commandline flags (e.g. --local) are exactly the same as command line flags when utilising a pre-built binary.

Next Steps:

  • Learn how to connect your local development game server binary into a running Code Blind Kubernetes cluster for even more live development options with an out of cluster dev server.

4 - Windows Gameservers

Run GameServers on Kubernetes nodes with the Windows operating system.

Prerequisites

The following prerequisites are required to create a GameServer:

  1. A Kubernetes cluster with the UDP port range 7000-8000 open on each node.
  2. Code Blind controller installed in the targeted cluster
  3. kubectl properly configured
  4. Netcat which is already installed on most Linux/macOS distributions, for windows you can use WSL.

If you don’t have a Kubernetes cluster you can follow these instructions to create a cluster on Google Kubernetes Engine (GKE), Minikube or Azure Kubernetes Service (AKS), and install Code Blind.

For the purpose of this guide we’re going to use the simple-game-server example as the GameServer container. This example is a very simple UDP server written in Go. Don’t hesitate to look at the code of this example for more information.

Ensure that you have some nodes to your cluster that are running Windows.

Objectives

  • Create a GameServer on a Windows node.
  • Connect to the GameServer.

1. Create a GameServer

Create a GameServer using the following command:

kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.38.0/examples/simple-game-server/gameserver-windows.yaml

You should see a successful output similar to this:

gameserver.agones.dev/simple-game-server-4ss4j created

Verify that the GameServer becomes Ready by running:

kubectl get gameservers

It should look something like this:

NAME                       STATE     ADDRESS       PORT   NODE     AGE
simple-game-server-7pjrq   Ready   35.233.183.43   7190   agones   3m

Take a note of the Game Server IP address and ports.

For the full details of the YAML file head to the GameServer Specification Guide

2. Connect to the GameServer

You can now communicate with the Game Server:

nc -u {IP} {PORT}
Hello World !
ACK: Hello World !
EXIT

You can finally type EXIT which tells the SDK to run the Shutdown command, and therefore shuts down the GameServer.

If you run kubectl describe gameserver again - either the GameServer will be gone completely, or it will be in Shutdown state, on the way to being deleted.

Next Steps

5 - Fleet Updates

Common patterns and approaches for updating Fleets with newer and/or different versions of your GameServer configuration.

Rolling Update Strategy

When Fleets are edited and updated, the default strategy of Code Blind is to roll the new version of the GameServer out to the entire Fleet, in a step by step increment and decrement by adding a chunk of the new version and removing a chunk of the current set of GameServers.

This is done while ensuring that Allocated GameServers are not deleted until they are specifically shutdown through the game servers SDK, as they are expected to have players on them.

You can see this in the Fleet.Spec.Strategy reference, with controls for how much of the Fleet is incremented and decremented at one time:

  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%

So when a Fleet is edited (any field other than replicas, see note below), either through kubectl edit/apply or via the Kubernetes API, this performs the following operations:

  1. Adds the maxSurge number of GameServers to the Fleet.
  2. Shutdown the maxUnavailable number of GameServers in the Fleet, skipping Allocated GameServers.
  3. Repeat above steps until all the previous GameServer configurations have been Shutdown and deleted.

By default, a Fleet will wait for new GameSevers to become Ready during a Rolling Update before continuing to shutdown additional GameServers, only counting GameServers that are Ready as being available when calculating the current maxUnavailable value which controls the rate at which GameServers are updated. This ensures that a Fleet cannot accidentally have zero GameServers in the Ready state if something goes wrong during a Rolling Update or if GameServers have a long delay before moving to the Ready state.

Recreate Strategy

This is an optimal Fleet update strategy if you want to replace all GameServers that are not Allocated with a new version as quickly as possible.

You can see this in the Fleet.Spec.Strategy reference:

  strategy:
    type: Recreate

So when a Fleet is edited, either through kubectl edit/apply or via the Kubernetes API, this performs the following operations:

  1. Shutdown all GameServers in the Fleet that are not currently Allocated.
  2. Create the same number of the new version of the GameServers that were previously deleted.
  3. Repeat above steps until all the previous GameServer configurations have been Shutdown and deleted.

Two (or more) Fleets Strategy

If you want very fine-grained control over the rate that new versions of a GameServer configuration is rolled out, or if you want to do some version of A/B testing or smoke test between different versions, running two (or more) Fleets at the same time is a good solution for this.

To do this, create a second Fleet inside your cluster, starting with zero replicas. From there you can scale this newer Fleet up and the older Fleet down as required by your specific rollout strategy.

This also allows you to rollback if issues arise with the newer version, as you can delete the newer Fleet and scale up the old Fleet to its previous levels, resulting in minimal impact to the players.

GameServerAllocation Across Fleets

Since GameServerAllocation is powered by label selectors, it is possible to allocate across multiple fleets, and/or give preference to particular sets of GameServers over others. You can see details of this in the GameServerAllocation reference.

In a scenario where a new v2 version of a Fleet is being slowly scaled up in a separate Fleet from the previous v1 Fleet, we can specify that we prefer allocation to occur from the v2 Fleet, and if none are available, fallback to the v1 Fleet, like so:

apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
  selectors:
    - matchLabels:
        agones.dev/fleet: v2
    - matchLabels:
        game: my-awesome-game
  
apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
  # Deprecated, use field selectors instead.
  required:
    matchLabels:
      game: my-awesome-game
  # Deprecated, use field selectors instead.
  preferred:
    - matchLabels:
        agones.dev/fleet: v2
  

In this example, all GameServers have the label game: my-awesome-game, so the Allocation will search across both Fleets through that mechanism. The selectors label matching selector tells the allocation system to first search all GameServers with the v2 Fleet label, and if not found, search through the rest of the set.

The above GameServerAllocation can then be used while you scale up the v2 Fleet and scale down the original Fleet at the rate that you deem fit for your specific rollout.

Notifying GameServers on Fleet Update/Downscale

When Allocated GameServers are utilised for a long time, such as a Lobby GameServer, or a GameServer that is being reused multiple times in a row, it can be useful to notify an Allocated GameServer process when its backing Fleet has been updated. When an update occurs, the Allocated GameServer, may want to actively perform a graceful shutdown and release its resources such that it can be replaced by a new version, or similar actions.

To do this, we provide the ability to apply a user-provided set of labels and/or annotations to the Allocated GameServers when a Fleet update occurs that updates its GameServer template, or generally causes the Fleet replica count to drop below the number of currently Allocated GameServers.

This provides two useful capabilities:

  1. The GameServer SDK.WatchGameServer() command can be utilised to react to this annotation and/or label change to indicate the Fleet system change, and the game server binary could execute code accordingly.
  2. This can also be used to proactively update GameServer labels, to effect change in allocation strategy - such as preferring the newer GameServers when allocating, but falling back to the older version if there aren’t enough of the new ones yet spun up.

The labels and/or annotations are applied to GameServers in a Fleet in the order designated by their configured Fleet scheduling.

Example yaml configuration:

apiVersion: "agones.dev/v1"
kind: Fleet
metadata:
  name: simple-game-server
spec:
  replicas: 2
  allocationOverflow: # This specifies which annotations and/or labels are applied
    labels:
      mykey: myvalue
      version: "" # empty an existing label value, so it's no longer in the allocation selection
    annotations:
      event: overflow
  template:
    spec:
      ports:
        - name: default
          containerPort: 7654
      template:
        spec:
          containers:
            - name: simple-game-server
              image: us-docker.pkg.dev/agones-images/examples/simple-game-server:0.27

See the Fleet reference for more details.

6 - GameServer Health Checking

Health checking exists to track the overall healthy state of the GameServer, such that action can be taken when a something goes wrong or a GameServer drops into an Unhealthy state

Disabling Health Checking

By default, health checking is enabled, but it can be turned off by setting the spec.health.disabled property to true.

SDK API

The Health() function on the SDK object needs to be called at an interval less than the spec.health.periodSeconds threshold time to be considered before it will be considered a failure.

The health check will also need to have not been called a consecutive number of times (spec.health.failureThreshold), giving it a chance to heal if it there is an issue.

Health Failure Strategy

The following is the process for what happens to a GameServer when it is unhealthy.

  1. If the GameServer container exits with an error before the GameServer moves to Ready then, it is restarted as per the restartPolicy (which defaults to “Always”).
  2. If the GameServer fails health checking at any point, then it doesn’t restart, but moves to an Unhealthy state.
  3. If the GameServer container exits while in Ready, Allocated or Reserved state, it will be restarted as per the restartPolicy (which defaults to “Always”, since RestartPolicy is a Pod wide setting), but will immediately move to an Unhealthy state.
  4. If the SDK sidecar fails, then it will be restarted, assuming the RestartPolicy is Always/OnFailure.

Fleet Management of Unhealthy GameServers

If a GameServer moves into an Unhealthy state when it is not part of a Fleet, the GameServer will remain in the Unhealthy state until explicitly deleted. This is useful for debugging Unhealthy GameServers, or if you are creating your own GameServer management layer, you can explicitly choose what to do if a GameServer becomes Unhealthy.

If a GameServer is part of a Fleet, the Fleet management system will delete any Unhealthy GameServers and immediately replace them with a brand new GameServer to ensure it has the configured number of Replicas.

Configuration Reference

  # Health checking for the running game server
  health:
    # Disable health checking. defaults to false, but can be set to true
    disabled: false
    # Number of seconds after the container has started before health check is initiated. Defaults to 5 seconds
    initialDelaySeconds: 5
    # If the `Health()` function doesn't get called at least once every period (seconds), then
    # the game server is not healthy. Defaults to 5
    periodSeconds: 5
    # Minimum consecutive failures for the health probe to be considered failed after having succeeded.
    # Defaults to 3. Minimum value is 1
    failureThreshold: 3

See the full GameServer example for more details

Example

C++

For a configuration that requires a health ping every 5 seconds, the example below sends a request every 2 seconds to be sure that the GameServer is under the threshold.

void doHealth(agones::SDK *sdk) {
    while (true) {
        if (!sdk->Health()) {
            std::cout << "Health ping failed" << std::endl;
        } else {
            std::cout << "Health ping sent" << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
}

int main() {
    agones::SDK *sdk = new agones::SDK();
    bool connected = sdk->Connect();
    if (!connected) {
        return -1;
    }
    std::thread health (doHealth, sdk);

    // ...  run the game server code

}

Full Game Server

Also look in the examples directory.

7 - Player Tracking

Track player connections, disconnections, counts and capacities through the Code Blind SDK

Managing GameServer Capacities

To track your GameServer current player capacity, Code Blind gives you the ability to both set an initial capacity at GameServer creation, as well be able to change it during the lifecycle of the GameServer through the Code Blind SDK.

To set the initial capacity, you can do so via GameServer.Spec.Players.InitialCapacity like so:

apiVersion: "agones.dev/v1"
kind: GameServer
metadata:
  name: "gs-example"
spec:
  # ...
  players:
    # set this GameServer's initial player capacity to 10
    initialCapacity: 10

From there, if you need to change the capacity of the GameSever as gameplay is in progress, you can also do so via SDK.Alpha().SetPlayerCapacity(count)

The current player capacity is represented in GameServer.Status.Players.Capacity resource value.

We can see this in action, when we look at the Status section of a GameServer resource , wherein the capacity has been set to 20:

...
Status:
  Address:    14.81.195.72
  Node Name:  gke-test-cluster-default-6cd0ba67-1mps
  Players:
    Capacity:  20
    Count:     0
    Ids:       <nil>
  Ports:
    Name:          gameport
    Port:          7983
  Reserved Until:  <nil>
  State:           Ready

From the SDK, the game server binary can also retrieve the current player capacity via SDK.Alpha().GetPlayerCapacity().

Connecting and Disconnecting Players

As players connect and disconnect from your game, the Player Tracking functions enable you to track which players are currently connected.

It assumed that each player that connects has a unique token that identifies them as a player.

When a player connects to the game server binary, calling SDK.Alpha().PlayerConnect(playerID) with the unique player token will register them as connected, and store their player id.

At disconnection time, call SDK.Alpha().PlayerDisconnect(playerID) , which will deregister them and remove their player id from the list.

Each of these playerIDs is stored on GameServer.Status.Players.IDs, and the current count of connected players can be seen in GameServer.Status.Players.Count.

You can see this in action below in the GameServer Status section, where there are 4 players connected:

...
Status:
  Address:    39.82.196.74
  Node Name:  gke-test-cluster-default-6cd0ba77-1mps
  Players:
    Capacity:  10
    Count:     4
    Ids:
      xy8a
      m0ux
      71nj
      lpq5
  Ports:
    Name:          gameport
    Port:          7166
  Reserved Until:  <nil>
  State:           Ready

Checking Player Data

Not only is the connected player data stored on the GameServer resource, it is also stored in memory within the SDK, so that it can be used from within the game server binary as a realtime, thread safe, registry of connected players.

Therefore, if you want to:

Next Steps

8 - Local Game Server

Register your local game server with Code Blind.

You can register a local game server with Code Blind. This means you can run an experimental build of your game server in the Code Blind environment without the need of packaging and deploying it to a fleet. This allows you to quickly iterate on your game server code while still being able to plugin to your Code Blind environment.

Register your server with Code Blind

To register your local game server you’ll need to know the IP address of the machine running it and the port. With that you’ll create a game server config like the one below.

apiVersion: "agones.dev/v1"
kind: GameServer
metadata:
  name: my-local-server
  annotations:
    # Causes Code Blind to register your local game server at 192.1.1.2, replace with your server's IP address.
    agones.dev/dev-address: "192.1.1.2"
spec:
  ports:
  - name: default
    portPolicy: Static
    hostPort: 17654
    containerPort: 17654
  # The following is ignored but required due to validation.
  template:
    spec:
      containers:
      - name: simple-game-server
        image: us-docker.pkg.dev/codeblind/examples/simple-server:0.27

Once you save this to a file make sure you have kubectl configured to point to your Code Blind cluster and then run kubectl apply -f dev-gameserver.yaml. This will register your server with Code Blind.

Local Game Servers has a few limitations:

  • PortPolicy must be Static.
  • The game server is not managed by Code Blind. Features like autoscaling, replication, etc are not available.

When you are finished working with your server, you can remove the registration with kubectl delete -f dev-gameserver.yaml

Next Steps:

  • Review the specification of GameServer.
  • Read about GameServer allocation.
  • Learn how to connect your local development game server binary into a running Code Blind Kubernetes cluster for even more live development options with an out of cluster dev server.

9 - Latency Testing with Multiple Clusters

When running multiple Code Blind clusters around the world, you may need to have clients determine which cluster to connect to based on latency.

To make latency testing easier, Code Blind installs with a simple ping service with both HTTP and UDP services that can be called for the purpose of timing how long the roundtrip takes for information to be returned from either of these services.

Installing

By default, Code Blind installs Kubernetes Services for both HTTP and the UDP ping endpoints. These can be disabled entirely, or disabled individually. See the Helm install guide for the parameters to pass through, as well as configuration options.

The ping services as all installed under the agones-system namespace.

HTTP Service

This exposes an endpoint that returns a simple text HTTP response on request to the root “/” path. By default this is ok, but it can be configured via the agones.ping.http.response parameter.

This could be useful for providing clusters with unique lookup names, such that clients are able to identify clusters from their responses.

To lookup the details of this service, run kubectl describe service agones-ping-http-service --namespace=agones-system

UDP Service

The UDP ping service is a rate limited UDP echo service that returns the udp packet that it receives to its designated sender.

Since UDP sender details can be spoofed, this service is rate limited to 20 requests per second, per sender address, per running instance (default is 2).

This rate limit can be raised or lowered via the Helm install parameter agones.ping.udp.rateLimit.

UDP packets are also limited to 1024 bytes in size.

To lookup the details of this service, run kubectl describe service agones-ping-udp-service --namespace=agones-system

Client side tooling

We deliberately didn’t provide any game client libraries, as all major languages and engines have capabilities to send HTTP requests as well as UDP packets.

10 - Metrics

Code Blind controller exposes metrics via OpenCensus. OpenCensus is a single distribution of libraries that collect metrics and distributed traces from your services, we only use it for metrics but it will allow us to support multiple exporters in the future.

We choose to start with Prometheus as this is the most popular with Kubernetes but it is also compatible with Cloud Monitoring. If you need another exporter, check the list of supported exporters. It should be pretty straightforward to register a new one. (GitHub PRs are more than welcome.)

We plan to support multiple exporters in the future via environment variables and helm flags.

Backend integrations

Prometheus

If you are running a Prometheus instance you just need to ensure that metrics and kubernetes service discovery are enabled. (helm chart values agones.metrics.prometheusEnabled and agones.metrics.prometheusServiceDiscovery). This will automatically add annotations required by Prometheus to discover Code Blind metrics and start collecting them. (see example)

If your Prometheus metrics collection agent requires that you scrape from the pods directly(such as with Google Cloud Managed Prometheus), then the metrics ports for the controller and allocator will both be named http and exposed on 8080. In the case of the allocator, the port name and number can be overriden with the agones.allocator.serviceMetrics.http.portName and agones.allocator.serviceMetrics.http.port helm chart values.

Prometheus Operator

If you have Prometheus operator installed in your cluster, just enable ServiceMonitor installation in values:

agones:
  metrics:
    serviceMonitor:
      enabled: true

Google Cloud Managed Service for Prometheus

Google Cloud Managed Service for Prometheus is a fully managed multi-cloud solution for Prometheus. If you wish to use Managed Prometheus with Code Blind, follow the Google Cloud Managed Service for Prometheus installation steps.

Google Cloud Monitoring (formerly Stackdriver)

We support the OpenCensus Stackdriver exporter. In order to use it you should enable Cloud Monitoring API in Google Cloud Console. Follow the Google Cloud Monitoring installation steps to see your metrics in Cloud Monitoring.

Metrics available

NameDescriptionType
agones_gameservers_countThe number of gameservers per fleet and statusgauge
agones_gameserver_allocations_duration_secondsThe distribution of gameserver allocation requests latencieshistogram
agones_gameservers_totalThe total of gameservers per fleet and statuscounter
agones_gameserver_player_connected_totalThe total number of players connected to gameservers (Only available when player tracking is enabled)gauge
agones_gameserver_player_capacity_totalThe available capacity for players on gameservers (Only available when player tracking is enabled)gauge
agones_fleets_replicas_countThe number of replicas per fleet (total, desired, ready, reserved, allocated)gauge
agones_fleet_autoscalers_able_to_scaleThe fleet autoscaler can access the fleet to scalegauge
agones_fleet_autoscalers_buffer_limitsThe limits of buffer based fleet autoscalers (min, max)gauge
agones_fleet_autoscalers_buffer_sizeThe buffer size of fleet autoscalers (count or percentage)gauge
agones_fleet_autoscalers_current_replicas_countThe current replicas count as seen by autoscalersgauge
agones_fleet_autoscalers_desired_replicas_countThe desired replicas count as seen by autoscalersgauge
agones_fleet_autoscalers_limitedThe fleet autoscaler is outside the limits set by MinReplicas and MaxReplicas.gauge
agones_gameservers_node_countThe distribution of gameservers per nodehistogram
agones_nodes_countThe count of nodes empty and with gameserversgauge
agones_gameservers_state_durationThe distribution of gameserver state duration in seconds. Note: this metric could have some missing samples by design. Do not use the _total counter as the real value for state changes.histogram
agones_k8s_client_http_request_totalThe total of HTTP requests to the Kubernetes API by status codecounter
agones_k8s_client_http_request_duration_secondsThe distribution of HTTP requests latencies to the Kubernetes API by status codehistogram
agones_k8s_client_cache_list_totalThe total number of list operations for client-go cachescounter
agones_k8s_client_cache_list_duration_secondsDuration of a Kubernetes list API call in secondshistogram
agones_k8s_client_cache_list_itemsCount of items in a list from the Kubernetes APIhistogram
agones_k8s_client_cache_watches_totalThe total number of watch operations for client-go cachescounter
agones_k8s_client_cache_last_resource_versionLast resource version from the Kubernetes APIgauge
agones_k8s_client_workqueue_depthCurrent depth of the work queuegauge
agones_k8s_client_workqueue_latency_secondsHow long an item stays in the work queuehistogram
agones_k8s_client_workqueue_items_totalTotal number of items added to the work queuecounter
agones_k8s_client_workqueue_work_duration_secondsHow long processing an item from the work queue takeshistogram
agones_k8s_client_workqueue_retries_totalTotal number of items retried to the work queuecounter
agones_k8s_client_workqueue_longest_running_processorHow long the longest running workqueue processor has been running in microsecondsgauge
agones_k8s_client_workqueue_unfinished_work_secondsHow long unfinished work has been sitting in the workqueue in secondsgauge

Dropping Metric Labels

When a Fleet or FleetAutoscaler is deleted from the system, Code Blind will automatically clear metrics that utilise their name as a label from the exported metrics, so the metrics exported do not continuously grow in size over the lifecycle of the Code Blind installation.

Dashboard

Grafana Dashboards

We provide a set of useful Grafana dashboards to monitor Code Blind workload, they are located under the grafana folder:

Dashboard screenshots :

grafana dashboard autoscalers

grafana dashboard controller

Installation

When operating a live multiplayer game you will need to observe performances, resource usage and availability to learn more about your system. This guide will explain how you can setup Prometheus and Grafana into your own Kubernetes cluster to monitor your Code Blind workload.

Before attemping this guide you should make sure you have kubectl and helm installed and configured to reach your kubernetes cluster.

Prometheus installation

Prometheus is an open source monitoring solution, we will use it to store Code Blind controller metrics and query back the data.

Let’s install Prometheus using the Prometheus Community Kubernetes Helm Charts repository.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm upgrade --install --wait prom prometheus-community/prometheus --namespace metrics --create-namespace \
    --set server.global.scrape_interval=30s \
    --set server.persistentVolume.enabled=true \
    --set server.persistentVolume.size=64Gi \
    -f ./build/prometheus.yaml

For resiliency it is recommended to run Prometheus on a dedicated node which is separate from nodes where Game Servers are scheduled. If you use the above command, with our prometheus.yaml to set up Prometheus, it will schedule Prometheus pods on nodes tainted with agones.dev/agones-metrics=true:NoExecute and labeled with agones.dev/agones-metrics=true if available.

As an example, to set up a dedicated node pool for Prometheus on GKE, run the following command before installing Prometheus. Alternatively you can taint and label nodes manually.

gcloud container node-pools create agones-metrics --cluster=... --zone=... \
  --node-taints agones.dev/agones-metrics=true:NoExecute \
  --node-labels agones.dev/agones-metrics=true \
  --num-nodes=1 \
  --machine-type=e2-standard-4

By default we will disable the push gateway (we don’t need it for Code Blind) and other exporters.

The helm chart supports nodeSelector, affinity and toleration, you can use them to schedule Prometheus deployments on an isolated node(s) to have an homogeneous game servers workload.

This will install a Prometheus Server in your current cluster with Persistent Volume Claim (Deactivated for Minikube and Kind) for storing and querying time series, it will automatically start collecting metrics from Code Blind Controller.

Finally, to access Prometheus metrics, rules and alerts explorer use

kubectl port-forward deployments/prom-prometheus-server 9090 -n metrics

Now you can access the prometheus dashboard http://localhost:9090.

On the landing page you can start exploring metrics by creating queries. You can also verify what targets Prometheus currently monitors (Header Status > Targets), you should see Code Blind controller pod in the kubernetes-pods section.

Now let’s install some Grafana dashboards.

Grafana installation

Grafana is a open source time series analytics platform which supports Prometheus data source. We can also easily import pre-built dashboards.

First we will install Code Blind dashboard as config maps in our cluster.

kubectl apply -f ./build/grafana/

Now we can install the Grafana Community Kubernetes Helm Charts from their repository. (Replace <your-admin-password> with the admin password of your choice)

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm upgrade --install --wait grafana grafana/grafana --namespace metrics \
  --set adminPassword=<your-admin-password> -f ./build/grafana.yaml

This will install Grafana with our prepopulated dashboards and prometheus datasource previously installed

Finally to access dashboards run

kubectl port-forward deployments/grafana 3000 -n metrics

Open a web browser to http://localhost:3000, you should see Code Blind dashboards after login as admin.

Google Cloud Managed Service for Prometheus installation

To collect Code Blind metrics using Managed Prometheus:

  • Follow the instructions to enable managed collection for a GKE cluster or non-GKE cluster.

  • Configure Managed Prometheus to scrape Code Blind by creating a PodMonitoring resource:

kubectl apply -n agones-system -f https://raw.githubusercontent.com/googleforgames/agones/release-1.38.0/build/prometheus-google-managed.yaml

To install Grafana using a Managed Prometheus backend:

Google Cloud Monitoring installation

In order to use Google Cloud Monitoring you must enable the Monitoring API in the Google Cloud Console. The Cloud Monitoring exporter uses a strategy called Application Default Credentials (ADC) to find your application’s credentials. Details can be found in Setting Up Authentication for Server to Server Production Applications.

You need to grant all the necessary permissions to the users (see Access Control Guide). The predefined role Monitoring Metric Writer contains those permissions. Use the following command to assign the role to your default service account.

gcloud projects add-iam-policy-binding [PROJECT_ID] --member serviceAccount:[PROJECT_NUMBER][email protected] --role roles/monitoring.metricWriter

Before proceeding, ensure you have created a metrics node pool as mentioned in the Google Cloud installation guide.

The default metrics exporter installed with Code Blind is Prometheus. If you are using the Helm installation, you can install or upgrade Code Blind to use Cloud Monitoring, using the following chart parameters:

helm upgrade --install --wait --set agones.metrics.stackdriverEnabled=true --set agones.metrics.prometheusEnabled=false --set agones.metrics.prometheusServiceDiscovery=false my-release-name agones/agones --namespace=agones-system

With this configuration only the Cloud Monitoring exporter would be used instead of Prometheus exporter.

Using Cloud Monitoring with Workload Identity

If you would like to enable Cloud Monitoring in conjunction with Workload Identity, there are a few extra steps you need to follow:

  1. When setting up the Google service account following the instructions for Authenticating to Google Cloud, create two IAM policy bindings, one for serviceAccount:PROJECT_ID.svc.id.goog[agones-system/agones-controller] and one for serviceAccount:PROJECT_ID.svc.id.goog[agones-system/agones-allocator].

  2. Pass parameters to helm when installing Code Blind to add annotations to the agones-controller and agones-allocator Kubernetes service accounts:

helm install my-release --namespace agones-system --create-namespace agones/agones --set agones.metrics.stackdriverEnabled=true --set agones.metrics.prometheusEnabled=false --set agones.metrics.prometheusServiceDiscovery=false --set agones.serviceaccount.allocator.annotations."iam\.gke\.io/gcp-service-account"="GSA_NAME@PROJECT_ID\.iam\.gserviceaccount\.com" --set agones.serviceaccount.allocator.labels."iam\.gke\.io/gcp-service-account"="GSA_NAME@PROJECT_ID\.iam\.gserviceaccount\.com" --set agones.serviceaccount.controller.annotations."iam\.gke\.io/gcp-service-account"="GSA_NAME@PROJECT_ID\.iam\.gserviceaccount\.com"

To verify that metrics are being sent to Cloud Monitoring, create a Fleet or a Gameserver and look for the metrics to show up in the Cloud Monitoring dashboard. Navigate to the Metrics explorer and search for metrics with the prefix agones/. Select a metric and look for data to be plotted in the graph to the right.

An example of a custom dashboard is:

cloud monitoring dashboard

Currently there exists only manual way of configuring Cloud Monitoring Dashboard. So it is up to you to set an Alignment Period (minimal is 1 minute), GroupBy, Filter parameters and other graph settings.

Troubleshooting

If you can’t see Code Blind metrics you should have a look at the controller logs for connection errors. Also ensure that your cluster has the necessary credentials to interact with Cloud Monitoring. You can configure stackdriverProjectID manually, if the automatic discovery is not working.

Permissions problem example from controller logs:

Failed to export to Stackdriver: rpc error: code = PermissionDenied desc = Permission monitoring.metricDescriptors.create denied (or the resource may not exist).

If you receive this error, ensure your service account has the role or corresponding permissions mentioned above.

11 - Access Code Blind via the Kubernetes API

It’s likely that we will want to programmatically interact with Code Blind. Everything that can be done via the kubectl and yaml configurations can also be done via the Kubernetes API.

Installing Code Blind creates several Custom Resource Definitions (CRD), which can be accessed and manipulated through the Kubernetes API.

The detailed list of Code Blind CRDs with their parameters could be found here - Code Blind CRD API Reference.

Kubernetes has multiple client libraries, however, at time of writing, only the Go and Python clients are documented to support accessing CRDs.

This can be found in the Accessing a custom resource section of the Kubernetes documentation.

At this time, we recommend interacting with Code Blind through the Go client that has been generated in this repository, but other methods may also work as well.

Go Client

Kubernetes Go Client tooling generates a Client for Code Blind that we can use to interact with the Code Blind installation on our Kubernetes cluster.

Authentication

This client uses the same authentication mechanisms as the Kubernetes API.

If you plan to run your code in the same cluster as the Code Blind install, have a look at the in cluster configuration example from the Kubernetes Client.

If you plan to run your code outside the Kubernetes cluster as your Code Blind install, look at the out of cluster configuration example from the Kubernetes client.

Example

The following is an example of a in-cluster configuration, that creates a Clientset for Code Blind and then creates a GameServer.

A full example code is available in the example folder.

package main

import (
	"fmt"
	"context"

	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
	"agones.dev/agones/pkg/client/clientset/versioned"
	"agones.dev/agones/pkg/util/runtime"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func main() {
	config, err := rest.InClusterConfig()
	logger := runtime.NewLoggerWithSource("main")
	if err != nil {
		logger.WithError(err).Fatal("Could not create in cluster config")
	}

	// Access to standard Kubernetes resources through the Kubernetes Clientset
	// We don't actually need this for this example, but it's just here for
	// illustrative purposes
	kubeClient, err := kubernetes.NewForConfig(config)
	if err != nil {
		logger.WithError(err).Fatal("Could not create the kubernetes clientset")
	}

	// Access to the Code Blind resources through the Code Blind Clientset
	// Note that we reuse the same config as we used for the Kubernetes Clientset
	agonesClient, err := versioned.NewForConfig(config)
	if err != nil {
		logger.WithError(err).Fatal("Could not create the agones api clientset")
	}

	// Create a GameServer
	gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "simple-game-server", Namespace: "default"},
		Spec: agonesv1.GameServerSpec{
			Container: "simple-game-server",
			Ports: []agonesv1.GameServerPort{{
				ContainerPort: 7654,
				HostPort:      7654,
				Name:          "gameport",
				PortPolicy:    agonesv1.Static,
				Protocol:      corev1.ProtocolUDP,
			}},
			Template: corev1.PodTemplateSpec{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{{Name: "simple-game-server", Image: "us-docker.pkg.dev/codeblind/examples/simple-server:0.27"}},
				},
			},
		},
	}
	newGS, err := agonesClient.AgonesV1().GameServers("default").Create(context.TODO(), gs, metav1.CreateOptions{})
	if err != nil {
		panic(err)
	}

	fmt.Printf("New game servers' name is: %s", newGS.ObjectMeta.Name)
}

In order to create GS using provided example, you can run it as a Kubernetes Job:

kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.38.0/examples/crd-client/create-gs.yaml --namespace agones-system
kubectl get pods --namespace agones-system
NAME                                 READY   STATUS      RESTARTS   AGE
create-gs-6wz86-7qsm5                0/1     Completed   0          6s
kubectl logs create-gs-6wz86-7qsm5  --namespace agones-system
{"message":"\u0026{0xc0001dde00 default}","severity":"info","source":"main","time":"2020-04-21T11:14:00.477576428Z"}
{"message":"New GameServer name is: helm-test-server-fxfgg","severity":"info","time":"2020-04-21T11:14:00.516024697Z"}

You have just created a GameServer using Kubernetes Go Client.

Best Practice: Using Informers and Listers

Almost all Kubernetes’ controllers and custom controllers utilise Informers and Listers to reduce the load on the Kubernetes’s control plane.

Repetitive, direct access of the Kubernetes control plane API can significantly reduce the performance of the cluster – and Informers and Listers help resolving that issue.

Informers and Listers reduce the load on the Kubernetes control plane by creating, using and maintaining an eventually consistent an in-memory cache. This can be watched and also queried with zero cost, since it will only read against its in-memory model of the Kubernetes resources.

Informer’s role and Lister’s role are different.

An Informer is the mechanism for watching a Kubernetes object’s event, such that when a Kubernetes object changes(e.g. CREATE,UPDATE,DELETE), the Informer is informed, and can execute a callback with the relevant object as an argument.

This can be very useful for building event based systems against the Kubernetes API.

A Lister is the mechanism for querying Kubernetes object’s against the client side in-memory cache. Since the Lister stores objects in an in-memory cache, queries against a come at practically no cost.

Of course, Code Blind itself also uses Informers and Listers in its codebase.

Example

The following is an example of Informers and Listers, that show the GameServer’s name & status & IPs in the Kubernetes cluster.

package main

import (
	"context"
	"time"

	"agones.dev/agones/pkg/client/clientset/versioned"
	"agones.dev/agones/pkg/client/informers/externalversions"
	"agones.dev/agones/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
)

func main() {
	config, err := rest.InClusterConfig()
	logger := runtime.NewLoggerWithSource("main")
	if err != nil {
		logger.WithError(err).Fatal("Could not create in cluster config")
	}
	kubeClient, err := kubernetes.NewForConfig(config)
	agonesClient, err := versioned.NewForConfig(config)
	if err != nil {
		logger.WithError(err).Fatal("Could not create the agones api clientset")
	}

	// Create InformerFactory which create the informer
	informerFactory := informers.NewSharedInformerFactory(kubeClient, time.Second*30)
	agonesInformerFactory := externalversions.NewSharedInformerFactory(agonesClient, time.Second*30)

	// Create Pod informer by informerFactory
	podInformer := informerFactory.Core().V1().Pods()

	// Create GameServer informer by informerFactory
	gameServers := agonesInformerFactory.Code Blind().V1().GameServers()
	gsInformer := gameServers.Informer()

	// Add EventHandler to informer
	// When the object's event happens, the function will be called
	// For example, when the pod is added, 'AddFunc' will be called and put out the "Pod Added"
	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    func(new interface{}) { logger.Infof("Pod Added") },
		UpdateFunc: func(old, new interface{}) { logger.Infof("Pod Updated") },
		DeleteFunc: func(old interface{}) { logger.Infof("Pod Deleted") },
	})
	gsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    func(new interface{}) { logger.Infof("GameServer Added") },
		UpdateFunc: func(old, new interface{}) { logger.Infof("GameServer Updated") },
		DeleteFunc: func(old interface{}) { logger.Infof("GameServer Deleted") },
	})

	ctx := context.Background()

	// Start Go routines for informer
	informerFactory.Start(ctx.Done())
	agonesInformerFactory.Start(ctx.Done())
	// Wait until finish caching with List API
	informerFactory.WaitForCacheSync(ctx.Done())
	agonesInformerFactory.WaitForCacheSync(ctx.Done())

	// Create Lister which can list objects from the in-memory-cache
	podLister := podInformer.Lister()
	gsLister := gameServers.Lister()

	for {
		// Get List objects of Pods from Pod Lister
		p := podLister.Pods("default")
		// Get List objects of GameServers from GameServer Lister
		gs, err := gsLister.List(labels.Everything())
		if err != nil {
			panic(err)
		}
		// Show GameServer's name & status & IPs
		for _, g := range gs {
			a, err := p.Get(g.GetName())
			if err != nil {
				panic(err)
			}
			logger.Infof("------------------------------")
			logger.Infof("Name: %s", g.GetName())
			logger.Infof("Status: %s", g.Status.State)
			logger.Infof("External IP: %s", g.Status.Address)
			logger.Infof("Internal IP: %s", a.Status.PodIP)
		}
		time.Sleep(time.Second * 25)
	}
}

You can list GameServer’s name and status and IPs using Kubernetes Informers and Listers.

Direct Access to the REST API via Kubectl

If there isn’t a client written in your preferred language, it is always possible to communicate directly with Kubernetes API to interact with Code Blind.

The Kubernetes API can be authenticated and exposed locally through the kubectl proxy

For example:

kubectl proxy &
Starting to serve on 127.0.0.1:8001

List all Code Blind endpoints

curl http://localhost:8001/apis | grep agones -A 5 -B 5
{
    "name": "agones.dev",
    "versions": [
    {
        "groupVersion": "agones.dev/v1",
        "version": "v1"
    }
    ],
    "preferredVersion": {
    "groupVersion": "agones.dev/v1",
    "version": "v1"
    },
    "serverAddressByClientCIDRs": null
}

List Code Blind resources

curl http://localhost:8001/apis/agones.dev/v1
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "agones.dev/v1",
  "resources": [
    {
      "name": "gameservers",
      "singularName": "gameserver",
      "namespaced": true,
      "kind": "GameServer",
      "verbs": [
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "create",
        "update",
        "watch"
      ],
      "shortNames": [
        "gs"
      ]
    }
  ]
}

List all gameservers in the default namespace

curl http://localhost:8001/apis/agones.dev/v1/namespaces/default/gameservers
{
    "apiVersion": "agones.dev/v1",
    "items": [
        {
            "apiVersion": "agones.dev/v1",
            "kind": "GameServer",
            "metadata": {
                "annotations": {
                    "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"agones.dev/v1\",\"kind\":\"GameServer\",\"metadata\":{\"annotations\":{},\"name\":\"simple-game-server\",\"namespace\":\"default\"},\"spec\":{\"containerPort\":7654,\"hostPort\":7777,\"portPolicy\":\"static\",\"template\":{\"spec\":{\"containers\":[{\"image\":\"us-docker.pkg.dev/codeblind/examples/simple-server:0.27\",\"name\":\"simple-game-server\"}]}}}}\n"
                },
                "clusterName": "",
                "creationTimestamp": "2018-03-02T21:41:05Z",
                "finalizers": [
                    "agones.dev"
                ],
                "generation": 0,
                "name": "simple-game-server",
                "namespace": "default",
                "resourceVersion": "760",
                "selfLink": "/apis/agones.dev/v1/namespaces/default/gameservers/simple-game-server",
                "uid": "692beea6-1e62-11e8-beb2-080027637781"
            },
            "spec": {
                "PortPolicy": "Static",
                "container": "simple-game-server",
                "containerPort": 7654,
                "health": {
                    "failureThreshold": 3,
                    "initialDelaySeconds": 5,
                    "periodSeconds": 5
                },
                "hostPort": 7777,
                "protocol": "UDP",
                "template": {
                    "metadata": {
                        "creationTimestamp": null
                    },
                    "spec": {
                        "containers": [
                            {
                                "image": "us-docker.pkg.dev/codeblind/examples/simple-server:0.27",
                                "name": "simple-game-server",
                                "resources": {}
                            }
                        ]
                    }
                }
            },
            "status": {
                "address": "192.168.99.100",
                "nodeName": "agones",
                "port": 7777,
                "state": "Ready"
            }
        }
    ],
    "kind": "GameServerList",
    "metadata": {
        "continue": "",
        "resourceVersion": "1062",
        "selfLink": "/apis/agones.dev/v1/namespaces/default/gameservers"
    }
}

Allocate a gameserver from a fleet named ‘simple-game-server’, with GameServerAllocation

curl -d '{"apiVersion":"allocation.agones.dev/v1","kind":"GameServerAllocation","spec":{"required":{"matchLabels":{"agones.dev/fleet":"simple-game-server"}}}}' -H "Content-Type: application/json" -X POST http://localhost:8001/apis/allocation.agones.dev/v1/namespaces/default/gameserverallocations
{
    "kind": "GameServerAllocation",
    "apiVersion": "allocation.agones.dev/v1",
    "metadata": {
        "name": "simple-game-server-v6jwb-cmdcv",
        "namespace": "default",
        "creationTimestamp": "2019-07-03T17:19:47Z"
    },
    "spec": {
        "multiClusterSetting": {
            "policySelector": {}
        },
        "required": {
            "matchLabels": {
                "agones.dev/fleet": "simple-game-server"
            }
        },
        "scheduling": "Packed",
        "metadata": {}
    },
    "status": {
        "state": "Allocated",
        "gameServerName": "simple-game-server-v6jwb-cmdcv",
        "ports": [
            {
                "name": "default",
                "port": 7445
            }
        ],
        "address": "34.94.118.237",
        "nodeName": "gke-test-cluster-default-f11755a7-5km3"
    }
}

You may wish to review the Code Blind Kubernetes API for the full data structure reference.

The Kubernetes API Concepts section may also provide the more details on the API conventions that are used in the Kubernetes API.

Next Steps

12 - Troubleshooting

Troubleshooting guides and steps.

Something went wrong with my GameServer

If there is something going wrong with your GameServer, there are a few approaches to determining the cause:

Run with the local SDK server

A good first step for seeing what may be going wrong is replicating the issue locally. To do this you can take advantage of the Code Blind local SDK server , with the following troubleshooting steps:

  1. Run your game server as a local binary against the local SDK server
  2. Run your game server container against the local SDK server. It’s worth noting that running with docker run --network=host ... can be an easy way to allow your game server container(s) access to the local SDK server)

At each stage, keep an eye on the logs of your game server binary, and the local SDK server, and ensure there are no system errors.

Run as a GameServer rather than a Fleet

A Fleet will automatically replace any unhealthy GameServer under its control - which can make it hard to catch all the details to determine the cause.

To work around this, instantiate a single instance of your game server as a
GameServer within your Code Blind cluster.

This GameServer will not be replaced if it moves to an Unhealthy state, giving you time to introspect what is going wrong.

Introspect with Kubernetes tooling

There are many Kubernetes tools that will help with determining where things have potentially gone wrong for your game server. Here are a few you may want to try.

kubectl describe

Depending on what is happening, you may want to run kubectl describe <gameserver name> to view the events that are associated with that particular GameServer resource. This can give you insight into the lifecycle of the GameServer and if anything has gone wrong.

For example, here we can see where the simple-game-server example has been moved to the Unhealthy state due to a crash in the backing GameServer Pod container’s binary.

kubectl describe gs simple-game-server-zqppv
Name:         simple-game-server-zqppv
Namespace:    default
Labels:       <none>
Annotations:  agones.dev/sdk-version: 1.0.0-dce1546
API Version:  agones.dev/v1
Kind:         GameServer
Metadata:
  Creation Timestamp:  2019-08-16T21:25:44Z
  Finalizers:
    agones.dev
  Generate Name:     simple-game-server-
  Generation:        1
  Resource Version:  1378575
  Self Link:         /apis/agones.dev/v1/namespaces/default/gameservers/simple-game-server-zqppv
  UID:               6818adc7-c06c-11e9-8dbd-42010a8a0109
Spec:
  Container:  simple-game-server
  Health:
    Failure Threshold:      3
    Initial Delay Seconds:  5
    Period Seconds:         5
  Ports:
    Container Port:  7654
    Host Port:       7058
    Name:            default
    Port Policy:     Dynamic
    Protocol:        UDP
  Scheduling:        Packed
  Template:
    Metadata:
      Creation Timestamp:  <nil>
    Spec:
      Containers:
        Image:  us-docker.pkg.dev/codeblind/examples/simple-server:0.27
        Name:   simple-game-server
        Resources:
          Limits:
            Cpu:     20m
            Memory:  32Mi
          Requests:
            Cpu:     20m
            Memory:  32Mi
Status:
  Address:    35.230.59.117
  Node Name:  gke-test-cluster-default-590db5e4-4s6r
  Ports:
    Name:          default
    Port:          7058
  Reserved Until:  <nil>
  State:           Unhealthy
Events:
  Type     Reason          Age   From                   Message
  ----     ------          ----  ----                   -------
  Normal   PortAllocation  72s   gameserver-controller  Port allocated
  Normal   Creating        72s   gameserver-controller  Pod simple-game-server-zqppv created
  Normal   Scheduled       72s   gameserver-controller  Address and port populated
  Normal   RequestReady    67s   gameserver-sidecar     SDK state change
  Normal   Ready           66s   gameserver-controller  SDK.Ready() complete
  Warning  Unhealthy       34s   health-controller      Issue with Gameserver pod

The backing Pod has the same name as the GameServer - so it’s also worth looking at the details and events for the Pod to see if there are any issues there, such as restarts due to binary crashes etc.

For example, you can see the restart count on the us-docker.pkg.dev/codeblind/examples/simple-server:0.27 container is set to 1, due to the game server binary crash

kubectl describe pod simple-game-server-zqppv
Name:               simple-game-server-zqppv
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               gke-test-cluster-default-590db5e4-4s6r/10.138.0.23
Start Time:         Fri, 16 Aug 2019 21:25:44 +0000
Labels:             agones.dev/gameserver=simple-game-server-zqppv
                    agones.dev/role=gameserver
Annotations:        agones.dev/container: simple-game-server
                    agones.dev/sdk-version: 1.0.0-dce1546
                    cluster-autoscaler.kubernetes.io/safe-to-evict: false
Status:             Running
IP:                 10.48.1.80
Controlled By:      GameServer/simple-game-server-zqppv
Containers:
  simple-game-server:
    Container ID:   docker://69eacd03cc89b0636b78abe47926b02183ba84d18fa20649ca443f5232511661
    Image:          us-docker.pkg.dev/codeblind/examples/simple-server:0.27
    Image ID:       docker-pullable://gcr.io/agones-images/simple-game-server@sha256:6a60eff5e68b88b5ce75ae98082d79cff36cda411a090f3495760e5c3b6c3575
    Port:           7654/UDP
    Host Port:      7058/UDP
    State:          Running
      Started:      Fri, 16 Aug 2019 21:26:22 +0000
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Fri, 16 Aug 2019 21:25:45 +0000
      Finished:     Fri, 16 Aug 2019 21:26:22 +0000
    Ready:          True
    Restart Count:  1
    Limits:
      cpu:     20m
      memory:  32Mi
    Requests:
      cpu:        20m
      memory:     32Mi
    Liveness:     http-get http://:8080/gshealthz delay=5s timeout=1s period=5s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from empty (ro)
  agones-gameserver-sidecar:
    Container ID:   docker://f3c475c34d26232e19b60be65b03bc6ce41931f4c37e00770d3ab4a36281d31c
    Image:          gcr.io/agones-mark/agones-sdk:1.0.0-dce1546
    Image ID:       docker-pullable://gcr.io/agones-mark/agones-sdk@sha256:4b5693e95ee3023a2b2e2099d102bb6bac58d4ce0ac472e58a09cee6d160cd19
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Fri, 16 Aug 2019 21:25:48 +0000
    Ready:          True
    Restart Count:  0
    Requests:
      cpu:     30m
    Liveness:  http-get http://:8080/healthz delay=3s timeout=1s period=3s #success=1 #failure=3
    Environment:
      GAMESERVER_NAME:  simple-game-server-zqppv
      POD_NAMESPACE:    default (v1:metadata.namespace)
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from agones-sdk-token-vr6qq (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  empty:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
  agones-sdk-token-vr6qq:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  agones-sdk-token-vr6qq
    Optional:    false
QoS Class:       Burstable
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age                   From                                             Message
  ----    ------     ----                  ----                                             -------
  Normal  Scheduled  2m32s                 default-scheduler                                Successfully assigned default/simple-game-server-zqppv to gke-test-cluster-default-590db5e4-4s6r
  Normal  Pulling    2m31s                 kubelet, gke-test-cluster-default-590db5e4-4s6r  pulling image "gcr.io/agones-mark/agones-sdk:1.0.0-dce1546"
  Normal  Started    2m28s                 kubelet, gke-test-cluster-default-590db5e4-4s6r  Started container
  Normal  Pulled     2m28s                 kubelet, gke-test-cluster-default-590db5e4-4s6r  Successfully pulled image "gcr.io/agones-mark/agones-sdk:1.0.0-dce1546"
  Normal  Created    2m28s                 kubelet, gke-test-cluster-default-590db5e4-4s6r  Created container
  Normal  Created    114s (x2 over 2m31s)  kubelet, gke-test-cluster-default-590db5e4-4s6r  Created container
  Normal  Started    114s (x2 over 2m31s)  kubelet, gke-test-cluster-default-590db5e4-4s6r  Started container
  Normal  Pulled     114s (x2 over 2m31s)  kubelet, gke-test-cluster-default-590db5e4-4s6r  Container image "us-docker.pkg.dev/codeblind/examples/simple-server:0.27" already present on machine

Finally, you can also get the logs of your GameServer Pod as well via kubectl logs <pod name> -c <game server container name>, for example:

kubectl logs simple-game-server-zqppv -c simple-game-server
2019/08/16 21:26:23 Creating SDK instance
2019/08/16 21:26:24 Starting Health Ping
2019/08/16 21:26:24 Starting UDP server, listening on port 7654
2019/08/16 21:26:24 Marking this server as ready

The above commands will only give the most recent container’s logs (so we won’t get the previous crash), but you can use kubectl logs --previous=true simple-game-server-zqppv -c simple-game-server to get the previous instance of the containers logs, or use your Kubernetes platform of choice’s logging aggregation tools to view the crash details.

kubectl events

The “Events” section that is seen at the bottom of a kubectl describe is backed an actual Event record in Kubernetes, which can be queried - and is general persistent for an hour after it is created.

Therefore, even a GameServer or Pod resource is no longer available in the system, its Events may well be.

kubectl get events can be used to see all these events. This can also be grepped with the GameServer name to see all events across both the GameServer and its backing Pod, like so:

kubectl get events | grep simple-game-server-v992s-jwpx2
2m47s       Normal   PortAllocation          gameserver/simple-game-server-v992s-jwpx2   Port allocated
2m47s       Normal   Creating                gameserver/simple-game-server-v992s-jwpx2   Pod simple-game-server-v992s-jwpx2 created
2m47s       Normal   Scheduled               pod/simple-game-server-v992s-jwpx2          Successfully assigned default/simple-game-server-v992s-jwpx2 to gke-test-cluster-default-77e7f57d-j1mp
2m47s       Normal   Scheduled               gameserver/simple-game-server-v992s-jwpx2   Address and port populated
2m46s       Normal   Pulled                  pod/simple-game-server-v992s-jwpx2          Container image "us-docker.pkg.dev/codeblind/examples/simple-server:0.27" already present on machine
2m46s       Normal   Created                 pod/simple-game-server-v992s-jwpx2          Created container simple-game-server
2m45s       Normal   Started                 pod/simple-game-server-v992s-jwpx2          Started container simple-game-server
2m45s       Normal   Pulled                  pod/simple-game-server-v992s-jwpx2          Container image "gcr.io/agones-images/agones-sdk:1.7.0" already present on machine
2m45s       Normal   Created                 pod/simple-game-server-v992s-jwpx2          Created container agones-gameserver-sidecar
2m45s       Normal   Started                 pod/simple-game-server-v992s-jwpx2          Started container agones-gameserver-sidecar
2m45s       Normal   RequestReady            gameserver/simple-game-server-v992s-jwpx2   SDK state change
2m45s       Normal   Ready                   gameserver/simple-game-server-v992s-jwpx2   SDK.Ready() complete
2m47s       Normal   SuccessfulCreate        gameserverset/simple-game-server-v992s      Created gameserver: simple-game-server-v992s-jwpx2

Other techniques

For more tips and tricks, the Kubernetes Cheatsheet: Interactive with Pods also provides more troubleshooting techniques.

How do I see the logs for Code Blind?

If something is going wrong, and you want to see the logs for Code Blind, there are potentially two places you will want to check:

  1. The controller: assuming you installed Code Blind in the agones-system namespace, you will find that there is a single pod called agones-controller-<hash> (where hash is the unique code that Kubernetes generates) that exists there, that you can get the logs from. This is the main controller for Code Blind, and should be the first place to check when things go wrong.
    1. To get the logs from this controller run:
      kubectl logs --namespace=agones-system agones-controller-<hash>
  2. The SDK server sidecar: Code Blind runs a small gRPC + http server for the SDK in a container in the same network namespace as the game server container to connect to via the SDK.
    The logs from this SDK server are also useful for tracking down issues, especially if you are having trouble with a particular GameServer.
    1. To find the Pod for the GameServer look for the pod with a name that is prefixed with the name of the owning GameServer. For example if you have a GameServer named simple-game-server, it’s pod could potentially be named simple-game-server-dnbwj.
    2. To get the logs from that Pod, we need to specify that we want the logs from the agones-gameserver-sidecar container. To do that, run the following:
      kubectl logs simple-game-server-dnbwj -c agones-gameserver-sidecar

Code Blind uses JSON structured logging, therefore errors will be visible through the "severity":"info" key and value.

Enable Debug Level Logging for the SDK Server

By default, the SDK Server binary is set to an Info level of logging.

You can use the sdkServer.logLevel to increase this to Debug levels, and see extra information about what is happening with the SDK Server that runs alonside your game server container(s).

See the GameServer reference for configuration details.

Enable Debug Level Logging for the Code Blind Controller

By default, the log level for the Code Blind controller is “info”. To get a more verbose log output, switch this to “debug” via the agones.controller.logLevel Helm Configuration parameters at installation.

The Feature Flag I enabled/disabled isn’t working as expected

It’s entirely possible that Alpha features may still have bugs in them (They are alpha after all 😃), but the first thing to check is what the actual Feature Flags states were passed to Code Blind are, and that they were set correctly.

The easiest way is to check the top info level log lines from the Code Blind controller.

For example:

$ kubectl logs -n agones-system agones-controller-7575dc59-7p2rg  | head
{"filename":"/home/agones/logs/agones-controller-20220615_211540.log","message":"logging to file","numbackups":99,"severity":"info","source":"main","time":"2022-06-15T21:15:40.309349789Z"}
{"logLevel":"info","message":"Setting LogLevel configuration","severity":"info","source":"main","time":"2022-06-15T21:15:40.309403296Z"}
{"ctlConf":{"MinPort":7000,"MaxPort":8000,"SidecarImage":"gcr.io/agones-images/agones-sdk:1.23.0","SidecarCPURequest":"30m","SidecarCPULimit":"0","SidecarMemoryRequest":"0","SidecarMemoryLimit":"0","SdkServiceAccount":"agones-sdk","AlwaysPullSidecar":false,"PrometheusMetrics":true,"Stackdriver":false,"StackdriverLabels":"","KeyFile":"/home/agones/certs/server.key","CertFile":"/home/agones/certs/server.crt","KubeConfig":"","GCPProjectID":"","NumWorkers":100,"APIServerSustainedQPS":400,"APIServerBurstQPS":500,"LogDir":"/home/agones/logs","LogLevel":"info","LogSizeLimitMB":10000},"featureGates":"Example=true\u0026NodeExternalDNS=true\u0026PlayerAllocationFilter=false\u0026PlayerTracking=false","message":"starting gameServer operator...","severity":"info","source":"main","time":"2022-06-15T21:15:40.309528802Z","version":"1.23.0"}
...

The ctlConf section has the full configuration for Code Blind as it was passed to the controller. Within that log line there is a featureGates key, that has the full Feature Gate configuration as a URL Query String (\u0026 is JSON for &), so you can see if the Feature Gates are set as expected.

I uninstalled Code Blind before deleted all my GameServers and now they won’t delete

Code Blind GameServers use Finalizers to manage garbage collection of the GameServers. This means that if the Code Blind controller doesn’t remove the finalizer for you (i.e. if it has been uninstalled), it can be tricky to remove them all.

Thankfully, if we create a patch to remove the finalizers from all GameServers, we can delete them with impunity.

A quick one liner to do this:

kubectl get gameserver -o name | xargs -n1 -P1 -I{} kubectl patch {} --type=merge -p '{"metadata": {"finalizers": []}}'

Once this is done, you can kubectl delete gs --all and clean everything up (if it’s not gone already).

I’m getting Forbidden errors when trying to install Code Blind

Ensure that you are running Kubernetes 1.12 or later, which does not require any special clusterrolebindings to install Code Blind.

If you want to install Code Blind on an older version of Kubernetes, you need to create a clusterrolebinding to add your identity as a cluster admin, e.g.

# Kubernetes Engine
kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole cluster-admin --user `gcloud config get-value account`
# Minikube
kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin --serviceaccount=kube-system:default

On GKE, gcloud config get-value accounts will return a lowercase email address, so if you are using a CamelCase email, you may need to type it in manually.

I’m getting stuck in “Terminating” when I uninstall Code Blind

If you try to uninstall the agones-system namespace before you have removed all of the components in the namespace you may end up in a Terminating state.

kubectl get ns
NAME              STATUS        AGE                                                                                                                                                    
agones-system     Terminating   4d

Fixing this up requires us to bypass the finalizer in Kubernetes (article link), by manually changing the namespace details:

First get the current state of the namespace:

kubectl get namespace agones-system -o json >tmp.json

Edit the response tmp.json to remove the finalizer data, for example remove the following:

"spec": {
    "finalizers": [
        "kubernetes"
    ]
},

Open a new terminal to proxy traffic:

 kubectl proxy
Starting to serve on 127.0.0.1:8001

Now make an API call to send the altered namespace data:

 curl -k -H "Content-Type: application/json" -X PUT --data-binary @tmp.json http://127.0.0.1:8001/api/v1/namespaces/agones-system/finalize

You may need to clean up any other Code Blind related resources you have in your cluster at this point.