Skip to content
agones · sidecar · rust

Lifecycle sidecarAgones Shim

Most game servers have no Agones SDK, so every fleet grows its own shell shim. This is one small Rust binary that does the job properly — probes, Ready, Health, Shutdown and Counters — configured entirely by environment variables so a single image serves any fleet.

What a shell shim cannot do

A nc loop can tell you a port is open. It cannot tell you the server registered with its service registry, cannot call Shutdown() on SIGTERM, and cannot report how many players are connected.

  • Deliberate drain — SIGTERM becomes Shutdown(), so the grace period is actually used.
  • Honest readiness — an HTTP probe can mean "routable", not merely "bound".
  • Occupancy — publishes a Counter, which a FleetAutoscaler can scale on.
kbve/agones-shimImage
17.8 MBSize
agones 1.58SDK
MITLicense

What it gives you

Features

Graceful shutdown

Traps SIGTERM and calls Shutdown() so Agones records a deliberate drain instead of an unhealthy pod.

Probes that mean something

TCP for "bound", HTTP for "answering". Multiple probes, comma separated, all must pass before Ready.

Counters

Scrapes a Prometheus gauge and publishes it as a Counter, giving a FleetAutoscaler something real to scale on.

Degrades cleanly

With no SDK reachable it runs probe-only, so the same image works under docker compose where there is no Agones.

Motivation

Why it exists

Game servers rarely speak the Agones SDK. AzerothCore doesn’t, and neither do most dedicated servers, so each fleet ends up with a hand-rolled shim inside its manifest — poll a socket with nc, post to the SDK’s HTTP endpoint with wget. ToCloud9 had three copies of exactly that, and they shared three defects.

Nothing ever called Shutdown(). On a scale-down or rolling update the pod takes SIGTERM, the shim dies, and Agones records the server as Unhealthy rather than shut down — so the terminationGracePeriodSeconds the manifest asks for is never actually spent letting the game flush state.

Readiness meant “the socket accepts”, which is not “reachable”. A ToCloud9 worldserver binds its port and only then registers with servers-registry. In that window Agones believes the server is good while the gateway cannot route anyone to it.

Nothing reported occupancy, so a FleetAutoscaler had nothing to scale on and the fleet sat at a fixed replica count.

Configuration

Environment

Nothing is game-specific — every behaviour is an environment variable, so one image serves any fleet.

VariablePurpose
AGONES_SHIM_READY_TCPPorts that must accept before Ready. 8085 or host:8085, comma separated
AGONES_SHIM_READY_HTTPURLs that must return 2xx before Ready
AGONES_SHIM_LIVE_TCP / _HTTPLiveness probes; defaults to the readiness set
AGONES_SHIM_READY_TIMEOUT_SECSGive up waiting. 0 (default) means never
AGONES_SHIM_HEALTH_INTERVAL_SECSHealth beat and liveness interval, default 5
AGONES_SHIM_COUNTER_NAMECounter to publish; must already exist in the GameServer spec
AGONES_SHIM_COUNTER_METRICS_URLPrometheus text endpoint to scrape
AGONES_SHIM_COUNTER_METRICMetric name to read from it
AGONES_SHIM_COUNTER_INTERVAL_SECSScrape interval, default 30
AGONES_SHIM_SDK_CONNECT_TIMEOUT_SECSBound on the SDK connection, default 30

Prefer an HTTP probe wherever the server exposes one. For the ToCloud9 worldserver, libsidecar’s health server starts within milliseconds of registration and well after the world socket binds — so a 200 on :8901 means registered and routable, a signal the socket check simply cannot express.

Design

Decisions worth knowing

The SDK connection is bounded on purpose. Sdk::new does not fail fast when nothing is listening — it just sits there — and every step below it, readiness included, is behind that call. Unbounded, a slow-starting sidecar means the GameServer is never marked Ready and Agones kills a pod that was healthy all along.

A failed Counter scrape keeps the previous value. Reporting zero because a metrics endpoint blipped would tell an autoscaler the server had emptied.

A dead game server exits without Shutdown(). If a liveness probe fails the shim stops beating health and exits non-zero, so Agones replaces the pod. Calling Shutdown() there would report a clean exit for a server that actually crashed.

Counters must be declared in the GameServer spec. The SDK can only update a Counter that already exists; set_counter_count on an undeclared name fails.

Adoption

What is not migrated

Factorio and Palworld already have their own shims, and both do more than lifecycle: they trap SIGTERM and flush a save through RCON before shutting down. That is game-specific work this deliberately does not replicate.

They could adopt this for the lifecycle plumbing and keep their save logic — the shim’s liveness probe would sit alongside a game-specific PreStop hook — but that is a separate migration with its own risk, and their current shims are not broken.