yafeiaa Blogs

Agones SDK-Server 与 Allocator

前言

Agones 通过 SDK-Server 让 GameServer 自己管理生命周期状态。每个 GameServer 启动时会连接本地的 SDK Server,通过 gRPC API 同步状态。

在实际部署时,sdk-server 会作为一个 sidecar 容器,和 gameserver 容器共享 network namespace,所以 gmaeserver 可以直接以 localhost 访问 sdk-server 的 api。

sdk-server 作为 agones 和 gameserver 之间的状态同步器,负责将 gameserver 的状态同步给 agones,以及将 agones 的分配请求同步给 gameserver。

Allocator Service

Allocator 是 Agones 提供的 gRPC 服务,用于分配 GameServer。它监听集群中所有 Ready 状态的 GameServer,当收到分配请求时,选择一个合适的 GameServer 并将其状态改为 Allocated。

Allocator 提供了两种分配方式:

  1. gRPC Service - 直接调用 gRPC API(适合高性能、安全性要求高的场景,通过mtls认证)
  2. Allocation API - 通过 Kubernetes CRD GameServerAllocation(更简单,通过k8s api调用)

核心 API

  • Ready() - 标记就绪,可以被分配
  • Health() - 发送心跳,保持健康状态
  • Shutdown() - 通知关闭
  • SetAnnotation() - 设置自定义信息(如 自定义的对外访问地址)
  • WatchGameServer() - 监听状态变化

状态流转

Port -> Ready -> Allocated -> Shutdown
  1. Port - 刚启动,等待 Ready
  2. Ready - 调用 Ready() 后进入,等待分配
  3. Allocated - 被 match-maker 分配后进入,开始游戏逻辑
  4. Shutdown - 调用 Shutdown() 或收到关闭信号后进入

一个dedicate-server和 match-maker通过 sdk-server 和 allocator 交互的例子

dedicate-server

启动流程:

sdk, _ := agones.NewSDK()
sdk.SetAnnotation("public_address", "127.0.0.1:7001")  // 设置自定义地址
sdk.Ready()  // 标记就绪

// 每 2 秒发送心跳
ticker := time.NewTicker(2 * time.Second)
for range ticker.C {
    sdk.Health()
}

// 监听状态变化
sdk.WatchGameServer(func(gs *sdk.GameServer) {
    switch gs.Status.State {
    case "Allocated":
        // 执行业务逻辑
    case "Shutdown":
        // 清理资源
    }
})

match-maker

通过 GameServerAllocation API 分配 GameServer。这是 Kubernetes CRD 资源,创建后会由 Agones 的 Allocator 处理:

// 创建 GameServerAllocation 对象
allocation := &allocationv1.GameServerAllocation{
    TypeMeta: metav1.TypeMeta{
        APIVersion: "allocation.agones.dev/v1",
        Kind:       "GameServerAllocation",
    },
    ObjectMeta: metav1.ObjectMeta{
        GenerateName: "allocation-",
        Namespace:    "default",
    },
    Spec: allocationv1.GameServerAllocationSpec{
        Selectors: []allocationv1.GameServerSelector{{
            LabelSelector: metav1.LabelSelector{
                MatchLabels: map[string]string{
                    "agones.dev/fleet": "dedicate-server-fleet",
                },
            },
        }},
    },
}

// 创建分配请求
created, err := clientset.AllocationV1().GameServerAllocations("default").Create(ctx, allocation, metav1.CreateOptions{})
if err != nil {
    return err
}

// 检查分配状态
if created.Status.State == allocationv1.GameServerAllocationAllocated {
    gameServerName := created.Status.GameServerName
    address := created.Status.Address
    // 分配成功
} else if created.Status.State == allocationv1.GameServerAllocationUnAllocated {
    // 没有可用的 GameServer,需要重试
}

分配流程:

  1. match-maker 创建 GameServerAllocation CRD
  2. Agones Allocator 监听到分配请求
  3. Allocator 从 Fleet 中选择一个 Ready 状态的 GameServer
  4. Allocator 将 GameServer 状态改为 Allocated
  5. 返回分配结果(GameServer 名称、地址、端口等)

分配成功后,从 GameServer 的 annotation 获取自定义信息:

gs, _ := clientset.AgonesV1().GameServers(namespace).Get(ctx, gameServerName, metav1.GetOptions{})
publicAddress := gs.Annotations["agones.dev/sdk-public_address"]

注意事项

  1. 心跳频率 - Health() 调用频率要小于健康检查周期(fleet.yaml 中配置的 periodSeconds
  2. 优雅关闭 - 收到 SIGTERM 或 WatchGameServer 监听到 Shutdown 状态时,先完成业务逻辑再调用 Shutdown()
  3. Annotation 前缀 - SetAnnotation 设置的 key 会自动加上 agones.dev/sdk- 前缀,读取时要用完整 key