search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

kubernetes在騰訊遊戲的應用實踐

摘要

騰訊遊戲是國內最大的網路遊戲社區,致力為遊戲玩家提供完整的快樂解決方案。騰訊互娛高級工程師黃惠波通過一些實際場景為大家解析kubernetes在騰訊遊戲中的應用實踐。

騰訊在線遊戲的容器化應用場景

2014年,我們開啟了容器化探索之路,先回顧一下之前遇到的一些問題。

在物理機時代,資源的交付時間較長,資源的利用率較低,也不能做到隔離。到了xen\kvm虛擬機時代,問題得到了初步的解決,但在彈性伸縮方面仍有不足。隨著Docker技術的興起,我們開始調研Docker在遊戲容器化方面的應用。我們的目標有兩個,一是提高資源利用率,二是通過Docker鏡像來標準化部署流程。

選擇Docker技術之後,我們開始了容器調度平台的選型。我們當時也調研了其它的一些組件,比如Shipyard、Fig等,但這些組件無法支撐海量遊戲容器調度。而自建調度平台的話,時間成本非常的高。就在那時,Google開源了kubernetes(當時的版本是kubernetes v0.4),我們基於這個版本進行了定製和開發,使其成為我們遊戲容器的調度管理平台。

在2015年初的時候,TDocker平台上線。之後,我們開始逐步接入業務。一開始的模式非常簡單,就是把Docker當成虛擬機來使用,但這不意味著遊戲全容器化的實現。

大家知道,對於一項新技術來說,大家都很謹慎,會通過不斷的灰度上線,由點到面的策略推動。截至目前,在全國各地以及加拿大等地區,都有我們的部署點;接入容器數超過兩萬,接入的業務也有兩百多款,包括手游、端游、頁游。在這麼多的業務中,主要分為兩種場景,第一種場景是輕量級虛擬機模式,這類容器承載多個服務進程,需要一個具體的內網IP,可以通稿SSH登錄。另一種是微服務化模式,這種模式會拆分得非常細,每一個容器對應一個服務進程,不需要對外可見的內網IP,可以使用虛擬IP。

接下來會對每一個場景做一些分享。首先來看一下傳統遊戲下的架構。這是非常典型的三層服務架構,包括了接入層、邏輯層、資料庫層。同時,遊戲又分為:全區全服、分區分服兩種類型。對於分區分服類遊戲,滾服對資源的調度非常頻繁,所以我們需要一個高效的調度平台。

容器資源的調度管理基於kubernetes v0.4版本,上圖是一個簡化后的調度框架。在Master端包括ApiServer和Scheduler,接收Web請求,然後做資源調度。在每個node節點上,包括agent進程、Docker進程,還有Lxcfs進程。在鏡像存儲方面,當時用的是Registry V1版,後端用的是ceph存儲。現在,我們自己維護了一個分支,功能上已滿足當前的遊戲需求,並保證運行的穩定。所以在虛擬機模式下,我們不會升級kubernetes,而是把一些好用的功能合併進來。

基於Kubernetes的功能定製與優化

首先講調度器,調度器為數以萬計的容器提供了一個靈活、穩定、可靠的底層資源計算調度引擎。資源的合理分配像是一場博弈,裡面有很多矛盾的地方,需要我們根據遊戲的特點做取捨。

我們在原有的調度策略上根據遊戲特點做了一些定製。比如在網路方面,傳統遊戲的每個容器都需要一個對外可見的實體IP,用戶可以通過SSH登錄到容器裡面,因此對網路資源進行調度。部署容器的時候,會申請network的資源(比如IP)然後進行劃分,綁定到minions對象。這樣調度器調度的時候,就可以通過這些配置信息給容器分配好網路資源。

