Overview
Agones provides four port allocation policies that control how ports are assigned to game servers: Dynamic, Static, Passthrough, and None. Each policy serves different use cases and has distinct performance and operational characteristics.
Port Policies
From the Agones source code (pkg/apis/agones/v1/gameserver.go:78-92):
// PortPolicy is the port policy for the GameServer
type PortPolicy string
const (
// Static PortPolicy means that the user defines the hostPort to be used
// in the configuration.
Static PortPolicy = "Static"
// Dynamic PortPolicy means that the system will choose an open
// port for the GameServer in question
Dynamic PortPolicy = "Dynamic"
// Passthrough dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
// This will mean that users will need to lookup what port has been opened through the server side SDK.
Passthrough PortPolicy = "Passthrough"
// None means the `hostPort` is ignored and if defined, the `containerPort` (optional) is used to set the port on the GameServer instance.
None PortPolicy = "None"
)
Dynamic Port Policy
How It Works
The default policy where Agones automatically allocates an available host port from the configured range:
- Port allocator finds an unused port in the range (default: 7000-8000)
- Assigns the port as
hostPort
- You define a fixed
containerPort in your GameServer spec
- Traffic to
hostPort forwards to containerPort
Configuration
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: dynamic-gameserver
spec:
ports:
- name: default
portPolicy: Dynamic # System allocates hostPort
containerPort: 7654 # Your game server listens here
protocol: UDP
template:
spec:
containers:
- name: game-server
image: my-game:latest
After Allocation
status:
ports:
- name: default
port: 7234 # Dynamically allocated hostPort
state: Ready
address: 203.0.113.42
nodeName: game-node-1
Clients connect to 203.0.113.42:7234, which forwards to container port 7654.
Implementation
From pkg/portallocator/portallocator.go:178-283, the allocator manages dynamic ports:
// Allocate assigns a port to the GameServer and returns it.
func (pa *portRangeAllocator) Allocate(gs *agonesv1.GameServer) *agonesv1.GameServer {
pa.mutex.Lock()
defer pa.mutex.Unlock()
findOpenPorts := func(amount int) []pn {
var ports []pn
if amount <= 0 {
return ports
}
for _, n := range pa.portAllocations {
for p, taken := range n {
if !taken {
ports = append(ports, pn{pa: n, port: p})
if len(ports) == amount {
return ports
}
}
}
}
return ports
}
// ... allocates and marks ports as taken
}
Use Cases
- Most common use case
- Standard multiplayer game servers
- Multiple game server instances per node
- When you control the client connection logic
Advantages
- No port conflicts
- Maximum density per node
- Simple configuration
- Automatic port management
Disadvantages
- Requires port range configuration
- Clients must query allocated port
- Small allocation overhead
Static Port Policy
How It Works
You manually specify both hostPort and containerPort:
- You choose the exact
hostPort to use
- Agones does not manage port allocation
- You are responsible for avoiding conflicts
- Best for predictable, well-known ports
Configuration
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: static-gameserver
spec:
ports:
- name: default
portPolicy: Static
hostPort: 7777 # Fixed, must be available
containerPort: 7654
protocol: UDP
template:
spec:
containers:
- name: game-server
image: my-game:latest
After Creation
status:
ports:
- name: default
port: 7777 # Exactly as specified
state: Ready
address: 203.0.113.42
Clients always connect to 203.0.113.42:7777.
Use Cases
- Legacy games expecting specific ports
- Single game server per node
- Development/testing with predictable ports
- Integration with external systems requiring fixed ports
Advantages
- Predictable port numbers
- No dynamic allocation overhead
- Simpler client configuration
- No port range limits
Disadvantages
- Manual port management required
- Risk of port conflicts
- Limited scalability (one port per node)
- Not suitable for high-density deployments
Static ports limit you to one GameServer per port per node. Ensure you have enough nodes for your desired scale.
Passthrough Port Policy
How It Works
Combines dynamic allocation with container port binding:
- Agones allocates a dynamic
hostPort
- Automatically sets
containerPort to match hostPort
- Your game server must query the SDK to discover its port
- Game server binds to the assigned port
Configuration
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: passthrough-gameserver
spec:
ports:
- name: default
portPolicy: Passthrough # containerPort = hostPort (dynamic)
# No containerPort specified - will be set dynamically
protocol: UDP
template:
spec:
containers:
- name: game-server
image: my-game:latest
After Allocation
status:
ports:
- name: default
port: 7234 # Both hostPort and containerPort are 7234
state: Ready
Game Server Implementation
import "agones.dev/agones/sdks/go"
sdk, err := sdk.NewSDK()
if err != nil {
log.Fatal(err)
}
// Get the allocated port
gs, err := sdk.GameServer()
if err != nil {
log.Fatal(err)
}
port := gs.Status.Ports[0].Port // The assigned port
// Bind to the assigned port
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
Implementation Details
From pkg/portallocator/portallocator.go:243-245:
if p.PortPolicy == agonesv1.Passthrough {
gs.Spec.Ports[i].ContainerPort = a.port
}
The allocator sets the containerPort to match the dynamically allocated hostPort.
Use Cases
- Game servers that need to know their external port
- When game server advertises its port to matchmaking
- Peer-to-peer game architectures
- WebRTC or similar protocols
Advantages
- No port conflicts
- Game server knows its external port
- No port forwarding translation
- Useful for NAT traversal scenarios
Disadvantages
- Game server must query SDK
- Cannot bind to port before SDK initialization
- Slightly more complex game server code
None Port Policy
How It Works
Beta feature (FeaturePortPolicyNone) that bypasses host port mapping entirely:
- No
hostPort is assigned
- Only
containerPort is used (optional)
- Clients connect directly to pod IP
- Bypasses kube-proxy and iptables
Configuration
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: none-gameserver
spec:
ports:
- name: default
portPolicy: None
containerPort: 7654 # Optional, for documentation
protocol: UDP
template:
spec:
containers:
- name: game-server
image: my-game:latest
After Creation
status:
ports:
- name: default
port: 7654 # containerPort, not a hostPort
state: Ready
address: 10.244.1.42 # Pod IP, not node IP
nodeName: game-node-1
Clients connect directly to pod IP: 10.244.1.42:7654.
Use Cases
- Ultra-low latency requirements
- Private clusters with direct pod network access
- Service mesh integration
- Development environments
Advantages
- Lowest possible latency (no kube-proxy)
- No port allocation overhead
- Direct pod-to-pod communication
- No host port limits
Disadvantages
- Requires pod network accessibility
- Not suitable for public internet
- Limited to certain network plugins
- Beta feature, may change
PortPolicy None requires clients to access the pod network directly. This typically only works within the same cluster or with specialized networking.
Port Policy Comparison
| Policy | Host Port | Container Port | Port Range | Client Connects To | Best For |
|---|
| Dynamic | Auto-assigned | Fixed | Required | Node IP:HostPort | Standard use |
| Static | User-defined | Fixed | Not needed | Node IP:HostPort | Legacy/single server |
| Passthrough | Auto-assigned | Same as HostPort | Required | Node IP:HostPort | NAT traversal |
| None | Not used | Optional | Not needed | Pod IP:ContainerPort | Low latency |
Multi-Port Configuration
You can mix policies in a single GameServer:
apiVersion: agones.dev/v1
kind: GameServer
spec:
ports:
- name: game
portPolicy: Dynamic
containerPort: 7654
protocol: UDP
- name: query
portPolicy: Static
hostPort: 27015
containerPort: 27015
protocol: TCP
- name: rcon
portPolicy: None
containerPort: 27016
protocol: TCP
Port Ranges
Default Range
helm install agones agones/agones \
--set agones.controller.portRange=7000-8000
This provides 1000 ports for Dynamic and Passthrough policies.
Multiple Named Ranges
With the FeaturePortRanges feature gate:
ports:
- name: game
portPolicy: Dynamic
range: default # Uses 7000-8000
containerPort: 7654
- name: voice
portPolicy: Dynamic
range: voice # Uses custom range (configured separately)
containerPort: 8654
Calculating Capacity
Max GameServers per Node = (MaxPort - MinPort + 1) / Ports per GameServer
Example:
- Range: 7000-8000 (1001 ports)
- Ports per GameServer: 1
- Max per Node: 1001 GameServers
Example with 2 ports:
- Range: 7000-8000 (1001 ports)
- Ports per GameServer: 2
- Max per Node: 500 GameServers
Protocol Support
TCPUDP Protocol
Agones supports dual TCP+UDP on the same port:
ports:
- name: dual
portPolicy: Dynamic
containerPort: 7654
protocol: TCPUDP # Allocates for both TCP and UDP
From pkg/portallocator/portallocator.go:247-267:
// create a port for TCP when using TCPUDP protocol
if p.Protocol == agonesv1.ProtocolTCPUDP {
var duplicate = p
duplicate.HostPort = a.port
if duplicate.PortPolicy == agonesv1.Passthrough {
duplicate.ContainerPort = a.port
}
extraPorts = append(extraPorts, duplicate)
gs.Spec.Ports[i].Name = p.Name + "-tcp"
gs.Spec.Ports[i].Protocol = corev1.ProtocolTCP
}
// create the UDP port when using TCPUDP protocol
for _, p := range extraPorts {
p.Name += "-udp"
p.Protocol = corev1.ProtocolUDP
gs.Spec.Ports = append(gs.Spec.Ports, p)
}
This creates two port entries: dual-tcp and dual-udp.
Best Practices
Use Dynamic by Default
Start with Dynamic policy for most game servers - it’s the most flexible
Reserve Static for Special Cases
Only use Static when required by legacy clients or external integrations
Consider Passthrough for P2P
Use Passthrough when your game server needs to advertise its external port
Test None for Performance
Benchmark None policy if you have direct pod network access and need minimal latency
Size Port Ranges Appropriately
Ensure port range can accommodate: (desired GameServers per node) × (ports per GameServer)
Monitor Port Exhaustion
Alert when port utilization exceeds 80%
Troubleshooting
Port Allocation Failures
# Check port allocator logs
kubectl logs -n agones-system deploy/agones-controller \
| grep portallocator
# Check GameServer events
kubectl describe gameserver <name>
Common issues:
- Port range exhausted
- Node has no available ports
- Port already in use (Static policy)
Query Allocated Ports
# Get allocated port
kubectl get gameserver my-gs -o jsonpath='{.status.ports[0].port}'
# Get full status
kubectl get gameserver my-gs -o yaml
Verify Port Accessibility
# Test UDP port
nc -u <node-ip> <port>
# Test TCP port
telnet <node-ip> <port>
Allocation Speed by Policy
- None: Fastest (no allocation)
- Static: Fast (no allocation)
- Dynamic: Moderate (lookup + allocation)
- Passthrough: Moderate (same as Dynamic)
- None: Lowest latency (direct)
- Static/Dynamic/Passthrough: Slightly higher (NodePort)