Skip to main content

Overview

Metadata methods allow game servers to dynamically update their Kubernetes labels and annotations at runtime. This enables dynamic tagging for matchmaking, monitoring, and operational purposes. These methods are part of the stable SDK service (agones.dev.sdk.SDK).

Use Cases

Dynamic Game Mode Tags

Update labels to reflect the current game mode, allowing matchmakers to filter servers by active mode:
  • game-mode=deathmatch
  • map=desert_storm
  • difficulty=hard

Match State Tracking

Tag servers with match progress information:
  • match-id=abc123
  • round=3
  • match-state=in-progress

Performance Metrics

Store performance annotations for monitoring:
  • last-tick-time=16.7ms
  • player-count=32
  • cpu-usage=high

Version and Build Tags

Dynamically mark server versions for gradual rollouts:
  • version=1.2.3
  • build=2024-03-10
  • canary=true

SetLabel

Applies a label to the game server’s metadata. Labels are key-value pairs used for selection and filtering.
rpc SetLabel(KeyValue) returns (Empty);

Behavior

  • Creates a new label if the key doesn’t exist
  • Updates the value if the key already exists
  • Labels are immediately visible to the Kubernetes API
  • Labels can be used by selectors in the Allocation API
Label keys and values must follow Kubernetes label requirements:
  • Keys: max 63 characters (prefix optional, max 253 chars)
  • Values: max 63 characters
  • Valid characters: alphanumeric, -, _, .
  • Must start/end with alphanumeric character

Request

key
string
required
The label key. Must be a valid Kubernetes label key.
value
string
required
The label value. Must be a valid Kubernetes label value.

Response

-
Empty
Returns empty on success.

Example Usage

import (
    "log"
    "agones.dev/agones/sdks/go"
)

func updateGameMode(sdk *sdk.SDK, mode string) error {
    if err := sdk.SetLabel("game-mode", mode); err != nil {
        return fmt.Errorf("could not set label: %v", err)
    }
    log.Printf("Set game mode label to: %s", mode)
    return nil
}

// Usage
updateGameMode(sdk, "capture-the-flag")

SetAnnotation

Applies an annotation to the game server’s metadata. Annotations are key-value pairs used for storing arbitrary metadata.
rpc SetAnnotation(KeyValue) returns (Empty);

Behavior

  • Creates a new annotation if the key doesn’t exist
  • Updates the value if the key already exists
  • Annotations are immediately visible to the Kubernetes API
  • Unlike labels, annotations are not used for selection
  • Annotations can store larger values and more complex data
Annotation keys follow the same rules as labels, but values can be:
  • Larger than labels (no hard limit, but keep under 256KB total)
  • Contain arbitrary string data
  • Include structured data like JSON

Request

key
string
required
The annotation key. Must be a valid Kubernetes annotation key.
value
string
required
The annotation value. Can contain arbitrary string data.

Response

-
Empty
Returns empty on success.

Example Usage

import (
    "encoding/json"
    "log"
    "agones.dev/agones/sdks/go"
)

type MatchInfo struct {
    MatchID   string   `json:"matchId"`
    StartTime int64    `json:"startTime"`
    Players   []string `json:"players"`
}

func setMatchInfo(sdk *sdk.SDK, info MatchInfo) error {
    jsonData, err := json.Marshal(info)
    if err != nil {
        return err
    }
    
    if err := sdk.SetAnnotation("match-info", string(jsonData)); err != nil {
        return fmt.Errorf("could not set annotation: %v", err)
    }
    
    log.Println("Set match info annotation")
    return nil
}

// Usage
info := MatchInfo{
    MatchID:   "match-12345",
    StartTime: time.Now().Unix(),
    Players:   []string{"player1", "player2"},
}
setMatchInfo(sdk, info)

Health

Sends health pings to signal that the game server is healthy. This is a streaming RPC where the client sends Empty messages at regular intervals.
rpc Health (stream Empty) returns (Empty);

