在前兩篇文章中,咱們研究瞭如何在 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/ 網絡遊戲