做者 | 葉磊(稻農)阿里巴巴高級技術專家前端
本文整理自《CNCF x Alibaba 雲原生技術公開課》第 25 講,點擊直達課程頁面。後端
關注「阿里巴巴雲原生」公衆號,回覆關鍵詞**「入門」**,便可下載從零入門 K8s 系列文章 PPT。api
導讀:本文將基於以前介紹的基本網絡模型,進行更深刻的一些瞭解,但願給予讀者一個更廣更深的認知。首先簡單回顧一下容器網絡的歷史沿革,剖析一下 Kubernetes 網絡模型的由來;其次會剖析一個實際的實現(Flannel Hostgw),展示了數據包從容器到宿主機的變換過程;最後對於和網絡息息相關的 Servcie 作了比較深刻的機制和使用介紹,經過一個簡單的例子說明了 Service 的工做原理。網絡
容器網絡發端於 Docker 的網絡。Docker 使用了一個比較簡單的網絡模型,即內部的網橋加內部的保留 IP。這種設計的好處在於容器的網絡和外部世界是解耦的,無需佔用宿主機的 IP 或者宿主機的資源,徹底是虛擬的。它的設計初衷是:當須要訪問外部世界時,會採用 SNAT 這種方法來借用 Node 的 IP 去訪問外面的服務。好比容器須要對外提供服務的時候,所用的是 DNAT 技術,也就是在 Node 上開一個端口,而後經過 iptable 或者別的某些機制,把流導入到容器的進程上以達到目的。負載均衡
該模型的問題在於,外部網絡沒法區分哪些是容器的網絡與流量、哪些是宿主機的網絡與流量。好比,若是要作一個高可用的時候,172.16.1.1 和 172.16.1.2 是擁有一樣功能的兩個容器,此時咱們須要將二者綁成一個 Group 對外提供服務,而這個時候咱們發現從外部看來二者沒有相同之處,它們的 IP 都是借用宿主機的端口,所以很難將二者歸攏到一塊兒。less
在此基礎上,Kubernetes 提出了這樣一種機制:即每個 Pod,也就是一個功能彙集小團伙應有本身的「身份證」,或者說 ID。在 TCP 協議棧上,這個 ID 就是 IP。ide
這個 IP 是真正屬於該 Pod 的,外部世界無論經過什麼方法必定要給它。對這個 Pod IP 的訪問就是真正對它的服務的訪問,中間拒絕任何的變造。好比以 10.1.1.1 的 IP 去訪問 10.1.2.1 的 Pod,結果到了 10.1.2.1 上發現,它實際上借用的是宿主機的 IP,而不是源 IP,這樣是不被容許的。Pod 內部會要求共享這個 IP,從而解決了一些功能內聚的容器如何變成一個部署的原子的問題。微服務
剩下的問題是咱們的部署手段。Kubernetes 對怎麼實現這個模型實際上是沒有什麼限制的,用 underlay 網絡來控制外部路由器進行導流是能夠的;若是但願解耦,用 overlay 網絡在底層網絡之上再加一層疊加網,這樣也是能夠的。總之,只要達到模型所要求的目的便可。性能
容器網絡的網絡包到底是怎麼傳送的?阿里雲
咱們能夠從如下兩個維度來看:
第一個維度是協議層次。
它和 TCP 協議棧的概念是相同的,須要從兩層、三層、四層一層層地摞上去,發包的時候從右往左,即先有應用數據,而後發到了 TCP 或者 UDP 的四層協議,繼續向下傳送,加上 IP 頭,再加上 MAC 頭就能夠送出去了。收包的時候則按照相反的順序,首先剝離 MAC 的頭,再剝離 IP 的頭,最後經過協議號在端口找到須要接收的進程。
第二維度是網絡拓撲。
一個容器的包所要解決的問題分爲兩步:第一步,如何從容器的空間 (c1) 跳到宿主機的空間 (infra);第二步,如何從宿主機空間到達遠端。
我我的的理解是,容器網絡的方案能夠經過接入、流控、通道這三個層面來考慮。
第一個是接入,就是說咱們的容器和宿主機之間是使用哪種機制作鏈接,好比 Veth + bridge、Veth + pair 這樣的經典方式,也有利用高版本內核的新機制等其餘方式(如 mac/IPvlan 等),來把包送入到宿主機空間;
第二個是流控,就是說個人這個方案要不要支持 Network Policy,若是支持的話又要用何種方式去實現。這裏須要注意的是,咱們的實現方式必定須要在數據路徑必經的一個關節點上。若是數據路徑不經過該 Hook 點,那就不會起做用;
第三個是通道,即兩個主機之間經過什麼方式完成包的傳輸。咱們有不少種方式,好比以路由的方式,具體又可分爲 BGP 路由或者直接路由。還有各類各樣的隧道技術等等。最終咱們實現的目的就是一個容器內的包經過容器,通過接入層傳到宿主機,再穿越宿主機的流控模塊(若是有)到達通道送到對端。
這個方案採用的是每一個 Node 獨佔網段,每一個 Subnet 會綁定在一個 Node 上,網關也設置在本地,或者說直接設在 cni0 這個網橋的內部端口上。該方案的好處是管理簡單,壞處就是沒法跨 Node 遷移 Pod。就是說這個 IP、網段已是屬於這個 Node 以後就沒法遷移到別的 Node 上。
這個方案的精髓在於 route 表的設置,如上圖所示。接下來爲你們一一解讀一下。
第一條很簡單,咱們在設置網卡的時候都會加上這一行。就是指定個人默認路由是經過哪一個 IP 走掉,默認設備又是什麼;
第二條是對 Subnet 的一個規則反饋。就是說個人這個網段是 10.244.0.0,掩碼是 24 位,它的網關地址就在網橋上,也就是 10.244.0.1。這就是說這個網段的每個包都發到這個網橋的 IP 上;
第三條是對對端的一個反饋。若是你的網段是 10.244.1.0(上圖右邊的 Subnet),咱們就把它的 Host 的網卡上的 IP (10.168.0.3) 做爲網關。也就是說,若是數據包是往 10.244.1.0 這個網段發的,就請以 10.168.0.3 做爲網關。
再來看一下這個數據包究竟是如何跑起來的?
假設容器 (10.244.0.2) 想要發一個包給 10.244.1.3,那麼它在本地產生了 TCP 或者 UDP 包以後,再依次填好對端 IP 地址、本地以太網的 MAC 地址做爲源 MAC 以及對端 MAC。通常來講本地會設定一條默認路由,默認路由會把 cni0 上的 IP 做爲它的默認網關,對端的 MAC 就是這個網關的 MAC 地址。而後這個包就能夠發到橋上去了。若是網段在本橋上,那麼經過 MAC 層的交換便可解決。
這個例子中咱們的 IP 並不屬於本網段,所以網橋會將其上送到主機的協議棧去處理。主機協議棧剛好找到了對端的 MAC 地址。使用 10.168.0.3 做爲它的網關,經過本地 ARP 探查後,咱們獲得了 10.168.0.3 的 MAC 地址。即經過協議棧層層組裝,咱們達到了目的,將 Dst-MAC 填爲右圖主機網卡的 MAC 地址,從而將包從主機的 eth0 發到對端的 eth0 上去。
因此你們能夠發現,這裏有一個隱含的限制,上圖中的 MAC 地址填好以後必定是能到達對端的,但若是這兩個宿主機之間不是二層鏈接的,中間通過了一些網關、一些複雜的路由,那麼這個 MAC 就不能直達,這種方案就是不能用的。當包到達了對端的 MAC 地址以後,發現這個包確實是給它的,可是 IP 又不是它本身的,就開始 Forward 流程,包上送到協議棧,以後再走一遍路由,恰好會發現 10.244.1.0/24 須要發到 10.244.1.1 這個網關上,從而到達了 cni0 網橋,它會找到 10.244.1.3 對應的 MAC 地址,再經過橋接機制,這個包就到達了對端容器。
你們能夠看到,整個過程老是二層、三層,發的時候又變成二層,再作路由,就是一個大環套小環。這是一個比較簡單的方案,若是中間要走隧道,則可能會有一條 vxlan tunnel 的設備,此時就不填直接的路由,而填成對端的隧道號。
Service 實際上是一種負載均衡 (Load Balance) 的機制。
咱們認爲它是一種用戶側(Client Side) 的負載均衡,也就是說 VIP 到 RIP 的轉換在用戶側就已經完成了,並不須要集中式地到達某一個 NGINX 或者是一個 ELB 這樣的組件來進行決策。
它的實現是這樣的:首先是由一羣 Pod 組成一組功能後端,再在前端上定義一個虛 IP 做爲訪問入口。通常來講,因爲 IP 不太好記,咱們還會附贈一個 DNS 的域名,Client 先訪問域名獲得虛 IP 以後再轉成實 IP。Kube-proxy 則是整個機制的實現核心,它隱藏了大量的複雜性。它的工做機制是經過 apiserver 監控 Pod/Service 的變化(好比是否是新增了 Service、Pod)並將其反饋到本地的規則或者是用戶態進程。
咱們來實際作一個 LVS 版的 Service。LVS 是一個專門用於負載均衡的內核機制。它工做在第四層,性能會比用 iptable 實現好一些。
假設咱們是一個 Kube-proxy,拿到了一個 Service 的配置,以下圖所示:它有一個 Cluster IP,在該 IP 上的端口是 9376,須要反饋到容器上的是 80 端口,還有三個可工做的 Pod,它們的 IP 分別是 10.1.2.3, 10.1.14.5, 10.1.3.8。
它要作的事情就是:
首先須要讓內核相信它擁有這樣的一個虛 IP,這是 LVS 的工做機制所決定的,由於它工做在第四層,並不關心 IP 轉發,只有它認爲這個 IP 是本身的纔會拆到 TCP 或 UDP 這一層。在第一步中,咱們將該 IP 設到內核中,告訴內核它確實有這麼一個 IP。實現的方法有不少,咱們這裏用的是 ip route 直接加 local 的方式,用 Dummy 啞設備上加 IP 的方式也是能夠的。
告訴它我須要爲這個 IP 進行負載均衡分發,後面的參數就是一些分發策略等等。virtual server 的 IP 其實就是咱們的 Cluster IP。
咱們須要爲 virtual server 配置相應的 real server,就是真正提供服務的後端是什麼。好比說咱們剛纔看到有三個 Pod,因而就把這三個的 IP 配到 virtual server 上,徹底一一對應過來就能夠了。Kube-proxy 工做跟這個也是相似的。只是它還須要去監控一些 Pod 的變化,好比 Pod 的數量變成 5 個了,那麼規則就應變成 5 條。若是這裏面某一個 Pod 死掉了或者被殺死了,那麼就要相應地減掉一條。又或者整個 Service 被撤銷了,那麼這些規則就要所有刪掉。因此它其實作的是一些管理層面的工做。
最後咱們介紹一下 Service 的類型,能夠分爲如下 4 類。
集羣內部的一個虛擬 IP,這個 IP 會綁定到一堆服務的 Group Pod 上面,這也是默認的服務方式。它的缺點是這種方式只能在 Node 內部也就是集羣內部使用。
供集羣外部調用。將 Service 承載在 Node 的靜態端口上,端口號和 Service 一一對應,那麼集羣外的用戶就能夠經過 <NodeIP>:<NodePort> 的方式調用到 Service。
給雲廠商的擴展接口。像阿里雲、亞馬遜這樣的雲廠商都是有成熟的 LB 機制的,這些機制多是由一個很大的集羣實現的,爲了避免浪費這種能力,雲廠商可經過這個接口進行擴展。它首先會自動建立 NodePort 和 ClusterIP 這兩種機制,雲廠商能夠選擇直接將 LB 掛到這兩種機制上,或者兩種都不用,直接把 Pod 的 RIP 掛到雲廠商的 ELB 的後端也是能夠的。
擯棄內部機制,依賴外部設施,好比某個用戶特別強,他以爲咱們提供的都沒什麼用,就是要本身實現,此時一個 Service 會和一個域名一一對應起來,整個負載均衡的工做都是外部實現的。
下圖是一個實例。它靈活地應用了 ClusterIP、NodePort 等多種服務方式,又結合了雲廠商的 ELB,變成了一個很靈活、極度伸縮、生產上真正可用的一套系統。
首先咱們用 ClusterIP 來作功能 Pod 的服務入口。你們能夠看到,若是有三種 Pod 的話,就有三個 Service Cluster IP 做爲它們的服務入口。這些方式都是 Client 端的,如何在 Server 端作一些控制呢?
首先會起一些 Ingress 的 Pod(Ingress 是 K8s 後來新增的一種服務,本質上仍是一堆同質的 Pod),而後將這些 Pod 組織起來,暴露到一個 NodePort 的 IP,K8s 的工做到此就結束了。
任何一個用戶訪問 23456 端口的 Pod 就會訪問到 Ingress 的服務,它的後面有一個 Controller,會把 Service IP 和 Ingress 的後端進行管理,最後會調到 ClusterIP,再調到咱們的功能 Pod。前面提到咱們去對接雲廠商的 ELB,咱們可讓 ELB 去監聽全部集羣節點上的 23456 端口,只要在 23456 端口上有服務的,就認爲有一個 Ingress 的實例在跑。
整個的流量通過外部域名的一個解析跟分流到達了雲廠商的 ELB,ELB 通過負載均衡並經過 NodePort 的方式到達 Ingress,Ingress 再經過 ClusterIP 調用到後臺真正的 Pod。這種系統看起來比較豐富,健壯性也比較好。任何一個環節都不存在單點的問題,任何一個環節也都有管理與反饋。
本文的主要內容就到此爲止了,這裏爲你們簡單總結一下:
「阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」