當前位置:
首頁 > 最新 > 在線遊戲場景下的 Kubernetes 網路實踐與優化

在線遊戲場景下的 Kubernetes 網路實踐與優化

為 容 器 技 術 而 生

周威/騰訊互娛後台研發工程師

嘉賓介紹:

曾在華為從事網路協議設計工作,後加入騰訊。先後從事過資料庫開發,分散式存儲 ceph,容器網路方面的研究,目前專註在 Kubernetes 網路和遊戲容器化落地; 開源技術愛好者,積极參与社區,為多個開源項目貢獻過代碼。

閱讀字數:6417 7 分鐘

講師:周威

校對:夏天

大家好,我今天分享的題目是「在線遊戲場景下的 Kubernetes 的網路實踐與優化」。我是來自騰訊互娛的周威,我所在的組是計算資源組,我們這個組主要是負責計算資源的一些分配和調度,主要涉及的是物理機、虛擬機和容器,我這邊主要是負責容器這方面。

TenC 容器平台

我們這邊有一個平台叫 TenC 容器平台,這個平台主要是專註於容器計算的一些資源服務,現在主要有三個方面的功能:

網路架構

第一部分,我先簡略的講一下我們的這個網路架構,大家對 Docker 和 Kubernetes 這個網路應該比較了解了,所以我就簡單提一下吧。

1

Docker 網路方案

Docker的話,這個網路主要是 4 種模式:

我們一般用的話都是把 Bridge 模式和 Container 模式,兩個組合起來用。對於 Kubernetes 這邊,每個 Pod 先起一個 Sandbox 的容器,創建好 Namespace,這個容器是 Bridge 模式的,然後將其他容器用 Container 模式加入。

2

跨主機通信方案

剛才說的是單機的一個架構,因為我們是要跑一個集群,肯定是需要跨母機的網路通信,這就涉及到一個跨主機的網路方案。

這個的話一般主流的有這 3 種方案:

3

Overlay Network

這個一般是有 BGP 和 MacVLAN 的方案,但是這種方式的話對底層網路有一定的要求,而且一般來說在單一的一個數據中心裡使用,如果這個機器比較多的話,會導致路由膨脹的問題,所以說我們綜合了一下這幾個方案,現在用的是第二種的方案,就是 Overlay 這個方案底層用的是 VXLAN,Kubernetes 那邊的話,我們用的 flannel 這個網路插件。

這是我們的網路架構圖,對一個容器,我們分配了一對虛擬的 veth pair。一個是綁定在容器裡面,一個是綁在了網橋上面,還有一個VXLAN的 tunnel,同時在上面配置了兩條路由規則。一個是本機的網段,就是下面這個。通過 cbr0 這個網橋來通信。另外一個就是集群的路由,如果不是本機的網段就會走這個路由。

4

數據流

現在我們來看一下數據的流向,假如有一個容器 C1,他想發一個包到 C2 這個容器。

流向大概是這樣的:首先他發了一個包,因為走了 veth pair 發到了網橋這邊,網橋這裡看一下它的目的地址,目的地址是 192.168.24.66。它會匹配這條規則,這個規則是走到了這個網卡。然後就會在上面封一個頭,這個是內層的,從 C1 發出來的那個報文,然後會封一個 VXLAN 的頭,就是這裡。

對於 flannel 這個插件來說,這個集群所有的全網信息,IP 地址還有 MAC 信息都會存到 etcd 裡面,這個包過來之後,會觸發內核的一個 L3 miss,這時 flannel 就會設一個表項告訴內核,把這個包通過 VXLAN 傳到這邊去,這個 HOST2,這邊拿到這個包的話,tunnel 這裡拿到的包已經去掉 VXLAN 的報頭了,理論上說其實是網卡先發送到這裡,然後通過這裡把包解析出來,他拿到了裡面的 IP 地址,也是這個地址。然後匹配的路由就是自己子網的路由,就發到這個設備上,就是 C2 這個容器,然後這個報文就收到了。

5

SRIOV

剛剛說的是集群裡面的通信,我們分配的是集群內部的一個子網 IP,比如剛剛說的要 192.168,這是一個內網 IP 了,但是有的時候我們還是要分配一個物理 IP,就是跟母機同一個網段的一個 IP。我們用的是 SRIOV,這個東西其實是單根虛擬化的一個縮寫,他以前一般是用在虛擬機上面的。

