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.
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.
| Variable | Purpose |
|---|---|
AGONES_SHIM_READY_TCP | Ports that must accept before Ready. 8085 or host:8085, comma separated |
AGONES_SHIM_READY_HTTP | URLs that must return 2xx before Ready |
AGONES_SHIM_LIVE_TCP / _HTTP | Liveness probes; defaults to the readiness set |
AGONES_SHIM_READY_TIMEOUT_SECS | Give up waiting. 0 (default) means never |
AGONES_SHIM_HEALTH_INTERVAL_SECS | Health beat and liveness interval, default 5 |
AGONES_SHIM_COUNTER_NAME | Counter to publish; must already exist in the GameServer spec |
AGONES_SHIM_COUNTER_METRICS_URL | Prometheus text endpoint to scrape |
AGONES_SHIM_COUNTER_METRIC | Metric name to read from it |
AGONES_SHIM_COUNTER_INTERVAL_SECS | Scrape interval, default 30 |
AGONES_SHIM_SDK_CONNECT_TIMEOUT_SECS | Bound 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.