diff --git a/deploy/containers/velocity.mdx b/deploy/containers/velocity.mdx index df1a5ad..df16879 100644 --- a/deploy/containers/velocity.mdx +++ b/deploy/containers/velocity.mdx @@ -31,6 +31,46 @@ FROM ghcr.io/groundsgg/containers/velocity:latest Verify that the image is available locally with `docker image inspect ghcr.io/groundsgg/containers/velocity:latest`. +## Static Backends + +The image bundles `plugin-agones`, which can register non-Agones backends through +`GROUNDS_STATIC_SERVERS`. In production, keep this value in one dedicated Helm chart as an +immutable, versioned ConfigMap. Every Java Velocity proxy must source the same required key: + +```yaml +env: + - name: GROUNDS_STATIC_SERVERS + valueFrom: + configMapKeyRef: + name: velocity-static-servers-v1 + key: GROUNDS_STATIC_SERVERS +``` + +The chart owns a ConfigMap such as `velocity-static-servers-v1`, with `immutable: true`, the +`GROUNDS_STATIC_SERVERS` key, and the Helm annotation `helm.sh/resource-policy: keep`. Its value +is a comma-separated list of `name=host:port` entries. To change the list, create a new versioned +ConfigMap, update every proxy reference, and roll out the proxies. Keep the prior version until +that rollout finishes, then garbage-collect it deliberately. Do not duplicate a literal value in +each production proxy chart. + +For local development, a literal environment value remains convenient: + +```yaml +env: + - name: GROUNDS_STATIC_SERVERS + value: "buildserver=buildserver:25565" +``` + +After the proxies restart, permitted players can connect with `/server buildserver`. See the +[Velocity plugin reference](/reference/plugins/agones/velocity#static-servers) for the parser, +startup failure behavior, and forwarding requirements. + + +The backend must use modern Velocity forwarding and the same forwarding secret as the proxies. +Static discovery does not configure Paper or make an offline-mode backend safe to expose publicly. + + ## Documentation -For more details, see the [velocity directory](https://github.com/groundsgg/containers/tree/main/velocity) in the repository. +- Configure discovery and forwarding with the [Velocity plugin reference](/reference/plugins/agones/velocity#static-servers). +- Review the [velocity directory](https://github.com/groundsgg/containers/tree/main/velocity) in the container repository. diff --git a/reference/plugins/agones/index.mdx b/reference/plugins/agones/index.mdx index 7303bb0..efbfb85 100644 --- a/reference/plugins/agones/index.mdx +++ b/reference/plugins/agones/index.mdx @@ -19,9 +19,10 @@ count: - the last player leaves → the runtime calls `Ready` - a 10 second fallback loop reconciles the state if an event is missed -On Velocity, the plugin adds two responsibilities on top of proxy-side state sync: +On Velocity, the plugin adds three responsibilities on top of proxy-side state sync: - it polls the Kubernetes API for running gameservers and registers them with the proxy +- it registers explicitly configured non-Agones backends from `GROUNDS_STATIC_SERVERS` - it routes newly joining players to a lobby server and rejects logins when no lobby is available @@ -46,7 +47,7 @@ runtime. The Velocity discovery path depends on three conventions that your gameserver deployment must follow: -- the pod runs in the `games` Kubernetes namespace +- the pod runs in the configured discovery namespace (default: `games`) - the Agones `GameServer` carries the label `grounds/server-type` with one of `lobby`, `game`, or `match` - the pod exposes Minecraft on port `25565` and reports its `PodIP` under `status.addresses` @@ -69,6 +70,18 @@ flowchart LR `grounds/server-type=lobby` must exist for new proxy logins to succeed. +## Static Backends + +Use `GROUNDS_STATIC_SERVERS` for a backend that does not run as an Agones `GameServer`, such as a +temporary buildserver deployed as a regular Kubernetes `Deployment`. Static servers join the +Velocity registry without participating in Agones lifecycle management or lobby selection. In +production, every Java Velocity proxy reads the same required value from the dedicated immutable, +versioned static-server ConfigMap; the plugin loads and caches it at startup. Local development +may provide the value directly as an environment literal. + +Continue with [Velocity](/reference/plugins/agones/velocity#static-servers) for the configuration +format, rollout and validation rules, and forwarding requirements. + ## Choose Your Platform @@ -93,6 +106,7 @@ flowchart LR Use this plugin when: - your gameserver deploys into the Grounds Agones-managed cluster +- a regular backend must be reachable through the same Velocity proxies - you want Agones state to reflect real player activity instead of a static allocation call - your gamemode publishes its role through `grounds/server-type` so the Velocity proxy can route to it diff --git a/reference/plugins/agones/velocity.mdx b/reference/plugins/agones/velocity.mdx index d50436c..d842b35 100644 --- a/reference/plugins/agones/velocity.mdx +++ b/reference/plugins/agones/velocity.mdx @@ -1,34 +1,45 @@ --- title: "Velocity" -description: "Proxy-side Agones gameserver discovery, lobby routing, and operator command" +description: "Proxy-side Agones and static server discovery, lobby routing, and operator command" --- Use the Velocity module of `plugin-agones` when you run the Grounds proxy in front of -Agones-managed gameservers. The plugin replaces the static `velocity.toml` server list with live -Kubernetes-driven discovery and routes joining players to a lobby. +Agones-managed gameservers. The plugin combines Kubernetes-driven discovery with optional static +backends and routes joining players to a lobby. ## Requirements -The plugin assumes the following environment: +For Agones discovery, the plugin expects the following environment: -- the Velocity pod runs in the `games` namespace alongside its gameservers -- the pod has read access to the Agones `gameservers.agones.dev` custom resources (`list` verb) -- an Agones sidecar runs next to the Velocity container and exposes the SDK on - `http://localhost:9358` +- `GROUNDS_AGONES_NAMESPACE` or `POD_NAMESPACE` identifies the namespace to query; the fallback is + `games` +- `GROUNDS_AGONES_ADDRESS_TYPE` must exactly match a type reported in + `GameServer.status.addresses`; common values are `PodIP`, `ExternalIP`, `InternalIP`, and + `Hostname`, with `PodIP` as the default +- `GROUNDS_AGONES_PORT` selects the backend TCP port; the default is `25565` +- the pod can list Agones `gameservers.agones.dev` resources in that namespace +- the pod can read gameserver pods when a `GameServer` status does not contain the configured + address type -The plugin unregisters every server defined in `velocity.toml` during startup. Your `velocity.toml` -`[servers]` section is effectively ignored once the plugin is loaded. +The Grounds Velocity image contains three example backends: `lobby`, `factions`, and `minigames`. +The plugin removes only those exact loopback placeholders during startup. It preserves every other +Velocity registration. +The proxy needs an Agones SDK sidecar only when the proxy itself runs as an Agones `GameServer`. +Without the sidecar, backend discovery and routing still work; only proxy self-state management is +disabled. + ## What the Plugin Provides -Once the plugin starts, it runs two responsibilities in parallel on the proxy: +Once the plugin starts, it runs these responsibilities on the proxy: -- `GameServerStateManager` syncs the proxy's own Agones state (`Ready` or `Allocated`) with the - connected player count +- `DiscoveryService` registers configured static backends - `DiscoveryService` polls Kubernetes every two seconds and keeps Velocity's server registry in sync with the running gameservers +- `GameServerStateManager` syncs the proxy's own Agones state (`Ready` or `Allocated`) with the + connected player count when an SDK sidecar is present Every discovered gameserver is registered under its Kubernetes resource name. The server's role is read from the `grounds/server-type` label and cached for routing and the `/agones` command. @@ -38,14 +49,15 @@ read from the `grounds/server-type` label and cached for routing and the `/agone For a gameserver pod to appear in Velocity, it must satisfy the discovery contract: - -The discovery query is scoped to the `games` namespace. Gameservers outside that namespace are -ignored. + +The discovery query is scoped to `GROUNDS_AGONES_NAMESPACE` or `POD_NAMESPACE`; if neither is set, +it defaults to `games`. Gameservers outside that namespace are ignored. Set `grounds/server-type` to `lobby`, `game`, or `match` on the `GameServer` resource metadata. -Unlabeled gameservers are skipped. +Unlabeled gameservers are skipped. This example uses the fallback namespace `games`; set +`metadata.namespace` to the configured discovery namespace when it differs. ```yaml apiVersion: agones.dev/v1 @@ -58,9 +70,12 @@ metadata: ``` - -The proxy registers the pod at `PodIP:25565`. The plugin does not honor Agones allocated ports — -the gameserver must listen on the default Minecraft port inside the pod. + +The proxy reads the address whose type matches `GROUNDS_AGONES_ADDRESS_TYPE` and connects to +`GROUNDS_AGONES_PORT`. These default to `PodIP` and `25565`. If the selected address is missing from +`GameServer.status.addresses`, the plugin falls back to the gameserver pod IP when Kubernetes +exposes one; otherwise the gameserver is skipped. The plugin does not honor Agones allocated ports, +so the gameserver must listen on the configured fixed port inside the pod. @@ -74,6 +89,85 @@ If your gameserver follows all four steps, it appears in `/agones` on the proxy of reaching a running state. +## Static Servers + +Use a static server when a backend runs as a regular Kubernetes `Deployment` or outside Agones. +In production, a dedicated Helm chart owns one immutable, versioned ConfigMap for every Java +Velocity proxy. The ConfigMap is retained with `helm.sh/resource-policy: keep`, allowing the old +and new versions to overlap during a rollout: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: velocity-static-servers-v1 + annotations: + helm.sh/resource-policy: keep +immutable: true +data: + GROUNDS_STATIC_SERVERS: "buildserver=buildserver:25565" +``` + +Each proxy must use a mandatory reference to that same key: + +```yaml +env: + - name: GROUNDS_STATIC_SERVERS + valueFrom: + configMapKeyRef: + name: velocity-static-servers-v1 + key: GROUNDS_STATIC_SERVERS +``` + +The plugin reads and caches the value at startup. To change it, create a new ConfigMap name (for +example, `velocity-static-servers-v2`), update every proxy reference, and roll out all proxies. +Only garbage-collect the retained old version after the rollout completes. Do not set independent +literal values on production proxies. + +For local development, set `GROUNDS_STATIC_SERVERS` directly. The value is a comma-separated list +of `name=host:port` entries: + +```text +buildserver=buildserver:25565,events=events.stage.svc.cluster.local:25566 +``` + +The plugin trims surrounding whitespace and validates the complete value at startup. Each entry +must have a non-empty name and host, and its port must be between `1` and `65535`. Server names are +case-insensitive; duplicates such as `BuildServer` and `buildserver` fail configuration. + +Static servers have the role `static`. They appear in Velocity's `/server` command and in +`/agones`, but they are never selected as login lobbies. If a static server and an Agones +`GameServer` use the same name, the static server takes precedence. + + +Static registration configures routing only. A Paper backend behind Velocity must also use modern +Velocity forwarding with the same forwarding secret as the proxies. Do not expose an offline-mode +backend through a public `LoadBalancer`. + + + +After all proxies restart, use `/server buildserver` to connect to the example backend. The player +still needs Velocity's `velocity.command.server` permission. + + +## Drain Routing + +During an automatic drain, the proxy defers a move only for players on a backend whose current +role is `game` or `match`. Players on `static`, `lobby`, an unknown role, or a backend with no role +are moved immediately. + +For an automatically drained Java player who is currently on a static backend, the source proxy +signs a short-lived, namespaced Minecraft transfer cookie with HMAC using the existing shared +Velocity forwarding secret. It requests and receives an exact client echo of that cookie before it +initiates the transfer. The target proxy validates the cookie signature and TTL, then uses the named +backend directly only if it is still registered with the `static` role. If the echo does not arrive +within one second, or the cookie is missing, expired, invalid, or names an unknown backend, the +transfer falls back to normal routing and therefore to a lobby when one is available. This requires +Java 1.20.5 or later, matching the existing host-transfer minimum. Bedrock drain remains disabled. + +The preservation applies only to automatic drain. Generic `/region` moves and other manual proxy +moves do not preserve the current backend. + ## Lobby Routing The plugin treats `grounds/server-type=lobby` as the entry point for new connections. @@ -107,7 +201,8 @@ The plugin registers an operator command for inspecting the current proxy state. | Output scope | all registered proxy servers | The command prints every registered server with its role, address, and connected player count. -Roles are color-coded: `lobby` (green), `game` (aqua), `match` (light purple), `unknown` (gray). +Roles include `lobby`, `game`, `match`, and `static`; registrations without a known role appear as +`unknown`. Example output: @@ -133,10 +228,9 @@ issue redundant calls to the sidecar. -If the plugin cannot build a Kubernetes client at startup, discovery stays disabled for the -process lifetime. The plugin logs the reason and continues without unregistering configured -servers, registering lobby-routing listeners, or polling Kubernetes. Existing `velocity.toml` -servers remain registered until the proxy restarts with a working Kubernetes configuration. +If the plugin cannot build a Kubernetes client at startup, Agones discovery stays disabled for the +process lifetime. The plugin still removes the exact image placeholders, registers +`GROUNDS_STATIC_SERVERS`, and installs the routing listeners. @@ -145,8 +239,21 @@ routing continue to work because they depend on the Kubernetes API, not on the s -Gameservers without a `PodIP` entry in `status.addresses` are skipped and logged as -`missing_pod_ip`. This usually clears up on the next poll once Agones populates the status. +If the configured address type is missing from `GameServer.status.addresses`, the plugin reads the +same-named pod and uses its pod IP. If neither source provides an address, the server is skipped and +retried during the next poll. + + + +The plugin rejects malformed entries and duplicate names during initialization. Correct +`GROUNDS_STATIC_SERVERS` and restart the proxy. An absent or blank value disables static +registration. + + + +In production the environment reference is mandatory, so a missing ConfigMap or key prevents the +proxy pod from starting. Restore the referenced version or update all proxy references to a valid +versioned ConfigMap, then roll out the affected proxies.