Kubernetes Deployment
This guide covers deploying Passage on Kubernetes, from basic setups to production-grade configurations with Agones integration.
Prerequisites
Section titled “Prerequisites”- Kubernetes cluster (1.25+)
kubectlconfigured- (Optional) Helm 3.x for chart-based deployment
- (Optional) Agones installed for game server discovery
Quick Start with Helm (Recommended)
Section titled “Quick Start with Helm (Recommended)”The official Helm chart is the easiest way to deploy Passage:
helm install passage oci://ghcr.io/scrayosnet/helm/passage \ --version 0.3.0 \ --namespace passage --create-namespaceTo expose Passage via a cloud load balancer:
helm install passage oci://ghcr.io/scrayosnet/helm/passage \ --version 0.3.0 \ --namespace passage --create-namespace \ --set service.type=LoadBalancerHelm with Custom Configuration
Section titled “Helm with Custom Configuration”Create a values.yaml to configure Passage and enable Agones:
service: type: LoadBalancer
config: # Passage application configuration goes here # See Reference > Configuration for all options
rbac: agones: enabled: true gameserverNamespace: minecrafthelm install passage oci://ghcr.io/scrayosnet/helm/passage \ --version 0.3.0 \ --namespace passage --create-namespace \ -f values.yamlAll available options are documented in helm/values.yaml.
Manual Deployment
Section titled “Manual Deployment”ConfigMap
Section titled “ConfigMap”apiVersion: v1kind: ConfigMapmetadata: name: passage-config namespace: passagedata: config.yaml: | address: "0.0.0.0:25565" timeout: 120
rate_limiter: duration: 60 limit: 60
routes: - hostname: "mc.example.net" status: type: fixed name: "My Kubernetes Network" description: "\"Powered by Passage\"" discovery: type: fixed_discovery targets: - identifier: "hub-1" address: "hub-service.minecraft:25565"Deployment
Section titled “Deployment”apiVersion: apps/v1kind: Deploymentmetadata: name: passage namespace: passagespec: replicas: 2 selector: matchLabels: app: passage template: metadata: labels: app: passage spec: containers: - name: passage image: ghcr.io/scrayosnet/passage:latest ports: - containerPort: 25565 protocol: TCP resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" volumeMounts: - name: config mountPath: /app/config readOnly: true env: - name: RUST_LOG value: "info" volumes: - name: config configMap: name: passage-configService
Section titled “Service”apiVersion: v1kind: Servicemetadata: name: passage namespace: passagespec: type: LoadBalancer externalTrafficPolicy: Local # Preserve client IP ports: - port: 25565 targetPort: 25565 protocol: TCP name: minecraft selector: app: passageDeploy:
kubectl apply -f passage-deployment.yamlkubectl get svc passage -n passage # Get external IPProduction Configuration
Section titled “Production Configuration”High Availability
Section titled “High Availability”apiVersion: apps/v1kind: Deploymentmetadata: name: passage namespace: passagespec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 # Zero-downtime updates selector: matchLabels: app: passage template: metadata: labels: app: passage spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: [passage] topologyKey: kubernetes.io/hostname containers: - name: passage image: ghcr.io/scrayosnet/passage:v0.1.24 ports: - containerPort: 25565 protocol: TCP resources: requests: memory: "128Mi" cpu: "200m" limits: memory: "512Mi" cpu: "1000m" livenessProbe: tcpSocket: port: 25565 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: tcpSocket: port: 25565 initialDelaySeconds: 5 periodSeconds: 10 volumeMounts: - name: config mountPath: /app/config readOnly: true - name: auth-secret mountPath: /run/secrets readOnly: true env: - name: RUST_LOG value: "info" - name: AUTH_SECRET_FILE value: "/run/secrets/auth-secret" volumes: - name: config configMap: name: passage-config - name: auth-secret secret: secretName: passage-auth-secretAuthentication Secret
Section titled “Authentication Secret”openssl rand -base64 32 > auth_secretkubectl create secret generic passage-auth-secret \ --from-file=auth-secret=auth_secret \ -n passagerm auth_secretResource Guidelines
Section titled “Resource Guidelines”| Scenario | CPU Request | CPU Limit | Memory Request | Memory Limit |
|---|---|---|---|---|
| Small (<100 players/min) | 100m | 500m | 64Mi | 256Mi |
| Medium (<500 players/min) | 200m | 1000m | 128Mi | 512Mi |
| Large (<2000 players/min) | 500m | 2000m | 256Mi | 1Gi |
Agones Integration
Section titled “Agones Integration”RBAC Permissions
Section titled “RBAC Permissions”When using the Helm chart, set rbac.agones.enabled: true — the chart creates the necessary ClusterRole and RoleBinding automatically.
For manual setup:
apiVersion: v1kind: ServiceAccountmetadata: name: passage namespace: passage---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: passagerules:- apiGroups: [""] resources: ["events"] verbs: ["create", "patch"]- apiGroups: ["agones.dev"] resources: ["gameservers"] verbs: ["list", "watch", "patch"]- apiGroups: ["allocation.agones.dev"] resources: ["gameserverallocations"] verbs: ["create"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: passage namespace: minecraft # Namespace where GameServers aresubjects:- kind: ServiceAccount name: passage namespace: passageroleRef: kind: ClusterRole name: passage apiGroup: rbac.authorization.k8s.ioPassage Configuration for Agones
Section titled “Passage Configuration for Agones”apiVersion: v1kind: ConfigMapmetadata: name: passage-config namespace: passagedata: config.yaml: | address: "0.0.0.0:25565" timeout: 120
rate_limiter: duration: 60 limit: 100
routes: - hostname: "mc.example.net" status: type: http address: "http://status-service.minecraft/status" cache_duration: 30 discovery: type: agones_discovery namespace: "minecraft" selectors: - matchLabels: game: "minecraft" type: "lobby" scheduling: "Packed" actions: - type: player_fill_strategy name: "fill" field: "players" max_players: 50Example GameServer Fleet
Section titled “Example GameServer Fleet”apiVersion: "agones.dev/v1"kind: Fleetmetadata: name: minecraft-lobbies namespace: minecraftspec: replicas: 5 template: metadata: labels: game: minecraft type: lobby spec: ports: - name: minecraft containerPort: 25565 protocol: TCP counters: players: count: 0 capacity: 50 template: spec: containers: - name: minecraft image: itzg/minecraft-server:latest env: - name: EULA value: "TRUE" - name: TYPE value: "PAPER"PROXY Protocol
Section titled “PROXY Protocol”If your load balancer sends PROXY protocol headers:
# In the Passage configproxy_protocol: allow_v1: true allow_v2: trueAWS NLB example:
apiVersion: v1kind: Servicemetadata: name: passage namespace: passage annotations: service.beta.kubernetes.io/aws-load-balancer-type: "nlb" service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"spec: type: LoadBalancer externalTrafficPolicy: Local ports: - port: 25565 targetPort: 25565 protocol: TCP selector: app: passageOpenTelemetry
Section titled “OpenTelemetry”# In the Passage configotel: environment: "production" traces: address: "http://tempo.monitoring:4318/v1/traces" metrics: address: "http://mimir.monitoring:4318/v1/metrics"Horizontal Pod Autoscaling
Section titled “Horizontal Pod Autoscaling”apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: passage namespace: passagespec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: passage minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70Troubleshooting
Section titled “Troubleshooting”Pods Not Starting
Section titled “Pods Not Starting”kubectl get pods -n passagekubectl logs -n passage -l app=passagekubectl describe pod -n passage <pod-name>Can’t Connect from Minecraft
Section titled “Can’t Connect from Minecraft”kubectl get svc passage -n passagekubectl port-forward -n passage svc/passage 25565:25565 # Test locallyAgones Discovery Not Working
Section titled “Agones Discovery Not Working”# Check RBAC permissionskubectl auth can-i watch gameservers \ --as=system:serviceaccount:passage:passage \ -n minecraft
# Check GameServers existkubectl get gameservers -n minecraft
# Check Passage logskubectl logs -n passage -l app=passage | grep agonesBest Practices
Section titled “Best Practices”- Use specific image tags (not
latest) for reproducible deployments - Run at least 2 replicas with pod anti-affinity for high availability
- Use
externalTrafficPolicy: Localto preserve client IP addresses - Store the auth secret in a Kubernetes Secret, not in the ConfigMap
- Enable rate limiting in production
- Configure liveness and readiness probes
- Set appropriate resource requests and limits
- Use RBAC with minimal permissions