iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🎮

Agones: GameServer

に公開

Hello, I'm @sugar235711.
This article is for Day 2 of the "Reading All the Source Code of the OSS I'm Interested in and Doing Something with It Advent Calendar 2025."
https://qiita.com/advent-calendar/2025/sugarcat

Regarding GameServer

Agones provides GameServer as a Custom Resource.

image

https://agones.dev/site/docs/advanced/system-diagram/

In this article, we will look into the internal implementation focusing on the GameServer CRD. Looking at the definition of a GameServer, the characteristic settings available to the user differ from standard resources like Deployment in points such as the Port Allocation settings for containers, protocol specification, and the ability to set the number of rooms or capacity for the GameServer itself.

apiVersion: "agones.dev/v1"
kind: GameServer
# GameServer Metadata
# https://v1-32.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectmeta-v1-meta
metadata:
  # generateName: "gds-example" # generate a unique name, with the given prefix
  name: "gds-example" # set a fixed name
spec:
  # if there is more than one container, specify which one is the game server
  container: example-server
  ports:
    # ...
    # portPolicy has four options:
    # - "Dynamic" (default) the system allocates a free hostPort for the gameserver, for game clients to connect to
    # - "Static", user defines the hostPort that the game client will connect to. Then onus is on the user to ensure that the
    # port is available. When static is the policy specified, `hostPort` is required to be populated
    # - "Passthrough" dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
    #      This will mean that users will need to lookup what port has been opened through the server side SDK.
    # - "None" means the `hostPort` is ignored and if defined, the `containerPort` (optional) is used to set the port on the GameServer instance.
    portPolicy: Static
    # The name of the container to open the port on. Defaults to the game server container if omitted or empty.
    container: simple-game-server
    # the port that is being opened on the game server process
    containerPort: 7654
    # the port exposed on the host, only required when `portPolicy` is "Static". Overwritten when portPolicy is "Dynamic".
    hostPort: 7777
    # protocol being used. Defaults to UDP. TCP and TCPUDP are other options
    # - "UDP" (default) use the UDP protocol
    # - "TCP", use the TCP protocol
    # - "TCPUDP", uses both TCP and UDP, and exposes the same hostPort for both protocols.
    #       This will mean that it adds an extra port, and the first port is set to TCP, and second port set to UDP
    protocol: UDP
 
  counters: # counters are int64 counters that can be incremented and decremented by set amounts. Keys must be declared at GameServer creation time.
    rooms: # arbitrary key.
      count: 1 # initial value can be set.
      capacity: 100 # (Optional) Defaults to 1000 and setting capacity to max(int64) may lead to issues and is not recommended. See GitHub issue https://github.com/googleforgames/agones/issues/3636 for more details.
  lists: # lists are lists of values stored against this GameServer that can be added and deleted from. Keys must be declared at GameServer creation time.
    players: # an empty list, with a capacity set to 10.
      capacity: 10 # capacity value, defaults to 1000.
    rooms: # note that it is allowed to have the same key name with one used in counters
      capacity: 333
      values: # initial values can also be set for lists
        - room1
        - room2
        - room3

https://agones.dev/site/docs/reference/gameserver/

The definition of the GameServer being sent to the Kubernetes API can be confirmed here:

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/apis/agones/v1/gameserver.go#L218-L247

GameServer maintains an internal State and manages the lifecycle from Pod startup to shutdown.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/apis/agones/v1/gameserver.go#L39-L71

Roughly, the following occurs and the GameServer state transitions:

  • Port Allocation by the Dynamic Policy specified in the Spec
  • Pod creation
  • Receipt of Ready/Shutdown signals via the SDK

GameServer States

First, in the Controller's Run method, syncGameServer registered to the WorkQueue is called, and the main part of the state transition is implemented within it.

  • Monitoring events (Add/Update/Delete) from the Kubernetes API server
  • Enqueueing to the WorkQueue and executing the Reconciliation Loop only when there is a state change

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L176-L186

Processing is carried out as it is enqueued according to each State.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L230-L244

First, the port allocation process is performed inside syncGameServerPortAllocationState.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L543-L564

In this Allocate method, ports are assigned based on the Dynamic Policy.

gsCopy := c.portAllocator.Allocate(gs.DeepCopy())

The Allocator checks the ports of each node, searches for required ports and ports that satisfy the policy, and registers them in the gameServerRegistry if found.
Allocated GameServers are managed with the UID as the key.

UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`

To prevent multiple GameServers from using the same port on the same node, mutex.Lock() is used for a thread-safe implementation.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/portallocator/portallocator.go#L226

There is also a mechanism implemented to virtually allocate ports when there is a shortage and encourage rescheduling by setting the Pod state to Pending.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/portallocator/portallocator.go#L273-L277

Once this process is complete, Pod creation (syncGameServerCreatingState) is performed.
Internally, the agones-gameserver-sidecar container is injected.
This acts as an API server for the GameServer process to communicate with Agones.
Communication is possible via both gRPC and HTTP, and it is utilized through the SDK on the GameServer side, functioning as a proxy for internal communication.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L716-L757

Additionally, user-defined containers are added to the PodSpec, and the Pod is created.
https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L664

To ensure that a ServiceAccount with the appropriate permissions is assigned to each container, the token on the GameServer side (/var/run/secrets/kubernetes.io/serviceaccount) is overridden with an empty volume.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/apis/agones/v1/gameserver.go#L887-L899

Reference
https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
https://qiita.com/knqyf263/items/ecc799650fe247dce9c5#service-account-admission-controller

After that, the state transitions as the Pod starts up. Requests to the Kubernetes API server are made via the SDK <-> Sidecar.

https://github.com/googleforgames/agones/blob/67c4b6b39b9f4a0b6c473e303acb38c2cccf150e/pkg/gameservers/controller.go#L946-L949

SDK Call
  ↓ localhost:9357 (gRPC)
Sidecar
  ↓ WorkerQueue
Sidecar Worker

PATCH /apis/agones.dev/v1/namespaces/{ns}/gameservers/{name}
Content-Type: application/json-patch+json
Authorization: Bearer <token>

Body: [
  {"op": "test", "path": "...", "value": "..."},
  {"op": "replace", "path": "...", "value": "..."}
]

Kubernetes API Server
  ↓ GameServer CRD Update
Informer (Controller)

Controller's Reconciliation Loop

For the operations themselves, the ClientSet created by the Code Generator is used.

Reference
https://github.com/kubernetes/code-generator
https://zenn.dev/ryooftheryo/articles/d103d973fc0cdd

While this is the main process, other controllers are running as separate Goroutines within this GameServer Controller. These basically manage the Pod lifecycle and update metadata, etc.
https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#objects

Summary

We have looked at the lifecycle of a GameServer.
Tomorrow, we will look at resources that manage groups of GameServers, such as Fleets.

Discussion