探索使用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/ 網絡遊戲

相關文章
相關標籤/搜索