七牛容器SDN技術與微服務架構實踐
Docker的橫空出世很大程度上推動了容器技術的熱度和發展。容器技術和傳統的虛擬化技術有很大的不同,具體包括:首先是相對於傳統的虛擬機,以前一個虛擬機裏做的事情,要打散成很多個容器去做,它們各自的職能會更少;第二點是會造成以前一個虛機的IP會變成很多個容器的多個IP,容器之間的關係會變得更加複雜;第三點是整個網絡中的網絡端點數量呈現一個上升的趨勢;第四點是容器的生命周期其實會更短。此外,容器由於其輕量級的優勢,可能會被不停地調度,從一台機器調度到另外一台機器,根據資源的負載均衡,容器的生命周期其實是比虛機要短的。以上幾點其實都是給傳統的虛擬化網絡提出的一些新的挑戰。
本文是對七牛資深架構師徐兆魁在2015全球架構師峰會上所做的同題目演講的整理,主要分為三個部分。第一部分簡單介紹SDN的內涵以及它和容器之間的關係。第二部分介紹一些現有圍繞容器的一些開源的SDN的解決方案,包括Flannel、Calico和Weave。第三部分是七牛在這部分的一些實踐和案例的分享。
關於SDN和容器
作為近年來比較熱的一個概念,眾所周知SDN是Software Defined Network的縮寫,即軟件定義網絡。但不同的人對SDN有不同的理解。在廣義上,隻要是你通過軟件實現了一個東西,然後那個東西能夠靈活地去達到網絡上麵的部署和伸縮,這就可以被認為是SDN。在後文中會對Flannel、Calico、Weave這三個解決方案進行分析,並從控製層和轉發層來著重探討它們的技術實現,雖然它們並沒有宣稱自己是SDN的解決方案。
由於容器技術給傳統的虛擬化網絡提出了一些新的挑戰,所以圍繞Docker產生了很多不同的網絡解決方案,以彌補Docker在這些方麵的不足。
圍繞容器的開源的SDN解決方案
Docker自己的網絡方案比較簡單,就是每個宿主機上會跑一個非常純粹的Linux Bridge,這個Bridge可以認為是一個二層的交換機,但它的能力有限,隻能做一些簡單的學習和轉發。然後出來的流量,出網橋的流量會走IPTABLES,做NAT的地址轉換,然後靠路由轉發去做一個宿主之間的通信。但是當真正用它的網絡模型部署一個比較複雜的業務時,會存在很多問題,比如容器重啟之後IP就變了;或者是由於每台宿主機會分配固定的網段,因此同一個容器遷到不同宿主機時,它的IP可能會發生變化,因為它是在不同的網段;同時,NAT的存在會造成兩端在通訊時看到對方的地址是不真實的,因為它被NAT過;並且NAT本身也是有性能損耗等。這些問題都對使用Docker自己的網絡方案造成了障礙。
Flannel
圖1
Flannel是CoreOS團隊針對Kubernetes設計的一個覆蓋網絡(Overlay Network)工具,如圖1所示。它們的控製平麵其實很簡單,如圖2所示。
圖2
每個機器上麵的Flannel進程會監聽ETCD,向ETCD申請每個節點可用的IP地址段,並且從ETCD拿到其他所有宿主機的網段信息,這樣它就可以做一些路由。對於它的轉發平麵(圖3)——轉發平麵主要表現數據流的流向——它在Docker進來的網橋基礎之上,又創建了一個新的叫VXLAN的設備(圖4),VXLAN是一個隧道的方案,它可以把一個二層的包,前麵加一個包頭,然後再把整個包作為物理網絡的一個包,去物理網絡裏麵去路由,流轉。
圖3
圖4
為什麼會要有這個東西呢?因為通常虛擬網絡的IP和MAC在物理的網絡其實是不認識的。因為識別IP需要物理網絡支持,這個其實是個隧道的方案。
總結一下Flannel方案,可以看出它並沒有實現隔離,並且它也是按照網段做IP分配,即一個容器從一台主機遷到另外一台主機的時候,它的地址一定會變化。
Calico
圖5
Calico的思路比較新,如圖5所示。它把每個操作係統的協議棧認為是一個路由器,然後把所有的容器認為是連在這個路由器上的網絡終端,在路由器之間跑標準的路由協議——BGP的協議,然後讓它們自己去學習這個網絡拓撲該如何轉發。所以Calico方案其實是一個純三層的方案,也就是說讓每台機器的協議棧的三層去確保兩個容器,跨主機容器之間的三層連通性。對於控製平麵(圖6),它每個節點上會運行兩個主要的程序,一個是它自己的叫Felix,左邊那個,它會監聽ECTD中心的存儲,從它獲取事件,比如說用戶在這台機器上加了一個IP,或者是分配了一個容器等。接著會在這台機器上創建出一個容器,並將其網卡、IP、MAC都設置好,然後在內核的路由表裏麵寫一條,注明這個IP應該到這張網卡。綠色部分是一個標準的路由程序,它會從內核裏麵獲取哪一些IP的路由發生了變化,然後通過標準BGP的路由協議擴散到整個其他的宿主機上,讓外界都知道這個IP在這裏,你們路由的時候得到這裏來。
圖6
關於Calico這裏討論一個問題,因為它跑的是純三層的協議,所以其實它對物理架構有一定的侵入性。Calico官方稱,你可以跑在一個大二層的網絡裏麵。所謂大二層就是沒有任何三層的網關,所有的機器、宿主機、物理機在二層是可達的。這個方案其實有一定的弊端,事實上在很多有經驗的網絡工程師眼裏,一個大二層其實是一個單一的故障率,也就是說任何一個都會有一定的硬件風險會讓整個大二層癱瘓。
另外,Calico跑在了一個三層網關的物理網絡上時,它需要把所有機器上的路由協議和整個物理網絡裏麵的路由器的三層路全部用BGP打通。這其實會帶來一個問題,這裏的容器數量可能是成千上萬的,然後你讓所有物理的路由學習到這些知識,其實會給物理集群裏的BGP路由帶來一定的壓力,這個壓力我雖然沒有測過,但據專業的網絡工程師告知,當網絡端點數達到足夠大的時候,它自我學習和發現拓撲以及收斂的過程是需要很多的資源和時間的。
轉發平麵(圖7)是Calico的優點。因為它是純三層的轉發,中間沒有任何的NAT,沒有任何的overlay,所以它的轉發效率可能是所有方案中最高的,因為它的包直接走原生TCP/IP的協議棧,它的隔離也因為這個棧而變得好做。因為TCP/IP的協議棧提供了一整套的防火牆的規則,所以它可以通過IPTABLES的規則達到比較複雜的隔離邏輯。
圖7
Weave
圖8
Weave方案比較有趣,如圖8所示。首先它會在每台機器上跑一個自己寫的Router程序起到路由器的作用,然後在路由器之間建立一個全打通的PC連接,接著在這張TCP的連接網裏麵互相跑路由協議,形成一個控製平麵(圖9)。可以看出,它的控製平麵和Calico一致,而轉發平麵(圖10)則是走隧道的,這一點和Flannel一致,所以Weave被認為是結合了Flannel和Calico這兩個方案的特點。
圖9
圖10
圖11所示是它的服務發現與負載均衡的一個簡單的方案,它在每個容器會起兩個網卡,一個網卡連著自己起的可以跟其他宿主機聯通的網橋;另一個網卡綁在原生Docker的一個網橋上,並在這個網橋上監聽一個DNS的服務,這個DNS實際上嵌在Router裏麵,即它可以從Router裏學習到一些服務的後端的一些配置。所以這時容器如果發起DNS查詢,實際上會被路由導到宿主機上,DNS Server上,然後DNS server做一些響應。它們官方負載均衡也是靠這個,但是這其實是一個短板,因為我們更偏向於四層或者是七層更精細的負載均衡。
在隔離方麵,Weave的方案比較粗糙,隻是子網級的隔離(圖12)。比如說有兩個容器都處在10.0.1-24網段,那麼它會在所有的容器裏麵加一條路由說該網段會走左邊的網橋出去,但是所有非此網段的流量會走Docker0,這個時候Docker0和其他是不聯通的,所以它就達到一個隔離的效果。
圖11
圖12
三個方案總結
總結一下:
1. Flannel僅僅作為單租戶的容器互聯方案還是很不錯的,但需要額外的組件去實現更高級的功能,例如服務發現與負載均衡。
2. Calico有著良好的性能和隔離策略,但其基於三層轉發的原理對物理架構可能會有一定的要求和侵入性。
3. Weave自帶DNS,一定程度上能解決服務發現,但因隔離功能有限,若作為多租戶的聯通方案還稍加欠缺。
4. 另外,Calico和Weave都使用了路由協議作為控製麵,而自主路由學習在大規模網絡端點下的表現其實是未經驗證的,曾谘詢過相關的網絡工程師,大規模端點的拓撲計算和收斂往往需要一定的時間和計算資源。
七牛的具體實踐
業務需求
七牛實際上一直在擁抱容器帶來的變革,擁抱新型的微服務架構理念。所以構建了一套容器平台,這麼做的目的,一方麵想推進通過將已有業務容器化簡化研發和上線流程,另一方麵也想通過這個方式去滿足用戶的一些計算需求,畢竟計算和數據離得越近越好。
所以我們業務上對網絡的需求是:
1. 首先一點,是能夠運行在底層異構的基礎網絡上,這一點對於推進已有業務的容器化來說是很重要的,否則會涉及到基礎網絡的大規模變更,這是無法接受的。
2. 我們試圖構造一個對容器遷移友好的網絡結構,允許容器在必要情況下發生調度。
3. 我們認為服務發現和負載均衡對業務來說是個基礎而普適的需求,尤其是在倡導微服務架構的今天,一個設計良好的組件應該是可水平伸縮的,因此對於組件的調用方,服務發現和負載均衡是非常必要的功能。當然有人會說這個功能和網絡層無關,而應由應用層去實現,這個說法挺有道理,但後麵我會講到由網絡層直接支持這兩個功能的好處。
4. 為了滿足七牛本身已有的一些對隔離有要求的服務,並滿足上層更豐富的權限模型和業務邏輯,我們試圖將隔離性做的更加靈活。
在這幾個需求的驅動下,我們最終嚐試跳出傳統網絡模型的束縛,嚐試去構造一個更加扁平而受控的網絡結構。
轉發平麵
首先,在轉發層麵,為了包容異構的基礎網絡,我們選擇了使用Open vSwitch構造L2 overlay模型,通過在OVS之間聯通vxlan隧道來實現虛擬網絡的二層互通。如圖13所示。但隧道通常是有計算成本的,隧道需要對虛擬二層幀進行頻繁解封包動作,而通用的cpu其實並不擅長這些。我們通過將vxlan的計算量offload到硬件網卡上,從而將一張萬兆網卡的帶寬利用率從40%提升到95%左右。
選擇overlay的另一個理由是,據我們目前所了解到,當下硬件的設備廠商在對SDN的支持上通常更偏向於overlay模型。
圖13
控製平麵
而在控製層麵,我們思考了容器和傳統虛機的一些不同:
前麵提到,微服務架構下,每個容器的職責相對虛機來說更加細化和固定,而這會造成容器與容器間的依賴關係也相對固定。那麼每台宿主機上的容器可能產生的outbound其實也是可推演的。如果進一步想的話,其實推演出來的理論範圍通常會遠大於容器實際產生的 outbound。所以我們嚐試使用被動的方式實現控製指令的注入。因此我們引入了OpenFlow作為控製麵的協議。OpenFlow作為目前SDN 控製平麵的協議標準,它有著很強的表達能力。從包匹配的角度看,它幾乎可匹配包頭中的任意字段,並支持多種流老化策略。此外,擴展性也很好,支持第三方的 Vendor 協議,可以實現標準協議中無法提供的功能OpenFlow可以按Table組織流表,並可在表間跳轉(這一點其實和IPTABLES很像,但OpenFlow的語義會更加豐富)。配合OpenFlow的這種Table組織方式,可以實現相對複雜的處理邏輯。如圖14所示。
圖14
選擇了OpenFlow,我們的控製平麵會顯得很中規中矩,也就是邏輯上的集中式控製,沒有weave/calico的P2P那麼炫酷。在這樣的結構下,當ovs遇到未知報文時,會主動提交包信息給Controller,Controller會根據包信息判斷後,給ovs下發合適的流表規則。為了實現負載均衡和高可用,我們給每組ovs配置多個Controller。如圖15所示。
例如:
1. 對於非法流量Controller會讓ovs簡單丟棄,並在將來一段時間內不要再詢問。
2. 對於合法流量,Controller會告訴ovs如何路由這個包並最終到達正確的目的地。
圖15
服務發現和負載均衡
關於服務發現和負載均衡,我們提供了以下幾個對象模型:
1. Container,容器實例,多個Container構成一個Pod(實體)。
2. Pod,每個Pod共享一個網絡棧,IP地址和端口空間(實體)。
3. Service,多個相同Pod副本構成一個Service,擁有一個Service IP(邏輯)。
4. 安全組,多個Service構成一個安全組(邏輯)。
其中,可動態伸縮的關係是一個Service與其後端Pod的映射,這一步是靠平台的自動服務發現來完成。隻要發起對Service IP的訪問,那麼Service本身就會完成服務發現和負載均衡的功能。後端Pod如果發生變動,調用方完全無需感知。
從實現上來說,我們將這個功能實現到了每個宿主機上,每個宿主機上的這個組件會直接代理本機產生的Service流量,這樣可以避免額外的內網流量開銷。
功能上,我們實現了IP級的負載均衡,什麼意思,就是每個Service IP的可訪問端口與後端Pod實際監聽的端口是一致的,比如後端Pod監聽了12345,那麼直接訪問Service IP的12345端口,即可直接訪問,而無需額外的端口配置。
這裏對比一下常見的幾種負載均衡:
1. 比DNS均衡更加精細。
2. 比端口級的負載均衡器更容易使用,對業務入侵更小。
另外,7層的負載均衡實際上有很大的想象空間,我們實現了大部分Nginx的常用配置,使用者可以靈活配置。業務甚至還可以指定後端進行訪問。
安全組
在隔離層麵,我們在邏輯上劃分了安全組,多個service組成一個安全組,安全組之間可以實現靈活的訪問控製。相同安全組內的容器可以互相不受限製的訪問。其中最常見的一個功能是,將安全組A中的某些特定的Service Export給另一組安全組B。Export後,安全組B內的容器則可以訪問這些導出的Service,而不能訪問A中的其他Service。如圖16所示。
圖16
介紹完了我們網絡的基礎功能,這裏通過分析兩個七牛的實際案例來說明這樣的結構是如何推動業務的架構演變的。
案例分析1——七牛文件處理FOP架構演變
第一個是七牛的文件處理架構(File OPeration),如圖17所示。文件處理功能一直是七牛非常創新、也是很核心的一個功能,用戶在上傳了一個文件後,通過簡單地在資源url中添加一些參數,就能直接下載到按參數處理後的文件,例如你可以在一個視頻文件的url中添加一些參數,最終下載到一張在視頻某一幀上打了水印並旋轉90度並裁剪成40x40大小的圖片。
圖17
而支撐這樣一個業務的架構,在早期是非常笨拙的。圖17左側是業務的入口,右側是實際進行計算的各種worker集群,裏麵包含了圖片處理,視頻處理,文檔處理等各種處理實例。
1. 集群信息寫死在入口配置中,後端配置變更不夠靈活。
2. 業務入口成為流量穿透的組件(業務的指令流與數據流混雜在一起)。
3. 突發請求情況下,應對可能不及時。
後麵負責文件處理的同事將架構進化成了這樣(如圖18)。
1. 增加Discovery組件,用於集群中worker信息的自動發現,每個worker被添加進集群都會主動注冊自己。
2. 業務入口從Discovery獲取集群信息,完成對請求的負載均衡。
3. 每個計算節點上新增Agent組件,用於向Discovery組件上報心跳和節點信息,並緩存處理後的結果數據(將數據流從入口分離),另外也負責節點內的請求負載均衡(實例可能會有多個)。
4. 此時業務入口隻需負責分發指令流,但仍然需要對請求做節點級別的負載均衡。
圖18
圖19描述的是文件處理架構遷移到容器平台後的早期結構,較遷移之前有如下變更。
1. 每個Agent對應一個計算worker,並按工種獨立成Service,比如Image Service,Video Service。
2. 取消業務的Discovery服務,轉由平台自身的服務發現功能。
3. 每個Agent的功能退化:
l 無需和Discovery維護心跳,也不在需要上報節點信息。
l 由於後端隻有一個worker,因此也不需要有節點內的負載均衡邏輯。
4. 業務入口無需負載均衡,隻需無腦地請求容器平台提供的入口地址即可。
圖19
圖20是遷移後發生的另一次演變,實際上上一個階段中,每個Agent仍然和計算實例綁定在一起,而這麼做其實隻是為了方便業務的無痛遷移,因為Agent本身的代碼會有一些邏輯上的假設。
這張圖中,我們進一步分離了Agent和worker,Agent獨立成一個Service,所有的worker 按工種獨立成Service,這麼分離的目的在於,Agent是可能會有文件內容緩存、屬於有狀態的服務,而所有的worker是真正幹活、無狀態的服務。分離之後的好處在於,worker的數量可以隨時調整和伸縮,而不影響Agent中攜帶的狀態。
好處:
1. 可以看到,相比於最早的架構,業務方隻需集中精力開發業務本身,而無需重複造輪子,實現各種複雜的服務發現和各種負載均衡的代碼。
2. 另外,自從部署到容器平台之後,平台的調度器會自動更具節點的資源消耗狀況做實例的遷移,這樣使得計算集群中每個節點的資源消耗更加均衡。
案例分析2——用戶自定義文件處理UFOP架構演變
另一個案例是七牛的用戶自定義文件處理。
用戶自定義文件處理(User-defined File OPeration,UFOP)是七牛提供的用於運行用戶上傳的文件處理程序的框架。他的作用實際上和前麵介紹的是一致的,隻是允許用戶自定義他的計算實例。例如七牛現有的鑒黃服務,就是一個第三方的worker,可以用於識別出一個圖片是否包含黃色內容。而正是由於引入了用戶的程序,所以UFOP在架構上和官方的 FOP的不同在於,UFOP對隔離有要求。
圖20是原本UFOP的架構,事實上,這裏已經使用了容器技術進行資源上的隔離,所有的容器通過Docker Expose將端口映射到物理機,然後通過一個集中式的注冊服務,將地址和端口信息注冊到一個中心服務,然後入口分發服務通過這個中心服務獲取集群信息做請求的負載均衡。
而在網絡的隔離上,由於Docker自身的弱隔離性,這個架構中選擇了禁止所有的容器間通信,而隻允許入口過來的流量。這個隔離尺度一定程度上限製了用戶自定義程序的靈活性。
圖20
而在遷移到容器平台後,由於有靈活的安全組控製,不同用戶上傳的處理程序天然就是隔離的,而用戶可以創建多種職責不同的Service來完成更複雜的處理邏輯。如圖21所示。
另外,遷移後的程序將擁有完整的端口空間,進一步放開了用戶自定義處理程序的靈活性。
圖21
以上內容是七牛首度公開關於多租戶虛擬網絡方麵的探索和實踐,並總結了我們對這一領域的觀察和思考,還有很多更為細節的點值得探討,望以後能與大家做更充分的交流。
最後更新:2017-09-08 10:32:36