在社區中,CPU的分配用的是共享CPU的方式,遊戲採用的是一種混部的模式。也就是說,將不同遊戲業務部署到同一台母機,採用綁定核的方式。這樣做一方面可以防止不同遊戲之間的CPU搶佔,另一方面對遊戲成本的核算也會更加精細。例如,某個遊戲用了多少CPU這些指標都是可以量化的。在容器分配CPU時,結全numa技術,對於CPU CORE的分配會盡量地分配到同一個numa node上,這樣可以提升性能,因為同個numa node的CPU訪問只需通過自身的local memory,無需通過系統匯流排。

在磁碟容量分配方面,由於遊戲業務是有狀態的服務,需要存儲,所以我們把磁碟也作為一個可調度的資源分配給容器。還有非親和性的調度。我們知道,在容器的可靠性與碎片優化之間需要一個權衡,讓用戶根據這些策略去選擇、部署自己的容器。例如在非親和性的策略中,用戶希望把容器是分散到各個母機上的,在母機宕機時,可以減少對遊戲的影響。

在IDC Module分配方面,遊戲容器的部署會按地區劃分,比如按照上海、深圳或天津地區的IDC來劃分,所以我們提供了IDC部署策略。由於遊戲需要考慮IDC的穿越流量問題,還有網路延時的問題,所以同一個遊戲的的不同模塊一般會部署到同一個IDC Module下面。

海量應用過程中遇到的問題與解決方案

以上是基於遊戲行業特點定製的調度規劃。在資源調度過程中,也遇到過一些問題,例如容器資源的重複調度。首先在調度過程中它會跟ScheduledPod(已完全調度的容器)進行比較,判斷現在是不是有足夠的資源分配給待調度容器,最後通過Bind(非同步)把Pod信息寫入到ETCD。這裡就會出現一個問題,那就是非同步寫入慢了或者ScheduledPod同步慢了導致ScheduledPods不能及時刷新,Scheduler計算出錯,從而造成資源重複計算。針對這個問題,我們的解決方案是在資源調度完成後,做一個檢測的邏輯,檢測調度的容器信息是否已在ScheduledPod Cache這裡,然後再進入下一個容器的調度。當然這會帶來一定的性能損耗。

解決了這個問題,又產生了另外一些問題,那就是性能的問題。在0.4版本的pod介面是非常低效的,在查每一個pod狀態的時候,會通過實時查所有的Host來確定,設計不太合理。社區也做了一些方案,當時我們也是參考了社區的一些方案做了一些改造,把pod狀態放在Cache裡面,定時更新,從而提高查詢效率。

還有一點就是RESTClient。在kubernetes中,rest API大部分是非同步進行,對於這些非同步的介面的請求,並不會立刻返回結果。這裡有一個輪詢檢測狀態的邏輯,在檢測輪詢的時候有幾秒的休眠,然後進再行下一個輪詢。默認的休眠時間是2秒,這個時間對大部分場景來說有點過長,我們通過一些功能點的調整,從物理機的小時級到虛擬機分鐘級的調度,再到還未調整之前的秒級調度,到現在達到的毫秒級調度,現在的調度能力已能滿足遊戲的需求。

講完調度,再看一下網路方面。網路是非常關鍵、也是最為複雜的一環。在虛擬機模式下,結合公司網路環境為遊戲提供高性能、穩定的網路環境,包括Bridge+VLAN\SR-IOV兩種方案。

先來說,Docker的網路還有kubernetes的網路。對於Docker的NAT網路來說,性能是最大的瓶頸,同時它與物理機或虛擬機的通信路徑不一致也會對業務帶來一些未知的影響。比如,外界不能看到容器真實的IP(必須要使用主機IP+port方式,埠本身就是稀缺資源,並且ip+port的方式,無疑增加了複雜度),TGW仍然可以為業務程序服務。Host模式沒有隔離,也不符合需求。在kubernetes中,pod作為最小調度單元,每個pod有兩個容器,一個就是網路容器,接管pod的網路,提供網路服務,並與其它容器共享net\IPC。另一個是App Container,也就是業務容器,使用第一個網路容器的網路。在這種模式下,容器之間的通訊是非常簡單的。對於pod到pod、pod到物理機、物理機到pod的通訊,我們為每個pod分配一個內網IP,對外可見,也可以互相通訊。

