探索使用Kubernetes扩展专用游戏服务器:第3部分-扩展节点

在前两篇文章中,咱们研究了如何在 Kubernetes 上托管专用游戏服务器,并测量和限制其内存和 CPU 资源。在本期中,咱们将探讨如何利用上一篇文章中的 CPU 信息来肯定什么时候须要扩展Kubernetes 集群,由于随着玩家人数的增长,咱们已经没有足够的空间来容纳更多的游戏服务器。node

分离 Apps 和 Game Servers

在开始编写代码以增长 Kubernetes 集群的大小以前,咱们应该作的第一步是将咱们的应用程序(例如,match makersgame server controllers 和即将编写的 node scaler)分离到不一样的应用程序中 一 在集群的不一样节点上,而不是游戏服务器运行的地方。api

这有几个好处:bash

  1. 咱们的应用程序的资源使用状况如今对游戏服务器没有影响,由于它们在不一样的计算机上。 这意味着,若是 matchmaker 因为某种缘由而致使 CPU 峰值,那么将存在一个额外的障碍,以确保它不会不适当地影响正在运行的专用游戏服务器。
  2. 这使得扩展和缩小专用游戏服务器的容量变得更容易 — 由于咱们只须要查看特定节点集的游戏服务器使用状况,而不是整个集群中的全部潜在容器。
  3. 在这种状况下,咱们可使用带有更多 CPU 核和内存的大机器来运行游戏服务器节点,也可使用带有更少内核和内存的小机器来运行控制器应用程序,由于它们须要的资源更少。咱们基本上可以为手头的工做选择合适的机器尺寸。这给了咱们很大的灵活性,同时仍然具备成本效益。

Kubernetes 使创建异构集群相对简单,并为咱们提供了工具,可经过节点上的节点选择器的功能来指定集群中 Pod 的调度位置。服务器

值得注意的是,beta 中还具备更复杂的 Node Affinity 功能,可是在此示例中咱们不须要它,所以咱们暂时将其忽略。网络

首先,咱们须要将标签(一组键-值对)分配给集群中的节点。这与您使用 Deployments 建立 Pods 并使用 Services 公开它们时所看到的状况彻底相同,只是将其应用于节点。我使用谷歌的云平台的容器引擎和它使用节点池标签应用于集群中的节点建立和创建异构集群——但你也能够作相似的事情在其余云提供商,以及直接经过 Kubernetes API 或命令行客户端。数据结构

在本例中,我将标签role:apps和role:game-server添加到集群中的适当节点。而后,咱们能够在Kubernetes配置中添加一个nodeSelector选项,以控制集群中的 Pods被调度到哪些节点上面。app

image1.png

例如,下面是 matchmaker 应用程序的配置,您能够看到节点选择器设置为 role:apps,以确保它只在应用程序节点(标记为“apps”角色的节点)上建立容器实例。。async

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: matchmaker
spec:
  replicas: 5
  template:
    metadata:
      labels:
        role: matchmaker-server
    spec:
      nodeSelector:
        role: apps # here is the node selector
      containers:
      - name: matchmaker
        image: gcr.io/soccer/matchmaker
        ports:
        - containerPort: 8080

一样的,咱们能够从上一篇文章中调整配置,使全部专用的游戏服务器 pod 调度仅在咱们专门为它们指定的机器上,即那些标记为 role: game-serveride

apiVersion: v1
kind: Pod
metadata:
  generateName: "game-"
spec:
  hostNetwork: true
  restartPolicy: Never
  nodeSelector:
    role: game-server # here is the node selector
  containers:
    - name: soccer-server
      image: gcr.io/soccer/soccer-server:0.1
      env:
        - name: SESSION_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        resources:
          limits:
            cpu: "0.1"

请注意,在示例代码中,使用 Kubernetes API 提供了与上面相同的配置,但 yaml 版本更容易理解,并且它是咱们在整个系列中一直使用的格式。函数

扩大规模的策略

