This repository contains the code and configuration for a Kubernetes operator that manages game servers and their deployments.
It includes custom resource definitions (CRDs), controllers, and supporting manifests to handle scaling, updates, and lifecycle management.
For more details, see DesignSummary.md and RepoOverview.md.
- Install WSL2 (Ubuntu) and Docker Desktop : enable WSL 2 based engine and WSL Integration for Ubuntu.
- In WSL:
sudo apt update && sudo apt install -y make curl git ca-certificates
# Go
wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz && sudo tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz
echo 'export PATH=/usr/local/go/bin:$PATH' >> ~/.profile && source ~/.profile
# kubectl + kind
curl -Lo kind https://kind.sigs.k8s.io/dl/v0.30.0/kind-linux-amd64 && chmod +x kind && sudo mv kind /usr/local/bin/
curl -Lo kubectl https://dl.k8s.io/release/v1.34.0/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# kubebuilder
curl -L https://go.kubebuilder.io/dl/latest/linux/amd64 | tar -xz -C /tmp && sudo mv /tmp/kubebuilder_*_linux_amd64 /usr/local/kubebuilder
echo 'export PATH=/usr/local/kubebuilder/bin:$PATH' >> ~/.profile && source ~/.profile
- Fix Docker-in-WSL if needed:
sudo usermod -aG docker $USER && newgrp docker
mkdir gameserver-operator && cd $_
git init
go mod init github.com/<you>/gameserver-operator
kubebuilder init --domain=example.com --repo=github.com/<you>/gameserver-operator
kubebuilder create api --group=game --version=v1alpha1 --kind=GameServer --resource --controller
kubebuilder create api --group=game --version=v1alpha1 --kind=GSDeployment --resource --controller
API package tips
api/v1alpha1/doc.go:
// +kubebuilder:object:generate=true
// +groupName=game.example.com
package v1alpha1groupversion_info.goregisters GameServer, GameServerList, GSDeployment, GSDeploymentList.- Short names:
//+kubebuilder:resource:shortName=gs // GameServer
//+kubebuilder:resource:shortName=gsd // GSDeploymentGenerate & install CRDs to current cluster
make generate
make manifests
make install
- GameServer controller
- Ensures one Pod (hostNetwork: true) per GameServer; injects
GAME_PORTfromspec.port; readiness probe/status. - Every 10s polls
http://<hostIP>:<port>/status; updates.status.players/.maxPlayers/.phase/.zeroSince+Reachablecondition.
- Ensures one Pod (hostNetwork: true) per GameServer; injects
- GSDeployment controller
- Ensures
minReplicas; allocates unique ports from[30000, 32000](configurable). - Scale up when any GS ≥ threshold (default 80%); add one up to
maxReplicas. - Scale down GS idle (
players==0) for > N sec (default 60), not belowminReplicas. - Reacts to GameServer status updates (event-driven).
- Ensures
kind create cluster
make install # CRDs
make docker-build IMG=gameserver-operator:dev
kind load docker-image gameserver-operator:dev
make deploy IMG=gameserver-operator:dev
kubectl -n gameserver-operator-system get deploy,pods
Sample GSDeployment (namespace games)
apiVersion: game.example.com/v1alpha1
kind: GSDeployment
metadata:
name: shooter-fleet
namespace: games
spec:
image: kyon/gameserver:latest
pollPath: /status
portRange: { start: 30000, end: 30005 }
minReplicas: 2
maxReplicas: 5
scaleUpThresholdPercent: 80
scaleDownZeroSeconds: 60
updateStrategy:
type: NoDisruption
drainTimeoutSeconds: 7200
maxSurge: 2
maxUnavailable: 0
parameters:
maxPlayers: 32
Run:
kubectl create ns games
kubectl apply -f gsd.yaml
kubectl -n games get gsd,gs,pods -o wide
Point kubectl to EKS
aws eks update-kubeconfig --region us-east-1 --name <CLUSTER>
Install CRDs
make manifests && make install
kubectl get crd | grep game.example.com
Build & push operator image to ECR
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=us-east-1
REPO=gameserver-operator
ECR_URI=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$REPO:eks
aws ecr create-repository --repository-name $REPO --region $REGION
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
docker build -t $ECR_URI .
docker push $ECR_URI
Deploy operator
make deploy IMG=$ECR_URI
kubectl -n gameserver-operator-system get deploy,pods
Deploy GSDeployment
kubectl apply -f gsd.yaml
kubectl -n games get gsd,gs,pods -o wide
Verbose logs
NS=gameserver-operator-system
kubectl -n $NS logs deploy/gameserver-operator-controller-manager -c manager --since=10m
Events
kubectl -n $NS get events --sort-by=.lastTimestamp | tail -n 50
RBAC checks
SA=$(kubectl -n $NS get deploy/gameserver-operator-controller-manager -o jsonpath='{.spec.template.spec.serviceAccountName}')
kubectl auth can-i create pods -n games --as=system:serviceaccount:$NS:$SA
kubectl auth can-i update gameservers/status -n games --as=system:serviceaccount:$NS:$SA
Run locally
make run # runs manager against current kubeconfig
- Docker not available in WSL : Enable Docker Desktop WSL Integration;
wsl --shutdown; add user todockergroup. - Permission denied /var/run/docker.sock :
usermod -aG docker $USER && newgrp docker. - Module path mismatch in Docker build :
go.modmodule github.com/<you>/gameserver-operator; imports must match; Dockerfile must copyapi/,internal/,cmd/; check.dockerignore. - Deepcopy not generated : all API files
package v1alpha1;doc.gomarkers;groupversion_info.goregisters all kinds;make generate(temporary: manual DeepCopy methods file). - Error "no kind registered in scheme" : add
utilruntime.Must(v1alpha1.AddToScheme(scheme))in main. - Controller not reconciling : ensure
cmd/main.goregisters BOTH reconcilers viaSetupWithManager; ensure Dockerfile buildscmd/main.goand copiesinternal/. - Image drift : rebuild,
kind load docker-image,kubectl set imageand rollout restart.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gs-manual
namespace: games
spec:
replicas: 1
selector:
matchLabels:
app: gs-manual
template:
metadata:
labels:
app: gs-manual
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: server
image: kyon/gameserver:latest
env:
- name: GAME_PORT
value: "30001"
ports:
- containerPort: 30001
readinessProbe:
httpGet:
path: /status
port: 30001
initialDelaySeconds: 2
periodSeconds: 5
timeoutSeconds: 2