This is the multi-page printable view of this section. Click here to print.
Guides
- 1: Feature Stages
- 2: Best Practices
- 3: Code Blind Game Server Client SDKs
- 3.1: Unreal Engine Game Server Client Plugin
- 3.2: Unity Game Server Client SDK
- 3.3: C++ Game Server Client SDK
- 3.4: Go Game Server Client SDK
- 3.5: C# Game Server Client SDK
- 3.6: Node.js Game Server Client SDK
- 3.7: Rust Game Server Client SDK
- 3.8: REST Game Server Client API
- 3.9: Local Development
- 4: Windows Gameservers
- 5: Fleet Updates
- 6: GameServer Health Checking
- 7: Player Tracking
- 8: Local Game Server
- 9: Latency Testing with Multiple Clusters
- 10: Metrics
- 11: Access Code Blind via the Kubernetes API
- 12: Troubleshooting
1 - Feature Stages
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 Name | Gate | Default | Stage | Since |
---|---|---|---|---|
Allocated GameServers are notified on relevant Fleet Updates | FleetAllocationOverflow | Enabled | Beta | 1.37.0 |
CountsAndLists | CountsAndLists | Disabled | Alpha | 1.37.0 |
GameServer player capacity filtering on GameServerAllocations | PlayerAllocationFilter | Disabled | Alpha | 1.14.0 |
Player Tracking | PlayerTracking | Disabled | Alpha | 1.6.0 |
DisableResyncOnSDKServer | DisableResyncOnSDKServer | Disabled | Alpha | 1.37.0 |
Example Gate (not in use) | Example | Disabled | None | 0.13.0 |
Note
If you aren’t sure if Feature Flags have been set correctly, have a look at the The Feature Flag I enabled/disabled isn’t working as expected troubleshooting section.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.
Note
Please do tryAlpha
features and give feedback on them. This is important to ensure less breaking changes
through the Beta
period.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.
Note
Note: Please do tryBeta
features and give feedback on them! After they exit beta, it may not be practical for us
to make more changes.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
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:
- Anthos Service Mesh can be used to route allocation traffic to different clusters based on arbitrary criteria. See Global Multiplayer Demo for an example where the match maker influences which cluster the allocation is routed to.
- Allocation Endpoint can be used in Cloud Run to proxy allocation requests.
- Or peruse the Third Party Examples
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
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
andNo 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 inrapid
channel to allow you to test the newest Kubernetes soon after it’s available in GKE.
Note
GKE Autopilot clusters must be on Release Channels.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:
- COMPUTE_REGION: the Google Cloud region where you will create the cluster.
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 (sinceblue
has 0 desired), then - delete the old,
blue
cluster when theFleet
successfully scales down.
- create a new,
(GKE Standard only) Use node pool blue/green upgrades
3 - Code Blind Game Server Client SDKs
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:
- Ready()
- Shutdown()
- SetLabel()
- SetAnnotation()
- Allocate()
- Reserve()
- Alpha().SetCapacity()
- Alpha().PlayerConnect()
- Alpha().PlayerDisconnect()
- Alpha().SetCounterCount()
- Alpha().IncrementCounter()
- Alpha().DecrementCounter()
- Alpha().SetCounterCapacity()
- Alpha().AppendListValue()
- Alpha().DeleteListValue()
- 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.
Note
Using a GameServerAllocation is preferred in all other scenarios, as it gives Code Blind control over how packedGameServers
are scheduled within a cluster, whereas with Allocate()
you
relinquish control to an external service which likely doesn’t have as much information as Code Blind.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
vspublic
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.
Warning
There are limits on the characters that be used for label keys and values. Details are here.
You will need to take them into account when combined with the label prefix above.
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
Warning
The Counters And Lists feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate CountsAndLists
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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.
Note
The SDK batches mutation operations every 1 second for performance reasons. However, changes made and subsequently retrieved through the SDK will be atomically accurate through the SDK, as those values are tracked within the SDK Server sidecar process.
Changes made through Allocation or the Kubernetes API to
GameServer.Spec.Counters
and GameServer.Spec.Lists
will be eventually consistent when being retrieved through the SDK.
Since the Code Blind SDK server batches the update operations of
GameServer.Status.Counters
and GameServer.Status.Lists
asynchronously, this means that if you update
GameServer.status
values
through both the SDK and the Allocation/Kubernetes API, the batch processing may silently truncate some of those values
to the capacity of that Counter or List.
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
Warning
The Player Tracking feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate PlayerTracking
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
Warning
Counters and Lists will eventually replace the Alpha functionality of Player Tracking, which will subsequently be removed from Code Blind. If you are currently using this Alpha feature, we would love for you to test (and ideally migrate to!) this new functionality to ensure it will meet all your needs.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.
Note
Do not use this method if you are manually managingGameServer.Status.Players.IDs
and GameServer.Status.Players.Count
through the Kubernetes API, as indeterminate results will occur.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.
Note
Do not use this method if you are manually managingGameServer.Status.Players.IDs
and GameServer.Status.Players.Count
through the Kubernetes API, as indeterminate results will occur.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.
Note
IfGameServer.Status.Players.Capacity
is set manually through the Kubernetes API, use SDK.GameServer()
or
SDK.WatchGameServer()
instead to view this value.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.
Note
IfGameServer.Status.Players.IDs
is set manually through the Kubernetes API, use SDK.GameServer()
or SDK.WatchGameServer() instead to retrieve the current player count.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.
Note
IfGameServer.Status.Players.IDs
is set manually through the Kubernetes API, use SDK.GameServer()
or SDK.WatchGameServer() instead to determine connected status.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.
Note
IfGameServer.Status.Players.IDs
is set manually through the Kubernetes API, use SDK.GameServer()
or SDK.WatchGameServer() instead to list the connected players.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
Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.
SDK Functionality
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GameServer | ✔️ |
Configuration | Watch | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
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
totrue
. - An event is broadcast with the
GameServer
data once the/gameserver
call succeeds.
- will call
- Health
- calls
/health
endpoint on supplied rate - enabled by default with 10 second rate
- disabled by default by setting
HealthRateSeconds
to0
.
- calls
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:
- UE5 Documentation Site
- UE5 Dedicated Servers
- useful guide to getting started with dedicated servers in Unreal
- UE5 Game Mode and Game State
- UE5 Game Mode API Reference
- useful for hooking up calls to Code Blind
- UE5 Game Session API Reference
- as above there are hooks in Game Session that can be used to call into Code Blind
- UE5 Building & Packaging Games
- only building out Unreal game servers / clients, will also need to package into a container
If you use Unreal Engine 4, There are few helpful links for it:
- UE4 Documentation Site
- UE4 Dedicated Servers
- UE4 Game Flow
- UE4 Game Mode
- UE4 Game Session
- UE4 Building & Packaging Games
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
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.
Using Blueprints (UE4)
Add Component to your Blueprint GameMode
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.
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 callSetLabel
,SetPlayerCapacity
PostLogin
to callPlayerConnect
NotifyLogout
to callPlayerDisconnect
3.2 - Unity 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
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GameServer | ✔️ |
Configuration | Watch | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
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
Warning
The Player Tracking feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate PlayerTracking
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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();
Warning
The following code causes deadlock. Do not use a Wait
method with the returned Task.
void Deadlock()
{
Task<bool> t = agones.Shutdown();
t.Wait(); // deadlock!!!
}
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
)
- Interval of the server sending a health ping to the Code Blind sidecar. (default:
- Health Enabled
- Whether the server sends a health ping to the Code Blind sidecar. (default:
true
)
- Whether the server sends a health ping to the Code Blind sidecar. (default:
- Log Enabled
- Debug Logging Enabled. Debug logging for development of this Plugin. (default:
false
)
- Debug Logging Enabled. Debug logging for development of this Plugin. (default:
3.3 - C++ 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
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GameServer | ✔️ |
Configuration | WatchGameServer | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
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
- Read the SDK Overview to review all SDK functionality
- Look at the C++ example for a full build template.
- Check out sdk.h.
3.4 - Go 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
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GameServer | ✔️ |
Configuration | Watch | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ✔️ |
Counters | SetCounterCount | ✔️ |
Counters | IncrementCounter | ✔️ |
Counters | DecrementCounter | ✔️ |
Counters | SetCounterCapacity | ✔️ |
Counters | GetCounterCapacity | ✔️ |
Lists | AppendListValue | ✔️ |
Lists | DeleteListValue | ✔️ |
Lists | SetListCapacity | ✔️ |
Lists | GetListCapacity | ✔️ |
Lists | ListContains | ✔️ |
Lists | GetListLength | ✔️ |
Lists | GetListValues | ✔️ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
Installation
go get
the source,
directly from GitHub
Usage
Review the GoDoc for usage instructions
3.5 - C# 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
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GetGameServer | ✔️ |
Configuration | WatchGameServer | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
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
Warning
The Player Tracking feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate PlayerTracking
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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 gRPCGrpc.Core.Status
object. To check the state of the request, checkStatus.StatusCode
&Status.Detail
. Ex:
if(status.StatusCode == StatusCode.OK)
//do stuff
3.6 - Node.js 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
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GetGameServer | ✔️ |
Configuration | WatchGameServer | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
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
Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.
SDK Functionality
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GameServer | ✔️ |
Configuration | Watch | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounterCount | ❌ |
Counters | SetCounterCount | ❌ |
Counters | IncrementCounter | ❌ |
Counters | DecrementCounter | ❌ |
Counters | SetCounterCapacity | ❌ |
Counters | GetCounterCapacity | ❌ |
Lists | AppendListValue | ❌ |
Lists | DeleteListValue | ❌ |
Lists | SetListCapacity | ❌ |
Lists | GetListCapacity | ❌ |
Lists | ListContains | ❌ |
Lists | GetListLength | ❌ |
Lists | GetListValues | ❌ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
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
Check the Client SDK Documentation for more details on each of the SDK functions and how to run the SDK locally.
SDK Functionality
Area | Action | Implemented |
---|---|---|
Lifecycle | Ready | ✔️ |
Lifecycle | Health | ✔️ |
Lifecycle | Reserve | ✔️ |
Lifecycle | Allocate | ✔️ |
Lifecycle | Shutdown | ✔️ |
Configuration | GetGameServer | ✔️ |
Configuration | WatchGameServer | ✔️ |
Metadata | SetAnnotation | ✔️ |
Metadata | SetLabel | ✔️ |
Counters | GetCounter | ✔️ |
Counters | UpdateCounter | ✔️ |
Lists | GetList | ✔️ |
Lists | UpdateList | ✔️ |
Lists | AddListValue | ✔️ |
Lists | RemoveListValue | ✔️ |
Player Tracking | GetPlayerCapacity | ✔️ |
Player Tracking | SetPlayerCapacity | ✔️ |
Player Tracking | PlayerConnect | ✔️ |
Player Tracking | GetConnectedPlayers | ✔️ |
Player Tracking | IsPlayerConnected | ✔️ |
Player Tracking | GetPlayerCount | ✔️ |
Player Tracking | PlayerDisconnect | ✔️ |
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.
Warning
The SDK Server sidecar process may startup after your game server binary. So your REST SDK API calls should contain some retry logic to take this into account.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.
Note
Using a GameServerAllocation is preferred in all other scenarios, as it gives Code Blind control over how packedGameServers
are scheduled within a cluster, whereas with Allocate()
you
relinquish control to an external service which likely doesn’t have as much information as Code Blind.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
Warning
The Counters and Lists feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate CountsAndLists
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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
Warning
The Player Tracking feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate PlayerTracking
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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
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.
Note
File modification events can fire more than one for each save (for a variety of reasons), but it’s best practice to ensure handlers that implementWatchGameServer()
be idempotent regardless, as repeats can
happen when live as well.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.
Note
All state transitions are supported for local SDK server, however not all of them are valid in the real scenario. For instance, you cannot make a transition of a GameServer from Shutdown to a Ready state, but can do using local SDK server.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
GameServers
on Kubernetes nodes with the Windows operating system.Warning
RunningGameServers
on Windows nodes is currently Alpha, and any feedback
would be appreciated.Prerequisites
The following prerequisites are required to create a GameServer:
- A Kubernetes cluster with the UDP port range 7000-8000 open on each node.
- Code Blind controller installed in the targeted cluster
- kubectl properly configured
- 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
Note
Starting with version 0.3, the simple-game-server example is compiled as a multi-arch docker image that will run on both Linux and Windows. To ensure that the game server runs on a Windows node, a nodeSelector of"kubernetes.io/os": windows
must be added to the game server specification.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
Note
If you have Code Blind installed on Google Kubernetes Engine, and are using Cloud Shell for your terminal, UDP is blocked. For this step, we recommend SSH’ing into a running VM in your project, such as a Kubernetes node. You can click the ‘SSH’ button on the Google Compute Engine Instances page to do this. Runtoolbox
on GKE Node to run docker container with tools and then nc
command would be available.You can now communicate with the Game Server:
Note
If you do not have netcat installed
(i.e. you get a response of nc: command not found
),
you can install netcat by running sudo apt install netcat
.
If you are on Windows, you can alternatively install netcat on WSL, or download a version of netcat for Windows from nmap.org.
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
- Make a local copy of the simple-game-server fleet configuration, modify it to include a node selector, and use it to go through the Quickstart: Create a Game Server Fleet.
- If you want to use your own GameServer container make sure you have properly integrated the Code Blind SDK.
5 - Fleet Updates
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:
- Adds the
maxSurge
number ofGameServers
to the Fleet. - Shutdown the
maxUnavailable
number ofGameServers
in the Fleet, skippingAllocated
GameServers
. - Repeat above steps until all the previous
GameServer
configurations have beenShutdown
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.
Note
When Fleet
update contains only changes to the replicas
parameter, then new GameServers will be created/deleted straight away,
which means in that case maxSurge
and maxUnavailable
parameters for a RollingUpdate will not be used.
The RollingUpdate strategy takes place when you update spec
parameters other than replicas
.
If you are using a Fleet which is scaled by a FleetAutoscaler, read the Fleetautoscaler guide for more details on how RollingUpdates with FleetAutoscalers need to be implemented.
You could also check the behaviour of the Fleet with a RollingUpdate strategy on a test Fleet
to preview your upcoming updates.
Use kubectl describe fleet
to track scaling events in a Fleet.
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:
Shutdown
allGameServers
in the Fleet that are not currentlyAllocated
.- Create the same number of the new version of the
GameServers
that were previously deleted. - Repeat above steps until all the previous
GameServer
configurations have beenShutdown
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.
Note
For GameServerAllocation, you will need to have at least a single shared label between theGameServers
in each
Fleet.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
Warning
The Allocated GameSever Overflow Notification feature is currently Beta, and while it is enabled by default it may change in the future.
Use the Feature Gate FleetAllocationOverflow
to disable this feature.
See the Feature Gate documentation for details on how to disable features.
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:
- 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. - This can also be used to proactively update
GameServer
labels, to effect change in allocation strategy - such as preferring the newerGameServers
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.
Note
This works the same across Fleet resizing and Rolling/Recreate Updates, in that the implementation responds to the underlyingGameServerSet
’s replicas being shrunk to a value smaller than the number of Allocated
GameServers
it controls. Therefore, this functionality works equally well with a rolling update as it does with an
update strategy that requires at least two Fleets
.6 - GameServer Health Checking
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.
- If the
GameServer
container exits with an error before theGameServer
moves toReady
then, it is restarted as per therestartPolicy
(which defaults to “Always”). - If the
GameServer
fails health checking at any point, then it doesn’t restart, but moves to anUnhealthy
state. - If the
GameServer
container exits while inReady
,Allocated
orReserved
state, it will be restarted as per therestartPolicy
(which defaults to “Always”, sinceRestartPolicy
is aPod
wide setting), but will immediately move to anUnhealthy
state. - 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
Warning
The Player Tracking feature is currently Alpha, not enabled by default, and may change in the future.
Use the FeatureGate PlayerTracking
to enable and test this feature.
See the Feature Gate documentation for details on how to enable features.
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()
.
Note
Changing the capacity value here has no impact on players actually connected to or trying to connect to your server, as that is not a responsibility of Code Blind.
This functionality is for tracking purposes only.
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
Note
Calling PlayerConnect
or PlayerDisconnect
functions will not
connect or disconnect players, as that is not under the control of Code Blind.
This functionality is for tracking purposes only.
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:
- Get the current player count, call
SDK.Alpha().GetPlayerCount()
- Check if a specific player is connected, call
SDK.Alpha().IsPlayerConnected(playerID)
- Retrieve the full list of connected players, call
SDK.Alpha().GetConnectedPlayers()
Next Steps
- Review the Player Tracking SDK Reference
8 - Local Game Server
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.- Review the flow of how allocation is done.
- Review the specification of GameServerAllocation.
- Check out the Allocator Service as a richer alternative to
GameServerAllocation
.
- 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
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
Name | Description | Type |
---|---|---|
agones_gameservers_count | The number of gameservers per fleet and status | gauge |
agones_gameserver_allocations_duration_seconds | The distribution of gameserver allocation requests latencies | histogram |
agones_gameservers_total | The total of gameservers per fleet and status | counter |
agones_gameserver_player_connected_total | The total number of players connected to gameservers (Only available when player tracking is enabled) | gauge |
agones_gameserver_player_capacity_total | The available capacity for players on gameservers (Only available when player tracking is enabled) | gauge |
agones_fleets_replicas_count | The number of replicas per fleet (total, desired, ready, reserved, allocated) | gauge |
agones_fleet_autoscalers_able_to_scale | The fleet autoscaler can access the fleet to scale | gauge |
agones_fleet_autoscalers_buffer_limits | The limits of buffer based fleet autoscalers (min, max) | gauge |
agones_fleet_autoscalers_buffer_size | The buffer size of fleet autoscalers (count or percentage) | gauge |
agones_fleet_autoscalers_current_replicas_count | The current replicas count as seen by autoscalers | gauge |
agones_fleet_autoscalers_desired_replicas_count | The desired replicas count as seen by autoscalers | gauge |
agones_fleet_autoscalers_limited | The fleet autoscaler is outside the limits set by MinReplicas and MaxReplicas. | gauge |
agones_gameservers_node_count | The distribution of gameservers per node | histogram |
agones_nodes_count | The count of nodes empty and with gameservers | gauge |
agones_gameservers_state_duration | The 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_total | The total of HTTP requests to the Kubernetes API by status code | counter |
agones_k8s_client_http_request_duration_seconds | The distribution of HTTP requests latencies to the Kubernetes API by status code | histogram |
agones_k8s_client_cache_list_total | The total number of list operations for client-go caches | counter |
agones_k8s_client_cache_list_duration_seconds | Duration of a Kubernetes list API call in seconds | histogram |
agones_k8s_client_cache_list_items | Count of items in a list from the Kubernetes API | histogram |
agones_k8s_client_cache_watches_total | The total number of watch operations for client-go caches | counter |
agones_k8s_client_cache_last_resource_version | Last resource version from the Kubernetes API | gauge |
agones_k8s_client_workqueue_depth | Current depth of the work queue | gauge |
agones_k8s_client_workqueue_latency_seconds | How long an item stays in the work queue | histogram |
agones_k8s_client_workqueue_items_total | Total number of items added to the work queue | counter |
agones_k8s_client_workqueue_work_duration_seconds | How long processing an item from the work queue takes | histogram |
agones_k8s_client_workqueue_retries_total | Total number of items retried to the work queue | counter |
agones_k8s_client_workqueue_longest_running_processor | How long the longest running workqueue processor has been running in microseconds | gauge |
agones_k8s_client_workqueue_unfinished_work_seconds | How long unfinished work has been sitting in the workqueue in seconds | gauge |
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:
Code Blind Autoscalers allows you to monitor your current autoscalers replicas request as well as fleet replicas allocation and readyness statuses. You can only select one autoscaler at the time using the provided dropdown.
Code Blind GameServers displays your current game servers workload status (allocations, game servers statuses, fleets replicas) with optional fleet name filtering.
Code Blind GameServer Allocations displays Code Blind gameservers allocations rates and counts per fleet.
Code Blind Allocator Resource displays Code Blind Allocators CPU, memory usage and also some useful Golang runtime metrics.
Code Blind Status displays Code Blind controller health status.
Code Blind Controller Resource Usage displays Code Blind Controller CPU and memory usage and also some Golang runtime metrics.
Code Blind Controller go-client requests displays Code Blind Controller Kubernetes API consumption.
Code Blind Controller go-client caches displays Code Blind Controller Kubernetes Watches/Lists operations used.
Code Blind Controller go-client workqueues displays Code Blind Controller workqueue processing time and rates.
Code Blind Controller API Server requests displays your current API server request rate, errors rate and request latencies with optional CustomResourceDefinition filtering by Types: fleets, gameserversets, gameservers, gameserverallocations.
Dashboard screenshots :
Note
You can import our dashboards by copying the json content from each config map into your own instance of Grafana (+ > Create > Import > Or paste json) or follow the installation guide.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.
Note
Metrics will be first registered when you will start using Code Blind.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
- Confirm that you can see Prometheus metrics in Cloud Monitoring. If that’s all you need, you can stop here.
To install Grafana using a Managed Prometheus backend:
- Complete the Before you begin. To align with the Code Blind Grafana installation, we’ll be installing in the
metrics
namespace, which you’ll need to create.- If your cluster has Workload Identity enabled, which is enabled on GKE Autopilot by default, follow Configure a service account for Workload Identity to ensure that you have appropriately authorized the default Kubernetes service account in the
metrics
namespace.
- If your cluster has Workload Identity enabled, which is enabled on GKE Autopilot by default, follow Configure a service account for Workload Identity to ensure that you have appropriately authorized the default Kubernetes service account in the
- Install the Standalone Prometheus frontend UI in the
metrics
namespace - this will act as your authentication proxy for PromQL queries. - Install Grafana as above, using
-f ./build/grafana-frontend.yaml
instead of-f ./build/grafana.yaml
.
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
Note
Cloud Operations for GKE (including Cloud Monitoring) is enabled by default on GKE clusters, however you can follow this guide if it is currently disabled in your GKE cluster.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
Note
If you are using the YAML installation, follow the instructions on the page to change the above parameters by using helm to generate a custom YAML file locally.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:
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 forserviceAccount:PROJECT_ID.svc.id.goog[agones-system/agones-allocator]
.Pass parameters to helm when installing Code Blind to add annotations to the
agones-controller
andagones-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:
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
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
- Learn how to use Allocator Service for single and multi-cluster Allocation.
12 - Troubleshooting
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:
- Run your game server as a local binary against the local SDK server
- 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:
- The controller: assuming you installed Code Blind in the
agones-system
namespace, you will find that there is a single pod calledagones-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.- To get the logs from this controller run:
kubectl logs --namespace=agones-system agones-controller-<hash>
- To get the logs from this controller run:
- 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 particularGameServer
.- To find the
Pod
for theGameServer
look for the pod with a name that is prefixed with the name of the owningGameServer
. For example if you have aGameServer
namedsimple-game-server
, it’s pod could potentially be namedsimple-game-server-dnbwj
. - To get the logs from that
Pod
, we need to specify that we want the logs from theagones-gameserver-sidecar
container. To do that, run the following:kubectl logs simple-game-server-dnbwj -c agones-gameserver-sidecar
- To find the
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.