云提供商上的 Kubernetes 每每带有自动伸缩功能,好比谷歌云平台集群自动伸缩器,但因为它们一般是为无状态应用程序构建的,并且咱们的专用游戏服务器将游戏模拟存储在内存中,因此它们在这种状况下没法工做。然而,使用 Kubernetes 提供的工具,构建咱们本身的定制 Kubernetes 集群自动scaler 并非特别困难!

对于云环境,在 Kubernetes 集群中扩展和缩小节点可能更有意义,由于咱们只想为咱们须要/使用的资源付费。若是咱们在本身的场所中运行,则更改 Kubernetes 集群的大小可能没什么意义,并且咱们能够在全部拥有的机器上运行一个大型集群,并将它们保持为静态大小,由于添加 而且删除物理计算机要比在云上花费更多,而且因为咱们拥有/租赁计算机的时间更长,所以不必定能节省咱们的钱。

有多种潜在策略可用来肯定什么时候要扩展集群中的节点数量,可是在本示例中,咱们将使事情变得相对简单:

  • 定义游戏服务器的最小和最大节点数,并确保咱们在该限制以内。
  • 使用 CPU 资源容量和使用率做为咱们跟踪集群中一个节点上能够容纳多少专用游戏服务器的指标(在本例中,咱们假设咱们老是有足够的内存)。
  • 在集群中,为必定数量的游戏服务器定义 CPU 容量缓冲区。也就是说,若是在任什么时候刻,你都没法在不耗尽集群 CPU 资源的状况下将 n 个服务器添加到集群中,那么就增长更多的节点。
  • 每当启动新的专用游戏服务器时,请计算是否须要在群集中添加新节点,由于跨节点的 CPU 容量低于缓冲区数量。
  • 做为故障保护,每隔 n 秒,还要计算是否须要将新节点添加到群集,由于所测量的 CPU 容量资源在缓冲区下方。

image.png

建立 Node Scaler

node scaler 本质上是运行一个事件循环来执行上面概述的策略。

结合使用 Go 和原生 Kubernetes Go client library 库能够相对容易地实现这一点,以下面在节点缩放器的 Start() 函数中所见。

注意,为了使事件循环更清晰,我已经删除了大部分错误处理和其余样板文件,但若是您感兴趣,这里是原始代码。

// Start the HTTP server on the given port
func (s *Server) Start() error {
        
        // Access Kubernetes and return a client
        s.cs, _ = kube.ClientSet()

        // ... there be more code here ... 
        
        // Use the K8s client's watcher channels to see game server events
        gw, _ := s.newGameWatcher()
        gw.start()

        // async loop around either the tick, or the event stream
        // and then scaleNodes() if either occur.
        go func() {
                log.Print("[Info][Start] Starting node scaling...")
                tick := time.Tick(s.tick)

                // ^^^ MAIN EVENT LOOP HERE ^^^
                for {
                        select {
                        case <-gw.events:
                                log.Print("[Info][Scaling] Received Event, Scaling...")
                                s.scaleNodes()                          
                        case <-tick:
                                log.Printf("[Info][Scaling] Tick of %#v, Scaling...", tick)
                                s.scaleNodes()
                        }
                }
        }()
      
        // Start the HTTP server
        return errors.Wrap(s.srv.ListenAndServe(), "Error starting server")
}

对于那些不熟悉 Go 的人,让咱们分析一下:

  1. kube.ClientSet() – 咱们有一小段实用程序代码,它向咱们返回一个 Kubernetes ClientSet,它使咱们可以访问正在运行的集群的 Kubernetes API
  2. gw, _ := s.newGameWatcherKubernetes 具备 API,使您能够监视整个集群中的更改。 在这种特殊状况下,此处的代码返回一个包含 Go Channel(本质上是一个阻塞队列)的数据结构,特别是 gw.events,每当在集群中添加或删除游戏 Pod 时,该数据结构都将返回一个值。
  3. tick := time.Tick(s.tick) – 这将建立另外一个 Go Channel,该 Channel 一直阻塞到给定时间(在这种状况下为10秒),而后返回一个值。
  4. 主事件循环在 “// ^^^ MAIN EVENT LOOP HERE ^^^” 注释下。在此代码块中是一条 select 语句。这实际上声明了系统将阻塞,直到 gw.events channeltick channel(每 10 秒触发一次)返回一个值,而后执行 s.scaleNodes()。 这意味着,每当添加/删除游戏服务器或每 10 秒触发一次 scaleNodes 命令。
  5. s.scaleNodes() – 运行上面概述的规模节点策略。

