Files
CosmicClash/deploy/k8s/base/fleet.yaml
T
Josh Creek ca70568fad fix(agones): make the kind gate's Agones lifecycle actually work
Several independent causes, all of which had to be right before the
Fleet could reach Ready.

The supervisor pointed --sdk-base-url at 127.0.0.1:9357, which is the
Agones sidecar's gRPC port; its HTTP surface is 9358, and that is what
AGONES_SDK_HTTP_PORT carries and what agones_sdk.gd reads. An HTTP
client against the gRPC port could never have worked, in kind or in
production.

The supervisor also treated the sidecar's first incomplete /gameserver
response as fatal. The sidecar accepts requests before the controller
populates status.address and status.ports, so this produced a restart
loop precisely during normal Agones startup. It now polls until the
endpoint is assigned or ReadyTimeout elapses.

server_boot.gd started ServerControl and the Agones SDK only under
--allocated-mode, but the kind smoke deliberately strips that flag, so
nothing served the readiness probe and the GameServer could never become
Ready. Lifecycle now keys on AGONES_SDK_HTTP_PORT, which Agones injects
into every managed container, while allocation and roster semantics stay
tied to --allocated-mode. The SDK node is added to the tree
non-deferred, since start_health() creates a Timer immediately.

Fleet: Agones assigns its own SDK service account and masks that token
from the game container while keeping it for the injected sidecar, so
the manifest must not pin serviceAccountName or
automountServiceAccountToken. Godot stores user:// under HOME, so HOME
points at the writable runtime volume to keep the root filesystem
read-only, and fsGroup makes that volume writable for the non-root user.

Namespace: Agones' Dynamic port policy injects a hostPort, which both
the baseline and restricted Pod Security Standards forbid, so the
workload namespace enforces privileged while continuing to audit and
warn against restricted.

NetworkPolicy: the injected sidecar reaches the Kubernetes API over
HTTPS, and NetworkPolicy applies to the whole Pod rather than to the
container whose token was masked.

The kind runner creates the namespace before Helm so Agones can install
its per-namespace SDK RBAC, scopes gameservers.namespaces to it, forces
the allocator and ping Services to ClusterIP because LoadBalancer
ingress never becomes ready in plain kind, and labels the node so the
production Fleet's on-demand/zone constraints are exercised rather than
edited out of the rendered manifest.
2026-09-05 20:50:01 +01:00

136 lines
5.4 KiB
YAML

apiVersion: agones.dev/v1
kind: Fleet
metadata:
name: cosmic-clash-game
namespace: cosmic-clash
labels:
app.kubernetes.io/name: game-fleet
spec:
replicas: 2
strategy:
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/name: game-server
cosmic-clash.io/region: EU
cosmic-clash.io/build: build-1
cosmic-clash.io/protocol: "1"
cosmic-clash.io/transport: enet
annotations:
# The release process replaces this with the immutable image digest;
# the Downward API passes the same value to the supervisor so the
# allocated child can validate its assignment manifest.
cosmic-clash.io/image-digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
spec:
ports:
- name: game
containerPort: 7777
protocol: UDP
health:
disabled: false
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
template:
spec:
nodeSelector:
cosmic-clash.io/capacity-type: on-demand
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: game-server
# Leave serviceAccountName unset: Agones assigns its SDK account and
# masks that account's token from this public game-server container,
# while retaining it in the injected SDK sidecar that needs API access.
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: game-server
image: ghcr.io/cosmic-clash/game-server@sha256:0000000000000000000000000000000000000000000000000000000000000000
args:
- --sdk-base-url=http://127.0.0.1:9358
- --ready-url=http://127.0.0.1:7780/ready
- --drain-url=http://127.0.0.1:7780/drain
- --initial-connect-ready-url=http://127.0.0.1:7780/initial-connect-ready
- --drain-token-env=COSMIC_CLASH_DRAIN_TOKEN
- --control-plane-url=http://control-plane.cosmic-clash.svc.cluster.local:8080
- --server-id-env=COSMIC_CLASH_SERVER_ID
- --image-digest-env=COSMIC_CLASH_IMAGE_DIGEST
- --roster-path=/run/cosmic-clash/join-roster.json
- --transport=enet
- --protocol-version=1
- --
- /opt/cosmic-clash/CosmicClashServer.x86_64
- --allocated-mode
- --match-id=allocation-placeholder
- --server-id=allocation-placeholder
- --playlist-version=casual
- --playlist=casual
- --arena-path=
- --client-build=build-1
- --assignment-expiry-unix=1
- --server-image-digest=sha256:0000000000000000000000000000000000000000000000000000000000000000
- --transport=enet
- --region=EU
- --join-authorisations-file=/run/cosmic-clash/join-roster.json
# The key SET, not one key: an allocated server must accept
# authorisations signed with any currently-valid key so a
# rotation does not break matches already in flight.
- --join-authorisations-key-file=/run/secrets/cosmic-clash/join-signing-keys.json
- --readiness-port=7780
env:
# Godot stores user:// beneath HOME. Point it at the writable
# runtime volume while retaining a read-only root filesystem.
- name: HOME
value: /run/cosmic-clash
- name: COSMIC_CLASH_SERVER_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: COSMIC_CLASH_IMAGE_DIGEST
valueFrom:
fieldRef:
fieldPath: metadata.annotations['cosmic-clash.io/image-digest']
- name: COSMIC_CLASH_DRAIN_TOKEN
valueFrom:
secretKeyRef:
name: cosmic-clash-game-server
key: drain-token
volumeMounts:
- name: allocated-roster
mountPath: /run/cosmic-clash
- name: join-signing-key
mountPath: /run/secrets/cosmic-clash
readOnly: true
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1
memory: 512Mi
volumes:
- name: allocated-roster
emptyDir: {}
- name: join-signing-key
secret:
secretName: cosmic-clash-game-server
items:
- key: join-signing-keys.json
path: join-signing-keys.json