比如說虛擬機起來的時候,然後要給他分一個在同母機的一個網站的一個 IP,這個需要網卡的支持。但是我們結合這個 CNI 的規範,就是容器網路介面,由 CoreOS 提出,我們把這兩個結合起來,寫一個叫 SRV-CNI 的一個插件,這個插件是開源的。通過這個插件,我們可以對容器分配一個物理 IP,這個 IP 是和母機的網在同一個網段,接下來我會具體的講一下他的這個應用場景。

幾個問題及解決方案

第二部分就是說我講一下我們在搭建這套集群,搭建這種網路模式的時候遇到一些問題,還有我們給的一些解決方案。

1

業務對外暴露服務

對於我們的遊戲業務來說,我們肯定需要接入外網,就是說需要讓外網去訪問我們這個服務,對吧?如果訪問服務的話,有個解決簡單的方案,就是我們給他配一個外網 IP,直接訪問。但是這樣的話,因為我們這個國內的網路情況十分複雜,如果一些接入問題,還有一些網路連通方面的問題就比較麻煩,所以說我們一般不會這麼干。我們會採用公司的一個叫 TGW 的一個服務,就是 Tencent Gateway.

他做了一些接入方面的一些優化,然後我們只需要在後端去把這個 IP 這個配置給它,它就會把流量轉發過來。但是這套方案在以前的那種伺服器,就是物理的伺服器的話,沒什麼問題,但是到了我們這個容器的方案會有一個問題,就是我們這個容器掛掉的時候,我們需要通知 TGW,他說我這個 IP 掛掉了把這個 T 掉,流量就不要的轉過來了。但是我們服務是由 Kubernetes 調度的話,其實這麼來通知他讓他 T 掉不大方便的,相對來耦合性也比較大。

所以我們做了一個這樣一個東西。我們在這個裡面加了一層代理 LB,其實對於公有雲來說,比如說 GCE 和 AWS,原生 Kubernetes 裡面集成它們的 LB 功能的,但是因為我們這個是一個私有雲,所以我們需要自己設計了一個 LB。我們這個 LB 其實是基於 haproxy 或者 nginx,兩個我們都有用,比如說流量從 TGW 這邊過來的話,先通過 LB 轉發到這邊,轉到我們下面這個 Service,他對應的 Pod。

一旦這個設備這個 Pod 增加一個,彈性伸縮,或者說已經掛掉了,然後調度了也一樣吧,增加了一個 Pod。我們這個 LB 會 Watch K8S 的 API 介面,然後我們就感知到這個變化,我們就把這個加到 haproxy 或者 nginx 的配置裡面,Reload 一下,其實對於 LB 這層的話,它變動不是很多。因為他也不會經常去發布它,除非物理 IP 掛掉了,也不會經常去調度它,它的變化就很少。對於 TGW 這層基本上不用變動。而 LB 這層的配置由我們自己感知,自己來做。這樣就和 TGW 互相解耦。這個就是我們對外暴露的一個解決方案。

2

Iptables 性能堪憂

上面這個問題就是我們原來用的是 Iptables,就是 Kubernetes 的 Service 所依賴的 Kube-proxy 使用的是 Iptables 來轉發流量。但是對於遊戲來說遊戲的模塊是比較多的。看一下,這就是我從一個遊戲裡面拿出來的一個列表,基本上是差不多有四十個模塊。

我們測試了一下,因為 Iptables 是一個鏈式的存儲,如果加一下規則的話,需要把所有的規則遍歷一下,就會導致加一條規則需要 5 分鐘。這個 5 分鐘對我們來說是完全不能接受的,這個體驗很差的,比如說你生成一個模塊,5 分鐘才能用。

基於這個問題的話,我們當時也想了幾個方案:

第一個方案最先想到的,既然 Service 的太多了,那就拆一下吧,拆成好幾個集群,每個集群上的 Service 肯定就少一點。但這樣的話集群拆多了的話,肯定我們這個管理成本就上升了,也不好管這麼多集群。所以我們當時就覺得這個方案不怎麼好。

第二個就是我們 Headless 的 Service,Headless 大家都知道吧,就是沒有 IP 的 Service。如果用這種 Service 的話,所有請求獲得後端 IP 地址是通過 DNS 來查到的。就是解析一次 DNS,DNS 那邊隨機給我返回一個 IP。但這個也有一個問題,就是這個遊戲模塊,或者第三方庫,實現的 DNS 解析這個過程其實是沒有那麼靠譜的。比如說有的庫解析一次,然後存起來一小時,就不再解析了,甚至有的地方做得更徹底,程序起來了,把它解析一下,存起來,程序不重啟就不管了,總的來說 DNS 解析有的時候沒那麼可控,覺得這個方案也沒那麼好使。

