使用 Kubernetes 扩展专用游戏服务器:第4部分-缩减节点

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 使用 Kubernetes 扩展专用游戏服务器:第4部分-缩减节点

在前三篇文章中,我们将游戏服务器托管在 Kubernetes 上,测量并限制它们的资源使用,并根据使用情况扩大集群中的节点。现在我们需要解决更困难的问题:当资源不再被使用时,缩小集群中的节点,同时确保正在进行的游戏在节点被删除时不会中断。

从表面上看,按比例缩小集群中的节点似乎特别复杂。每个游戏服务器具有当前游戏的内存状态,并且多个游戏客户端连接到玩游戏的单个游戏服务器。删除任意节点可能会断开活动玩家的连接,这会使他们生气!因此,只有在节点没有专用游戏服务器的情况下,我们才能从集群中删除节点。


这意味着,如果您运行在谷歌 Kubernetes Engine (GKE) 或类似的平台上,就不能使用托管的自动缩放系统。引用 GKE autoscaler 的文档“ Cluster autoscaler 假设所有复制的 pod 都可以在其他节点上重新启动……” — 这在我们的例子中绝对不起作用,因为它可以很容易地删除那些有活跃玩家的节点。


也就是说,当我们更仔细地研究这种情况时,我们会发现我们可以将其分解为三个独立的策略,当这些策略结合在一起时,我们就可以将问题缩小成一个可管理的问题,我们可以自己执行:


  1. 将游戏服务器组合在一起,以避免整个集群的碎片化
  2. CPU 容量超过配置的缓冲区时,封锁节点
  3. 一旦节点上的所有游戏退出,就从集群中删除被封锁的节点

让我们看一下每个细节。


系列



  1. 探索使用 Kubernetes 扩展专用游戏服务器:第1部分-容器化和部署
  2. 探索使用Kubernetes扩展专用游戏服务器:第2部分-管理CPU和内存
  3. 探索使用Kubernetes扩展专用游戏服务器:第3部分 - 扩展节点


在集群中将游戏服务器分组在一起



我们想要避免集群中游戏服务器的碎片化,这样我们就不会在多个节点上运行一个任性的小游戏服务器集,这将防止这些节点被关闭和回收它们的资源。


这意味着我们不希望有一个调度模式在整个集群的随机节点上创建游戏服务器 Pod,如下所示:


微信图片_20220611154912.png

而是我们想让我们的游戏服务器pod安排得尽可能紧凑,像这样:


微信图片_20220611154914.png

要将我们的游戏服务器分组在一起,我们可以利用带有 PreferredDuringSchedulingIgnoredDuringExecution 选项的 Kubernetes Pod PodAffinity 配置。


这使我们能够告诉 Pods 我们更喜欢按它们当前所在的节点的主机名对它们进行分组,这实质上意味着 Kubernetes 将更喜欢将专用的游戏服务器 Pod 放置在已经具有专用游戏服务器的节点上(上面已经有 Pod 了)。


在理想情况下,我们希望在拥有最专用游戏服务器 Pod 的节点上调度专用游戏服务器 Pod,只要该节点还有足够的空闲 CPU 资源。如果我们想为 Kubernetes 编写自己的自定义调度程序,我们当然可以这样做,但为了保持演示简单,我们将坚持使用 PodAffinity 解决方案。也就是说,当我们考虑到我们的游戏长度很短,并且我们将很快添加(and explaining)封锁节点时,这种技术组合已经足够满足我们的需求,并且消除了我们编写额外复杂代码的需要。


当我们将 PodAffinity 配置添加到前一篇文章的配置时,我们得到以下内容,它告诉 Kubernetes 在可能的情况下将带有标签 sessions: gamepod 放置在彼此相同的节点上。


apiVersion: v1
kind: Pod
metadata:
  generateName: "game-"
spec:
  hostNetwork: true
  restartPolicy: Never
  nodeSelector:
    role: game-server
  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"
  affinity:
    podAffinity: # group game server Pods
      preferredDuringSchedulingIgnoredDuringExecution:
      - podAffinityTerm:
          labelSelector:
            matchLabels:
              sessions: game
          topologyKey: kubernetes.io/hostname


封锁节点



