Skip to content

Environment variables, and the one way to reach the JVM

A ServerGroup and a ProxyGroup each take spec.env, an ordinary list of Kubernetes EnvVars that is appended to the ones the operator sets:

apiVersion: spawnery.cloud/v1alpha1
kind: ServerGroup
metadata:
  name: bingo-solo
spec:
  # ...
  env:
    - name: JAVA_TOOL_OPTIONS
      value: "-Dgame.amountOfTeams=0"

valueFrom works too — a secretKeyRef is the right way to hand a plugin an API token, and it is no wider a door than spec.mounts already is: a namespace is one trust domain, and anybody who can write a group can already mount any Secret in it.

Why this is how you set a JVM flag

Neither entrypoint takes arguments from any spec. Both build their own java command line — MaxRAMPercentage, G1 tuning, AlwaysPreTouch unless the container is unbounded — and exec it, so that the JVM is PID 1 and receives SIGTERM directly. Threading a per-group argument list through that would mean a group could replace those flags, and the first thing anybody would replace is the memory sizing that the group's own resources.limits.memory is supposed to govern.

JAVA_TOOL_OPTIONS is the seam the JVM itself offers, and it has the precedence this needs. Measured on OpenJDK 21.0.12:

JAVA_TOOL_OPTIONS command line what the process got
-Dfoo fromenv fromenv
-Dfoo fromenv fromcmd fromcmd
-Xmx 100m 200m 200 MB

So a group adds what the entrypoint does not set, and cannot displace what it does. The JVM prints Picked up JAVA_TOOL_OPTIONS: … on stderr at every start, which is worth knowing before somebody files it as a warning.

It carries JVM options only. A Paper or Velocity program argument — --world-dir, --nogui — is not reachable this way and is not reachable at all; those are the entrypoint's.

The reserved prefix

A name may not begin with SPAWNERY_. That prefix is the operator's: it writes SPAWNERY_NETWORK, SPAWNERY_GROUP, SPAWNERY_SERVER or SPAWNERY_PROXY, the agent's endpoint, and for a proxy its player limit and fallback groups. The agent reads them to know what it is and whom to call.

Kubernetes does not refuse a duplicate name in a container's env list — it keeps both entries and the last one wins. A group shadowing SPAWNERY_GROUP would therefore be admitted, and kubectl describe pod would print both values with nothing saying which one the process read. A CEL rule on the CRD refuses the prefix at admission instead, so the error lands on the object somebody just wrote:

The ServerGroup "lobby" is invalid: spec.env: Invalid value: "array":
the SPAWNERY_ prefix is reserved for the environment variables the
operator sets itself

The prefix is reserved whole rather than the individual names being denied, so a variable added in a later release cannot collide with one an installation already set. It also covers SPAWNERY_PLUGIN_SOURCE and SPAWNERY_CGROUP_ROOT, which exist so the image tests can point them at a temporary directory and would break a start if a group moved them.

The list is a map keyed by name, so the same name twice is refused as well.

Editing it replaces the group's servers

spec.env shapes the pod, so it is part of podspec.DesiredServerHash and DesiredProxyHash. Changing it makes every server of the group stale and rolls them exactly the way an image bump does, through maxUnavailable and the cold start. Nobody is kicked; it still costs a changeover.

That is the opposite of extraPlugins, whose contents reach no hash at all. The difference is not a preference: the operator holds a claim name and cannot digest a filesystem, while an env list it renders itself it can. If you want a value you can change without rolling anything, it belongs on the volume or in a config the plugin re-reads — not here.