然後找了第三個方案。IPVS 的方案,原來的 Iptables 是基於 List,鏈表的方式,它查的比較慢,但是IPVS是基於 Hash 的存儲,它查的就很快了。我們也試了一下,一樣的規則數量,1w 條,增加一條,只需要 2 分鐘,這個就非常快了。在調度或者生產的時候這個地方就不存在瓶頸了。但是這個IPVS用的話還有點小問題,這個我們下一節再說。

3

訪問外部模塊許可權擴大

接下來還有一個問題,我們集群裡面跑的容器,需要訪問一些外部的周邊系統,一般來說在我們公司的話,這些周邊系統都會做一些許可權控制和一些白名單的限制。或者說簡單一點,就像 MySQL 的那樣,它也有基於 IP 的許可權驗證。如果直接通過 NAT 方式去訪問周邊服務的話,這個時候外部的服務,看到的就是母機的 IP。

如果我們加白名單的話,就需要把母機的 IP 加到白名單裡面,但是因為調度的原因,我們並不知道這個容器下一次會跑到哪個母機上面,那這樣的話,我們需要把所有 IP 都加到白名單里。這樣做其實是有很大的安全風險,這裡也需要一個好的解決方案。

俗話說的好,所有的軟體問題都可以通過加一個中間層來解決。然後我們也是這麼解決的,就是中間加一層,加一個 Service。對於每個容器來說,它如果訪問這個 DB,我們給它創建一個 Service,這個 Service 把所有的請求拿過來,然後把它轉到這個 DB 去。這個時候對於這個容器來講,它訪問的就是這個 Service,對於這個 DB 來講,它看到的就是這幾個 IP,它不會看到母機 IP。這個時候我們就給這三個 IP 授權,其實我剛剛說的那個 SRV,分配一個物理 IP,其實這個地方也用到了,就是這種 IP。而且在這個地方,我們其實對 Kubernetes 稍微做了一點改動。

Kubernetes的 Service 的這個名字是有限制的,就是 xx,或者 aaa-bbb 什麼的,不能帶點的名字。但是有的時候會有一個問題,就是如果用域名來訪問的話,比如說我們這個Service設置一個名字叫 aaa,那麼它的域名就是 aaa,再訪問的時候,如果這個不是一個 DB,比如說這個 Web 服務,它用的是 https 的,這會導致域名和證書不匹配了。這個不匹配的話,雖然我們可以忽略這個這書,或者忽略這個錯誤,但是這個的話也是會引起安全風險。所以說我們就改進一下,把這個規則放開了,讓它可以設加點的這個 Service 名字。

踩的一些坑

好,上面三個就是我們的一些的問題的解決方案。下面就是第三個部分,遇到一些坑,我們的做法。

1

IPVS 功能尚不成熟

第一個是 Kubernetes 的 IPVS 功能,我們當時測IPVS其實是比較早的一個時候,大概是今年夏天的時候,那個時候IPVS這個功能還在開發,我們那時候跑的是 1.7 的版本,我們就把IPVS的 Patch 給 Backport 過來進行測試,等到 1.8 發布的話我們就遷移到了 1.8 這個版本。

但是 1.8 的這個版本的IPVS功能其實是 Alpha 特性。我們測的時候也遇到了一些問題,我們合入了主線的一些修復,還有自己提交的一些 Patch,這些都 Merge 到主線了,會在 1.9 版本發布,IPVS也變成了 Beta 版。1.9 應該是今天才發布,所以說如果大家也是像我們一樣用IPVS這個功能的話,我還是建議大家第一天就用這個版本。

2

跨 Namespace 轉發問題

有一個就是我剛剛說的那個代理。這個代理的話,它是一個 4 層轉發,就比如說你去訪問那個 mysql,不可能說你要訪問 mysql,就寫個 mysql 代理,轉一下協議。訪問 mongodb 就寫個 mogodb 的代理。

所以這裡用的是 4 層轉發,當然 haproxy 也可以,但是我們基於其他原因考慮,用的是 Iptables 或者 IPVS。如果用IPVS的話,我們遇到這樣一個問題,我們設想是這麼來轉發的,在一個母機上面有個容器,我們通過訪問代理的這個 Service 轉到了代理這個容器上面,由代理容器訪問外部服務,數據流嚮應該是這樣的,這個圖應該比較直觀了。

