在前两篇文章中,咱们研究了如何在 Kubernetes
上托管专用游戏服务器,并测量和限制其内存和 CPU
资源。在本期中,咱们将探讨如何利用上一篇文章中的 CPU
信息来肯定什么时候须要扩展Kubernetes
集群,由于随着玩家人数的增长,咱们已经没有足够的空间来容纳更多的游戏服务器。node
在开始编写代码以增长 Kubernetes
集群的大小以前,咱们应该作的第一步是将咱们的应用程序(例如,match makers
,game server controllers
和即将编写的 node scaler
)分离到不一样的应用程序中 一 在集群的不一样节点上,而不是游戏服务器运行的地方。api
这有几个好处:bash
matchmaker
因为某种缘由而致使 CPU
峰值,那么将存在一个额外的障碍,以确保它不会不适当地影响正在运行的专用游戏服务器。CPU
核和内存的大机器来运行游戏服务器节点,也可使用带有更少内核和内存的小机器来运行控制器应用程序,由于它们须要的资源更少。咱们基本上可以为手头的工做选择合适的机器尺寸。这给了咱们很大的灵活性,同时仍然具备成本效益。Kubernetes
使创建异构集群相对简单,并为咱们提供了工具,可经过节点上的节点选择器的功能来指定集群中 Pod
的调度位置。服务器
值得注意的是,beta
中还具备更复杂的 Node Affinity
功能,可是在此示例中咱们不须要它,所以咱们暂时将其忽略。网络
首先,咱们须要将标签(一组键-值对)分配给集群中的节点。这与您使用 Deployments
建立 Pods
并使用 Services
公开它们时所看到的状况彻底相同,只是将其应用于节点。我使用谷歌的云平台的容器引擎和它使用节点池标签应用于集群中的节点建立和创建异构集群——但你也能够作相似的事情在其余云提供商,以及直接经过 Kubernetes API
或命令行客户端。数据结构
在本例中,我将标签role:apps和role:game-server添加到集群中的适当节点。而后,咱们能够在Kubernetes配置中添加一个nodeSelector选项,以控制集群中的 Pods被调度到哪些节点上面。app
例如,下面是 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-server
:ide
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
容量资源在缓冲区下方。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
的人,让咱们分析一下:
kube.ClientSet()
– 咱们有一小段实用程序代码,它向咱们返回一个 Kubernetes ClientSet
,它使咱们可以访问正在运行的集群的 Kubernetes API
。gw, _ := s.newGameWatcher
– Kubernetes
具备 API
,使您能够监视整个集群中的更改。 在这种特殊状况下,此处的代码返回一个包含 Go Channel
(本质上是一个阻塞队列)的数据结构,特别是 gw.events
,每当在集群中添加或删除游戏 Pod
时,该数据结构都将返回一个值。tick := time.Tick(s.tick)
– 这将建立另外一个 Go Channel
,该 Channel
一直阻塞到给定时间(在这种状况下为10秒),而后返回一个值。“// ^^^ MAIN EVENT LOOP HERE ^^^”
注释下。在此代码块中是一条 select
语句。这实际上声明了系统将阻塞,直到 gw.events channel
或 tick channel
(每 10
秒触发一次)返回一个值,而后执行 s.scaleNodes()
。 这意味着,每当添加/删除游戏服务器或每 10
秒触发一次 scaleNodes
命令。s.scaleNodes()
– 运行上面概述的规模节点策略。在 s.scaleNodes()
中,咱们经过 Kubernetes API
查询咱们在每一个 Pod
上设置的 CPU
限制,以及集群中每一个 Kubernetes
节点上可用的总 CPU
。咱们能够经过 Rest API
和 Go Client
在 Pod specification
中查看已配置的 CPU
限制,这使咱们可以跟踪每台游戏服务器占用的 CPU
数量以及任何存在于节点上 Kubernetes
管理的 Pod
。经过 Node specification
,Go 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/ 网络游戏