Kubernetes 學習筆記之阿里雲遊戲業務實戰
作者介紹:段鵬舉,邊鋒後端開發工程師。
本人一直做業務開發,不曾瞭解過運維知識,因為要對一個專案的技術部分負責,開發業務的同時還需要思考系統層面的事情,團隊人數又少,不得不採用 k8s 這種能達到事半功倍效果的工具。本文是在阿里雲 kubernetes 部署遊戲業務的實戰筆記,不涉及 k8s 原理等深層知識。我學習 k8s 的時間也比較短,如有理解錯誤的地方,還望海涵。
目標
我們遊戲按照業務邏輯劃分,伺服器可分為三種類型,前端伺服器(客戶端直接進行連線的)、後端伺服器(只負責處理各種遊戲邏輯不提供連線)、任務伺服器(各種 cron、job 任務),其中前端伺服器按照功能劃分為 http 短連線伺服器和 socket 長連線伺服器,後端伺服器按照業務劃分 例如 matching 匹配伺服器。
在部署這些伺服器的同時,我需要使用 kubernetes 達到的目標有:
-
對於每種型別的伺服器,需要同時存在若干個版本
-
對於無狀態伺服器如 http、cron 可以比較方便的更新、回滾
-
對於有狀態伺服器如 socket、matching 可以業務無間斷的進行更新、回滾,使用者不會掉線、無感知
-
可以進行灰度釋出
-
當伺服器的負載變化時,能夠自動伸縮伺服器數量
-
當伺服器異常宕機時,能夠自我修復
準備 Docker 映象
-
使用阿里雲容器映象服務準備好 docker 遠端倉庫
-
在應用(伺服器程式碼)準備好之後,使用 Docker 構建映象,並打上版本號,Push 到遠端倉庫(這一步驟可以通過 Jekins 自動完成,後續實踐的時候會更新文件,目前就以手工進行)
部署應用
類似於 web 前端框架中的命令式(jquery)與宣告式(react),我對 k8s 有一種理解與之類似:我們只需要通過配置檔案“告訴”k8s 我們想要的最終結果就行,中間的過程無須再關心,k8s 會以各種機制保證這一結果
1.因為使用的 Docker 遠端倉庫是私有倉庫,部署應用時就需要新增 imagePullSecrets,首先使用 kubectl 在 default 名稱空間裡建立 secret,如需指定名稱空間新增 -n 引數,後面命令類似
2.根據伺服器的特點建立部署 yaml 檔案
-
http 無狀態應用
建立對應的 service
建立對應的 ingress(路由)對外提供服務
此時已經可以通過域名進行訪問了,這就是我們想要的“最終狀態”,而具體實現細節以及如何維持這個狀態不變,我們無需再關心
為何不直接使用 Service 對外提供服務?
其實我們只需要把 Service 的型別改成 LoadBlancer,阿里雲(其他雲服務商類似)會給 Service 新增一個監聽的 nodePort,再自動建立一個負載均衡,通過 tcp 轉發到 Service 的 nodePort 上(這地方阿里雲有個 bug 每次更新 Service 它都會把轉發型別改成 tcp),可想而知,當我們的 Service 越來越多時,nodePort 的管理成本也就越來越高, k8s 提供了另外一個資源解決這種問題,就是 Ingress
Ingress 工作機制
Ingress 其實就是從 kuberenets 叢集外部訪問叢集的一個入口,將外部的請求根據配置的規則轉發到叢集內不同的 Service 上,其實就相當於 nginx、haproxy 等負載均衡代理伺服器,我們直接使用 Nginx 也可以達到一樣的目的,只是 nginx 這種方式當新增、移除 Service 時動態重新整理會比較麻煩一點,Ingress 相當於都給你做好了,不需要再次實現一遍,Ingress 預設使用的 Controller 就是 nginx。
Ingress controller 可以理解為一個監聽器,通過不斷地與 kube-apiserver 打交道,實時的感知後端 service、pod 的變化,當得到這些變化資訊後,Ingress controller 再結合 Ingress 的配置,更新反向代理負載均衡器,達到服務發現的作用。
配置 Ingress
可以通過 annotations 註解的方式告訴 Ingress 你的配置,例如:如果你使用的是 Nginx-Ingress-Controller,可以通過 nginx.ingress.kubernetes.io/cors-allow-origin:*
來配置 cors,和配置 Nginx 幾乎是一樣的,只是名稱不一樣而已。
所有的 Nginx-Ingress-Controller 的註解可以在這裡查詢 傳送門
可以進入 nginx-Ingress-controller 的 pod 中,新增一些註解,更新,會看到 nginx 重新生成了配置,並“重新啟動”,對比註解和 nginx.conf 很快就能理解 Ingress
Ingress 灰度釋出
可以通過添加註解 nginx.ingress.kubernetes.io/service-match:'test-svc: header("Version", "1.0.0000")'
,來進行灰度釋出,比如匹配 request headers中Version=1.0.0000
的流量轉發到 test-svc,可以匹配 header、query、cookie,同時還可以配置權重等,例如修復問題時只把 10%的流量切進來,待問題驗證得到解決後再設定 100。
我們每次遊戲前端釋出版本都會在 header 中新增一個 Version 引數,我設定灰度釋出之後就可以把特定前端版本的流量自由的切到某個特定的服務中,比較靈活。
滾動更新
當不需要灰度釋出時,僅僅需要對某個 Service 的 pod 進行更新,只需要更改上文 Deployment 中映象版本即可,當 k8s 檢測到 template 欄位更改時,會根據設定的 rollingUpdate strategy 策略進行滾動更新,對於 http 這種無狀態的服務,也能達到業務不間斷更新
-
長連線 有狀態應用
無狀態: 該服務執行的例項不會在本地儲存需要持久化的資料,並且多個例項對於同一個請求響應的結果是完全一致的
有狀態:和上面的概念是對立的了,該服務執行的例項需要在本地儲存持久化資料,比如 socket 長連線
Service 和 Ingress 與無狀態 http 應用基本一致,參照上文部署即可。全部部署完成後,觀察 k8s 後臺可以看到,有 name 分別為 connector-prod-v100000-0、connector-prod-v100000-1、connector-prod-v100000-2 的三個 pod 正在執行,後面的 -n 是由於資源型別設定為 StatefulSet k8s 自動加上的以作區分。
在容器中獲取 pod 資訊
一般來說對於 StatefulSet 我們可能會在容器內知道這個 pod 的 name,這時候就可以採用類似於上面的方法,通過 valueFrom
fieldPath:metadata.name
把 pod name 資訊注入到容器的環境變數中,這種特殊的語法是 Downward API,幫助我們獲取許多 pod 的資訊,可參照傳送門進行學習
滾動更新
對於 StatefulSet 預設的滾動更新策略是 OnDelete, 也就是當這個 pod 被刪除後,k8s 再次建立時會更新映象。即使我們改變這個策略,那麼可以直接對齊進行更新嗎?對於大多數 StatefulSet 是不太合適的(比如 pod 上面有使用者的長連線 如果直接更新使用者會斷線 影響體驗),或者說對於 StatefulSet 的滾動更新一直都是個很複雜的話題,所以如果要更新,推薦使用 灰度釋出 。
灰度釋出的過程與上文 http 一致,對於我們的業務來說,使用者的下一次連線會切到指定的版本上
-
matching 後端有狀態應用
因為後端伺服器不需要外界的訪問,所以建立一個 StatefulSet 啟動後端微服務就可以,啟動後會監聽訊息佇列進行處理並返回資料
RPC 設計
可以看到,我把許多 POD 資訊注入到了容器中
-
環境變數 SERVER_ID 是名稱-環境-版本號-序號命名的可以選擇某一個伺服器;
-
環境變數 SERVER_TYPE 可以選擇某一型別的伺服器;
-
環境變數 VERSION 可以選擇某一版本的伺服器。
因此就可以通過類似於標籤選擇的方式傳送和接受訊息佇列,例如:
在程式碼中獲取環境變數
如果想匹配某一個伺服器:
如果想匹配某一型別的所有伺服器:
在其他應用內傳送 rpc(如在 http 應用內呼叫 matching 應用),按照上面的標籤格式傳送訊息即可:
長連線客戶端傳送 rpc
和前端同學約定,在 socket 請求中按照 event 欄位分為 4 個型別:Push、Notify、Request、Response
Request-Response
客戶端主動發起,要求有迴應,類似於 http:
Notify-Push
客戶端主動發起,不要求迴應
在伺服器需要回應的地方傳送 PUSH 事件
對於處理訊息會發送到哪個後端伺服器,寫一個路由函式發起 rpc 即可
"灰度釋出"
同理,有狀態後端伺服器也不適用滾動更新,因為會丟失業務資訊。因為後端伺服器外界不可訪問,也不能用 Ingress 路由灰度釋出的方式來更新,怎麼辦呢?
其實按照上面的 rpc 設計已經解決了這個問題,例如現在匹配服是 1.0.0000 版本,如果想要釋出 1.0.0001 版本,只需要部署一個 matching-v100001 的應用,客戶端在配置檔案裡把 Version 改成 1.0.0001,那麼下一次請求就會匹配到 matching-v100001 的應用上,這樣可以根據客戶端配置隨時切換伺服器版本號,達到了灰度釋出的效果。
-
cron 定時任務
部署之後定時器就開始運行了,非常簡單。通過 spec.successfulJobsHistoryLimit
和 spec.failedJobsHistoryLimit
,表示歷史限制,是可選的欄位。它們指定了可以保留多少完成和失敗的 Job,預設沒有限制,所有成功和失敗的 Job 都會被保留。然而,當執行一個 Cron Job 時,Job 可以很快就堆積很多,所以一般推薦設定這兩個欄位的值。如果設定限制的值為 0,那麼相關型別的 Job 完成後將不會被保留。
更新
直接更改映象版本號就可以了,下次執行的時候會以新的映象版本執行
結束
至此,基本的遊戲業務框架已經搭建完成,最初的目標都達成了。下一期更新 kubernetes 學習筆記 (四):自動化部署 k8s 實戰
掃描下面的二維碼(或微信搜尋 k8s技術圈
)關注我們的微信公眾帳號,在微信公眾帳號中回覆 加群 即可加入到我們的 kubernetes 討論群裡面共同學習。