现在我们已经把我们的游戏服务器很好地打包在一起了,我们可以讨论“封锁节点”了。“封锁节点”到底是什么意思?很简单,Kubernetes 让我们能够告诉调度器:“嘿,调度器,不要在这个节点上调度任何新东西”。这将确保该节点上不会调度新的 pod。事实上,在 Kubernetes 文档的某些地方,这被简单地称为标记节点不可调度。


微信图片_20220611154940.png


在下面的代码中,如果您专注于 s.bufferCount < available,您将看到,如果当前拥有的 CPU 缓冲区的数量大于我们所需要的数量,我们将向警戒节点发出请求。


// scale scales nodes up and down, depending on CPU constraints
// this includes adding nodes, cordoning them as well as deleting them
func (s Server) scaleNodes() error {
        nl, err := s.newNodeList()
        if err != nil {
                return err
        }
        available := nl.cpuRequestsAvailable()
        if available < s.bufferCount {
                finished, err := s.uncordonNodes(nl, s.bufferCount-available)
                // short circuit if uncordoning means we have enough buffer now
                if err != nil || finished {
                        return err
                }
                nl, err := s.newNodeList()
                if err != nil {
                        return err
                }
                // recalculate
                available = nl.cpuRequestsAvailable()
                err = s.increaseNodes(nl, s.bufferCount-available)
                if err != nil {
                        return err
                }
        } else if s.bufferCount < available {
                err := s.cordonNodes(nl, available-s.bufferCount)
                if err != nil {
                        return err
                }
        }
        return s.deleteCordonedNodes()
}


从上面的代码中还可以看到,如果我们降到配置的 CPU 缓冲区以下,则可以取消集群中任何可用的封闭节点的约束。这比添加一个全新的节点要快,因此在从头开始添加全新的节点之前,请先检查受约束的节点,这一点很重要。由于这个原因,我们还配置了删除隔离节点的时间延迟,以限制不必要地在集群中创建和删除节点时的抖动。


这是一个很好的开始。但是,当我们要封锁节点时,我们只希望封锁其上具有最少数量的游戏服务器 Pod 的节点,因为在这种情况下,随着游戏会话的结束,它们最有可能先清空。


得益于 Kubernetes API,计算每个节点上的游戏服务器 Pod 的数量并按升序对其进行排序相对容易。从那里,我们可以算术确定如果我们封锁每个可用节点,是否仍保持在所需的 CPU 缓冲区上方。如果是这样,我们可以安全地封锁这些节点。


// cordonNodes decrease the number of available nodes by the given number of cpu blocks (but not over),
// but cordoning those nodes that have the least number of games currently on them
func (s Server) cordonNodes(nl *nodeList, gameNumber int64) error {
       // … removed some input validation ... 
        // how many nodes (n) do we have to delete such that we are cordoning no more
        // than the gameNumber
        capacity := nl.nodes.Items[0].Status.Capacity[v1.ResourceCPU] //assuming all nodes are the same
        cpuRequest := gameNumber * s.cpuRequest
        diff := int64(math.Floor(float64(cpuRequest) / float64(capacity.MilliValue())))
        if diff <= 0 {
                log.Print("[Info][CordonNodes] No nodes to be cordoned.")
                return nil
        }
        log.Printf("[Info][CordonNodes] Cordoning %v nodes", diff)
        // sort the nodes, such that the one with the least number of games are first
        nodes := nl.nodes.Items
        sort.Slice(nodes, func(i, j int) bool {
                return len(nl.nodePods(nodes[i]).Items) < len(nl.nodePods(nodes[j]).Items)
        })
        // grab the first n number of them
        cNodes := nodes[0:diff]
        // cordon them all
        for _, n := range cNodes {
                log.Printf("[Info][CordonNodes] Cordoning node: %v", n.Name)
                err := s.cordon(&n, true)
                if err != nil {
                        return err
                }
        }
        return nil
}


从集群中删除节点



现在我们的集群中的节点已经被封锁,这只是一个等待,直到被封锁的节点上没有游戏服务器 Pod 为止,然后再删除它。下面的代码还确保节点数永远不会低于配置的最小值,这是集群容量的良好基线。


您可以在下面的代码中看到这一点:


// deleteCordonedNodes will delete a cordoned node if it
// the time since it was cordoned has expired
func (s Server) deleteCordonedNodes() error {
  nl, err := s.newNodeList()
  if err != nil {
     return err
  }
  l := int64(len(nl.nodes.Items))
  if l <= s.minNodeNumber {
     log.Print("[Info][deleteCordonedNodes] Already at minimum node count. exiting")
     return nil
  }
  var dn []v1.Node
  for _, n := range nl.cordonedNodes() {
     ct, err := cordonTimestamp(n)
     if err != nil {
        return err
     }
     pl := nl.nodePods(n)
     // if no game session pods && if they have passed expiry, then delete them
     if len(filterGameSessionPods(pl.Items)) == 0 && ct.Add(s.shutdown).Before(s.clock.Now()) {
        err := s.cs.CoreV1().Nodes().Delete(n.Name, nil)
        if err != nil {
           return errors.Wrapf(err, "Error deleting cordoned node: %v", n.Name)
        }
        dn = append(dn, n)
        // don't delete more nodes than the minimum number set
        if l--; l <= s.minNodeNumber {
           break
        }
     }
  }
  return s.nodePool.DeleteNodes(dn)
}


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
29天前
|
Kubernetes API 调度
k8s中节点无法启动Pod
【10月更文挑战第3天】
73 6
|
28天前
|
Prometheus Kubernetes 监控
k8s部署针对外部服务器的prometheus服务
通过上述步骤,您不仅成功地在Kubernetes集群内部署了Prometheus,还实现了对集群外服务器的有效监控。理解并实施网络配置是关键,确保监控数据的准确无误传输。随着监控需求的增长,您还可以进一步探索Prometheus生态中的其他组件,如Alertmanager、Grafana等,以构建完整的监控与报警体系。
115 60
|
29天前
|
Prometheus Kubernetes 监控
k8s部署针对外部服务器的prometheus服务
通过上述步骤,您不仅成功地在Kubernetes集群内部署了Prometheus,还实现了对集群外服务器的有效监控。理解并实施网络配置是关键,确保监控数据的准确无误传输。随着监控需求的增长,您还可以进一步探索Prometheus生态中的其他组件,如Alertmanager、Grafana等,以构建完整的监控与报警体系。
169 62
|
30天前
|
分布式计算 Hadoop Shell
Hadoop-35 HBase 集群配置和启动 3节点云服务器 集群效果测试 Shell测试
Hadoop-35 HBase 集群配置和启动 3节点云服务器 集群效果测试 Shell测试
65 4
|
1月前
|
XML 分布式计算 资源调度
大数据-02-Hadoop集群 XML配置 超详细 core-site.xml hdfs-site.xml 3节点云服务器 2C4G HDFS Yarn MapRedece(一)
大数据-02-Hadoop集群 XML配置 超详细 core-site.xml hdfs-site.xml 3节点云服务器 2C4G HDFS Yarn MapRedece(一)
131 5
|
30天前
|
分布式计算 Hadoop Shell
Hadoop-36 HBase 3节点云服务器集群 HBase Shell 增删改查 全程多图详细 列族 row key value filter
Hadoop-36 HBase 3节点云服务器集群 HBase Shell 增删改查 全程多图详细 列族 row key value filter
55 3
|
1月前
|
XML 资源调度 网络协议
大数据-02-Hadoop集群 XML配置 超详细 core-site.xml hdfs-site.xml 3节点云服务器 2C4G HDFS Yarn MapRedece(二)
大数据-02-Hadoop集群 XML配置 超详细 core-site.xml hdfs-site.xml 3节点云服务器 2C4G HDFS Yarn MapRedece(二)
66 4
|
1月前
|
分布式计算 资源调度 Hadoop
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
65 4
|
29天前
|
Kubernetes 应用服务中间件 nginx
搭建Kubernetes v1.31.1服务器集群,采用Calico网络技术
在阿里云服务器上部署k8s集群,一、3台k8s服务器,1个Master节点,2个工作节点,采用Calico网络技术。二、部署nginx服务到k8s集群,并验证nginx服务运行状态。
323 1
|
30天前
|
SQL 存储 数据管理
Hadoop-15-Hive 元数据管理与存储 Metadata 内嵌模式 本地模式 远程模式 集群规划配置 启动服务 3节点云服务器实测
Hadoop-15-Hive 元数据管理与存储 Metadata 内嵌模式 本地模式 远程模式 集群规划配置 启动服务 3节点云服务器实测
51 2