s.scaleNodes() 中,咱们经过 Kubernetes API 查询咱们在每一个 Pod 上设置的 CPU 限制,以及集群中每一个 Kubernetes 节点上可用的总 CPU。咱们能够经过 Rest APIGo ClientPod specification 中查看已配置的 CPU 限制,这使咱们可以跟踪每台游戏服务器占用的 CPU 数量以及任何存在于节点上 Kubernetes 管理的 Pod。经过 Node specificationGo Client 还能够跟踪每一个节点中可用的 CPU 容量。 在这种状况下,须要对 Pods 占用的 CPU 数量求和,而后从每一个节点的容量中减去 CPU 的数量,而后肯定是否须要将一个或多个节点添加到集群中,这样咱们才能保持该缓冲区空间,用于建立新的游戏服务器。

若是您在此示例中深刻研究代码,将会看到咱们正在使用 Google Cloud Platform 上的 API 向集群添加新节点。为 Google Compute Engine 托管实例组提供的 API 容许咱们从Kubernetes 集群的 Nodepool 中添加(和删除)实例。 话虽这么说,任何云提供商都将具备相似的 API,让您作一样的事情,在这里您能够看到咱们定义的接口,该接口用于抽象该实现细节,以即可以轻松地对其进行修改以与其余提供商一块儿使用。

部署节点缩放器

在下面,您能够看到节点缩放器的部署 YAML。 如您所见,环境变量用于设置全部配置选项,包括:

  • 集群中的哪些节点应进行管理
  • 每一个专用游戏服务器须要多少 CPU
  • 最小和最大节点数
  • 一直存在多少缓冲区
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nodescaler
spec:
  replicas: 1 # only want one, to avoid race conditions
  template:
    metadata:
      labels:
        role: nodescaler-server
    spec:
      nodeSelector:
        role: apps
      strategy:
        type: Recreate
      containers:
      - name: nodescaler
        image: gcr.io/soccer/nodescaler
        env:
          - name: NODE_SELECTOR # the nodes to be managed
            value: "role=game-server"
          - name: CPU_REQUEST # how much CPU each server needs
            value: "0.1"
          - name: BUFFER_COUNT # how many servers do we need buffer for
            value: "30"
          - name: TICK # how often to tick over and recheck everything
            value: "10s"
          - name: MIN_NODE # minimum number of nodes for game servers
            value: "1"
          - name: MAX_NODE # maximum number of nodes for game servers
            value: "15"

您可能已经注意到,咱们将部署设置为 replicas: 1。咱们这样作的缘由是,咱们老是但愿在Kubernetes 集群中在任何给定的时间点上只有一个活跃的 node scaler 实例。这确保了集群中不会有超过一个进程试图扩大或最终缩小咱们的节点,这确定会致使竞争条件,并可能致使各类奇怪的状况。

一样,若是要更新节点缩放器,要确保在建立节点缩放器以前正确关闭节点缩放器,咱们还配置strategy.type: Recreate,以便 Kubernetes 在从新建立节点缩放器以前销毁当前运行的节点缩放器 Pod。更新版本,也避免了任何潜在的竞争状况。

看看它的实际应用

部署节点缩放器后,让咱们跟踪日志并查看其运行状况。 在下面的视频中,经过日志能够看到,当群集中有一个节点分配给游戏服务器时,咱们有能力启动 40 个专用游戏服务器,并配置了 30 个专用游戏服务器的缓冲区的需求。 当咱们经过 matchmaker 经过运行专用游戏服务器来填充可用的CPU容量时,请注意在剩余空间中可建立的游戏服务器数量会如何降低,最终会添加一个新节点来维护缓冲区!

图片来源:http://www.coubai.com/ 网络游戏

相关文章
相关标签/搜索