接下來,通過兩個方案給大家分析。首先是Bridge+Vlan的方案,母機都是部署在虛擬化區上,通過Vlan做網路的隔離。在母機上架部署時,創建Bridge設備和VLAN設備並將它們進行關聯。創建容器的時候,使用pipework腳本創建容器所需要的虛擬網卡設備,並把它們綁定到容器和Bridge上,同時會設置容器內的IP、MAC地址以及路由等信息。從而打通容器到外界的網路通信。這裡也做了一些優化以提高性能,這裡可以看到一個性能的對比,其中Bridge相對NAT網路有相當大的提升。這種方式可以滿足一部分遊戲的需求,而有一些業務,像FPS、Moba遊戲等大流量,對網路要求非常高的業務,還有類似MySQL-Proxy這種組件,在Bridge場景下是無法滿足需求的。

所以我們探索了另一種網路方式,就是SR-IOV模式,這種模式在zen\kvm虛擬化上用的比較多,遊戲也需要這種網路方案,因此我們把這種網路方案也結合到Docker容器裡面。

這裡需要硬體的支持,結合SR-IOV技術的網卡,在Docker容器內就可以直接通過驅動來載入虛擬的網卡並使用。使用的方式就如同在一台物理機上使用一個真實的物理網卡一樣,這個虛擬網卡也擁有驅動程序,也擁有PCI BUSID,因此所有的虛擬機網路操作都如同操作普通網卡一般,從而在性能上得到提升。為了進一步發揮SR-IOV的網路性能,還需要對容器的相關網路參數進行配置,主要包括以下幾個方面:VF中斷CPU綁定;關閉物理機的irqbalance;容器內設置RPS(軟中斷均衡)網卡不能中斷均衡,對高性能的網路形成了阻礙。為了解決這個問題,需要設置容器內的軟中斷均衡。通過上述的調整,性能得到了大幅度提升。

這是我們測試的一個結果,這邊是物理機的,這是Bridge,這是SR-IOV,SR-IOV在網路性能方面基本上已經接近了物理機,所以這個對於遊戲大包量、大流量的應用是非常適合的,現在我們把SR-IOV網路作為傳統遊戲里默認的網路模式。

在遊戲容器化過程中,我們希望資源的使用是明確的、合理的、可量化的。所以我們會為每個容器分配固定的資源,比如多少CPU、多少內存,還有需要多大磁碟、IO帶寬。在啟動容器的時候,比如CPU/Memory,通過Cgroup去做一個限制,disk通過xfs quota去做配額的限制。還有Buffered-IO帶寬的限制。

在資源分配方面,我們開始做的是限定的CPU、內存的分配。在容器的整個生命周期,這個配置並非一沉不變,比如在業務運行過程中都會有一些起伏和動態調整,這是遊戲的一張生命周期圖像,生命周期比較短,可能是一年半載的時間,而且這裡在線人數起伏也會比較大,需要動態調整。而動態調整就會涉及兩個方面,一是橫向的水平擴展,二是垂直伸縮。

每個遊戲都會有一個IP,因此橫向拓展比較困難,因而更傾向於穩定的垂直擴縮。在虛擬化時代,擴縮容是有損的,需要重啟機器來實現,而Docker可以做到無損的擴縮容。我們對這個需求做了一些定製開發,比如CPU或者內存,通過修改Cgroup的配額去把它提升上去或是削減下來。

當在線人數上來的時候,我們可以給業務做到無損擴容,不影響業務服務。過了一段時間,當人數降下來時,資源會閑置,我們會對空閑的資源做一些重複利用,將其回收。這個時候做一些縮容,針對縮容我們做一個常態的動作,檢測這些容器的CPU、內存,結合業務的負載、活動、定時驅動。