Behavior

  • Opens a client-streaming connection
  • Client sends Empty messages periodically
  • If health pings stop for longer than the failure threshold, the game server is marked as unhealthy
  • Unhealthy servers are terminated and replaced by Agones
Health checking is enabled by default. Configure health check parameters in the GameServer spec:
  • health.periodSeconds: How often to expect pings (default: 5)
  • health.failureThreshold: Failed checks before unhealthy (default: 3)
  • health.initialDelaySeconds: Delay before first check (default: 5)

When to Use

Always implement health checking unless:
  • You have external health monitoring
  • Your game server has a very short lifecycle
  • You’re explicitly disabling health checks in the spec

Request

stream
Empty
Stream of empty messages sent periodically to indicate health.

Response

-
Empty
Returns empty when the stream closes.

Example Usage

import (
    "context"
    "log"
    "time"
    "agones.dev/agones/sdks/go"
)

func healthCheck(ctx context.Context, sdk *sdk.SDK) {
    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            if err := sdk.Health(); err != nil {
                log.Printf("Could not send health ping: %v", err)
            }
        }
    }
}

// Start health checking in a goroutine
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go healthCheck(ctx, sdk)

Supporting Types

KeyValue

Represents a key-value pair for labels and annotations.
message KeyValue {
    string key = 1;
    string value = 2;
}

Duration

Represents a time duration in seconds (used by Reserve in lifecycle methods).
message Duration {
    int64 seconds = 1;
}

Empty

An empty message used for RPCs that don’t require parameters or return values.
message Empty {}

HTTP Gateway

Metadata methods are available via HTTP/JSON:
curl -X PUT http://localhost:9357/metadata/label \
  -H "Content-Type: application/json" \
  -d '{
    "key": "game-mode",
    "value": "deathmatch"
  }'

Best Practices

Label vs Annotation Guidelines

Use labels for:
  • Values used in selectors (game mode, version, region)
  • Short, simple key-value pairs
  • Data that changes infrequently
  • Metadata used by the Allocation API
Use annotations for:
  • Structured data (JSON, timestamps)
  • Longer strings
  • Data not used for selection
  • Monitoring and debugging information

Minimize Update Frequency

While metadata updates are fast, avoid excessive calls:
// Bad: Updating on every tick
func onTick() {
    sdk.SetLabel("tick-count", strconv.Itoa(tickCount))
}

// Good: Update only when meaningful changes occur
func onGameModeChange(newMode string) {
    sdk.SetLabel("game-mode", newMode)
}
If you need to update multiple labels, consider updating them when it makes logical sense:
func onMatchStart(matchID string, mode string, mapName string) {
    // These updates happen together
    sdk.SetLabel("match-id", matchID)
    sdk.SetLabel("game-mode", mode)
    sdk.SetLabel("map", mapName)
    sdk.SetLabel("match-state", "starting")
}

Use Consistent Key Names

Establish naming conventions across your fleet:
// Good: Consistent kebab-case
sdk.SetLabel("game-mode", "deathmatch")
sdk.SetLabel("match-state", "in-progress")
sdk.SetLabel("player-count", "16")

// Bad: Inconsistent naming
sdk.SetLabel("gameMode", "deathmatch")  // camelCase
sdk.SetLabel("match_state", "in-progress") // snake_case
sdk.SetLabel("PlayerCount", "16")       // PascalCase

Validate Before Setting

Validate metadata values before sending to avoid errors:
func setGameMode(sdk *sdk.SDK, mode string) error {
    // Validate label value
    if len(mode) > 63 {
        return fmt.Errorf("mode too long: %d chars", len(mode))
    }
    if !isValidLabelValue(mode) {
        return fmt.Errorf("invalid mode: %s", mode)
    }
    
    return sdk.SetLabel("game-mode", mode)
}

Build docs developers (and LLMs) love