但是我們測試的時候發現,這樣做流量不通,過不去。我們抓包的時候發現就流量從這裡過來了,這邊也收到了,但是沒往這轉,在這裡斷掉了。我們就很奇怪,這是怎麼回事,經過挺久的分析查找,翻了下內核代碼。看這裡,IPVS這裡有個標誌位,IPVS轉過的數據包,它再來的時候就不會在轉發了,這個可能是內核防止形成環路,或者重複轉發的一個考慮。但是在我們這個場景上面,因為我們這邊轉過去的話,在容器裡面我們還要去轉發一次。它如果不轉的話,這個流量肯定出不去了。

我們認為這樣做,在傳統的同一個 Namesapce 的情況下,這樣轉發是沒有問題的,但是如果跨了 Namespace 的話的,也不轉發的話是不合理的,因為不同 Namespace 之間的轉發不應該受到影響。於是我們就給內核提交了一個 Patch,社區接受了我們的 Patch 把這個 bug 修復了。

根據這個郵件,大家可以看一下,這應該是 11 月 4 號合入主線的,大概在上月底至本月初合到了所有 Stable 分支,所以說如果大家也想這樣用的話,就用新一點版本的內核,或者把這個 Patch 自己合過來吧。

3

網卡丟包問題

最後一個問題就是網卡丟包問題。我們測試的時候發現流量大了,或者說服務性能跑起來之後網卡就丟包。

我們登上母機看了一下,發現有一個內核進程,這個進程大家應該認識吧,就是軟中斷的處理進程,它跑的 CPU 非常高,這個這後面這個標號零,就是代表它是運行在在 CPU#0 的。這個中斷比較高,我們看了一發現是網卡中斷佔用比較高。因為我們這邊是開了 SRIOV 的機器上,對於我們的網卡來說,有 16 個收發隊列,但是如果我們使用 SRIOV 的話,會給每個 VF 分配一個隊列,我們一般會開啟 12 個 VF,這樣 PF 只剩下 4 個隊列可用。

進一步分析發現,有幾個業務進程也跑在 CPU#0 上面,我們其實是把 VF 的中斷綁定到了前 4 個 CPU 上面了,這樣的話業務進程也分配到 CPU#0 的話,佔用了 CPU 就導致網卡中斷處理不過來,然後就發生丟包。對於這個問題來說,其實最徹底的解決方案是使用 GRO 來減少 CPU 使用率,但是 GRO 這個特性需要網卡和內核支持,我們的環境沒法開啟。

我們就使用了另一個方案,把前 4 個核全部預留出來,全力的跑網卡中斷程序,讓業務進程不會佔用這 4 個 CPU,這樣就解決了網卡丟包問題。這就是我們這解決的一個方案。

Q&A

Q1:我想先問一下,就是你剛才提到的那個遊戲規模是怎麼樣的? 還有一個是 flannel 性能,剛才提到的 Iptables 問題是不是 flannel 造成的?還有一個就是你們彈性伸縮的話,為什麼沒有使用 Kubernetes 的方案?

A:第一個問題,我們的遊戲規模,這個問題比較敏感,其實我剛那個圖,就是服務的那個列表。就是從一個實際運行的遊戲上面截取出來的,第二個問題,Iptables 的性能其實就是它自己的實現的問題,和 flannel 是沒有關係的,因為你可以回去試一下,在一個機器上配置 1w 條 Iptables 規則的話,再加一條,就會需要很久時間,或者刪一條,也是一樣的。flannel 的話開啟 UDP 的 RSS,性能會好很多,最後那個問題,其實我們就是用的 Kubernetes 的伸縮方案,這個 LB 只是用來對接 TGW,它只是感知 Pod 變化,它自己並不參與伸縮的過程。

Q2:老師你好,我是搞網路的,我就說你們 MTU 設置多大?還有針對移動網路有那些優化?

A:對於更外層一點的接入層,其實是 TGW 做的,他們專門做這些優化的團隊。是比較專業的對國內這個網路環境做優化的,我們更專註在集群網路在這裡,這些要是由我們團隊來做的話就太複雜了。MTU 的話因為我們用的一般就是 1500,因為公司網路的限制,巨型幀的標準不統一還有一些兼容性考慮,我們沒有使用。其實開啟了 GSO 的話已經可以可以顯著減少 CPU 的消耗了。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 K8sMeetup 的精彩文章:

手把手教你度過運維 Kubernetes 瓶頸期
KubeCon在Austin 盛大舉行、微軟開源 Virtual Kubelet 工具

TAG:K8sMeetup |