Buffered IO Throttle需要內核支持,我們與內核團隊進地了緊密的合作,提供了支持 Buffered IO Throttle功能的內核版本。根據容器在母機資源的佔比分配一定比例的IO帶寬。這在某種程序上解決了遊戲之間互相影響的問題。

監控、告警是整個遊戲運營過程中最為核心的功能之一。容器上的監控有別於物理機,cAdvisor和kubenetes結合得比較緊密,是個不錯的方案。但它也會帶來問題,那就是需要自建監控平台,而且它與周邊各系統的兼容性也有待考驗,同時改變運維的使用習慣也需要時間。綜合考慮各種因素后,我們放棄了cAdvisor,重新調研其它方案,希望可以沿用公司成熟的監控平台,而且兼容周邊系統。最終我們選用的是lxcfs + 公司agent的方案,通過lxcfs去實現Docker容器內的虛擬proc文件系統,增強容器的隔離性。

我們這裡以meminfo內存統計信息為例,為大家講解如何通過lxcfs用戶態文件系統實現Docker容器內的虛擬proc文件系。掛載虛擬proc文件系統到Docker容器,通過Docker的volume功能,將母機上的/var/lib/dockerfs/docker-xxx/proc掛載到Docker容器內部的虛擬proc文件系統目錄下/proc/。此時在容器內部/proc/目錄下可以看到一些列proc文件,其中包括meminfo。用戶在容器內讀取/proc/meminfo時,實際上是讀取宿主機上的/var/lib/dockerfs/docker-xxx/proc/meminfo掛載到容器內部的meminfo文件。內核VFS將用戶請求轉發到具體文件系統——fuse,fuse文件系統封裝VFS請求,將請求轉發給Fuse設備(/dev/fuse)。如果設備上有已經處理完成的請求(例如Cache),文件系統獲取處理結果並返回給VFS,VFS再反饋給用戶。用戶庫(fuse daemon)直接訪問Fuse設備,讀取文件系統轉發到設備上的請求,分析請求類型,調用用戶介面處理請求,處理完成後將處理結果返回給設備,再由設備返回給VFS,VFS再反饋給用戶,從而實現容器內的隔離。公司agent可以通過讀取memory等信息,上報到監控平台做分析與報警。同時運維通過SSH登錄到這個容器,通過free、top等命令查看性能,維持了運維原來的使用習慣。

在傳統遊戲里,更多的是有狀態的服務會涉及到數據的存儲,我們通過Docker的volume提供持久化存儲。最開始我們採用HostPath方式,把host上的目錄掛載到容器里(例如/data)作為數據存儲。這種做法非常方便、簡單,無需額外的支持,但數據的安全性、可靠性方面比較差。所以我們採用了另外一種方案,即Ceph。改造kubenetes支持ceph,通過volume掛載,提供更安全、更可靠的數據存儲方案。解決在host故障時,數據丟失的問題,應用場景也變得更加廣泛,包括資料庫存儲,鏡像存儲,容器遷移等。

今年,我們開始支撐第一款微服務化遊戲(極品飛車online),源於之前對kubernetes的使用經驗。在微服化容器的調度中我們沿用了kubernetes,但在版本上重新做了選擇,跟隨著社區的發展,選用了v1.2版。在微服務化模式下,遊戲的架構產生了很大的變化。按功能細分到各個小模塊,通過鏡像交付、分發,最後以容器來部署服務。每個模塊相對獨立,之間信息流交互通過消息組件(例如RabbitMQ)來實現。同時每個容器無須配置內網IP,可以通過域名來訪問。所以在網路方面也有所調整,我們也評估了docker overlay、flannel、vxlan、maxvlan、SR-IOV等,結合其中的優缺點,最後我們選定的方案如下:

1、集群內pod與pod的之間的通信,由於不需要內網IP(可以用虛擬IP)所以採用overlay網路,由flannel組件實現。

2、公司內網到集群內pod通信,例如HAProxy,遊戲某些模塊,採用SR-IOV網路,由自己定製的sriov-cni組件實現。這類pod具備雙重網路,eth0對應overlay網路,eth1對應SR-IOV網路。

3、pod到公司內網之間的通信。在微服務場景下,遊戲的數據存儲,周邊系統等,部署在物理機或者虛擬機上,因此pod到這些模塊、系統的訪問,走的是NAT網路。

4、公網(Internet)接入,採用公司的TGW方案。

在整個微服化平台上,涉及到的關健技術點會更多:

1、網路方案:即上述講到了overlay + SR-IOV + TGW + NAT方案

2、日誌,監控:對於微服務化架構的遊戲,版本的交付都是通過鏡像,不會把公司的agent打到鏡像,所以原來的lxcfs + agent監控就不適應了,所以這裡我們重新打造了一個新的日誌、監控平台,與藍鯨團隊合作,實現了遊戲業務日誌採集;容器健康狀態、性能的監控

3、高可用方案:在資源的部署方面,我們採用了replication controller方式,通過kubernetes的controller manager模塊來監測pod的狀態,在發生故障的時候,實現快速的遷移、恢復服務。另一方面,在load balance場景下,我們採用了HAProxy來實現

4、安全方面:kubernetes集群承載著整個遊戲容器資源的調度、管理。不管是人為誤操作,還是黑客入侵,造成的影響將是非常之大。所以安全是我們需要考慮的重點,結合kubernetes,我們目前做了以幾方面,後面會有更加的安全策略提供

4.1 Authentication 和 Authorization。使用https來加密流量,同時在用戶許可權驗證上,提供了token驗證方式、ABAC許可權認證方式

4.2 Admission Controllers:配置具體的准入規則

4.3 ServiceAccount:主要解決運行在pod里的進程需要調用kubernetes API以及非kubernetes API的其它服務問題

5、配置管理:通過configmap\secret為遊戲提供簡易的配置管理

6、服務發現:kubernetes會為每個pod分配一個虛擬的IP,但這個IP是非固定的,例如pod發生故障遷移后,那麼IP就會發生變化。所以在微服務化遊戲架構下,業務pod之間的訪問更多地採用域名方式進行訪問。在kubernetes生態鏈中,提供了skydns作為DNS伺服器,結合kubernetes的server可以很好的解決域名訪問問題

開始講遊戲容器化的時候談到用鏡像來標準化部署,所以我們花了很多時間打造企業級的鏡像倉庫。目前支持registry v1\v2兩個版本,如右圖所示,在client端(docker)與registry之間採用nginx作為代理,實現v1\v2不同請求的轉發,這樣一來,用戶無需關心到底請求的是v1還是v2。在安全方面,不同類型用戶不同的許可權驗證方案。公司內部用戶接入OA認證,與公司平台打通。外部用戶需要申請訪問許可權,由管理員分配帳號,然後通過分配的帳號來請求。在大批量拉取鏡像的時候,鏡像中心的性能、效率是我們需要考慮的問題。前期我們通過mirror方案來實現,在主要城市部署mirror registry,通過就近原則來拉取鏡像,解決性能瓶頸。後續我們還會採用P2P方案來提升鏡像拉取性能。同時我們定製了Notification Server,用於鏡像pull\push日誌記錄,便於後續分析與審計。在鏡像後端存儲方面,採用ceph集群方案,從而提供穩定、強大的數據存儲。

微服務化之路我們剛剛起航,在面臨挑戰的同時也帶來了機遇。不僅僅是在線業務的探索,我們也會探索離線計算、深度學習等系統的支持。

來源於社區,回饋於社區。後續我們還會更多地參與社區互動,為社區做貢獻。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