更新時間:2020-04-04 來源:黑馬程序員 瀏覽量:
容器網(wǎng)絡(luò)發(fā)端于 Docker 的網(wǎng)絡(luò)。Docker 使用了一個比較簡單的網(wǎng)絡(luò)模型,即內(nèi)部的網(wǎng)橋加內(nèi)部的保留 IP。這種設(shè)計的好處在于容器的網(wǎng)絡(luò)和外部世界是解耦的,無需占用宿主機的 IP 或者宿主機的資源,完全是虛擬的。
它的設(shè)計初衷是:當(dāng)需要訪問外部世界時,會采用 SNAT 這種方法來借用 Node 的 IP 去訪問外面的服務(wù)。比如容器需要對外提供服務(wù)的時候,所用的是 DNAT 技術(shù),也就是在 Node 上開一個端口,然后通過 iptable 或者別的某些機制,把流導(dǎo)入到容器的進程上以達到目的。
該模型的問題在于,外部網(wǎng)絡(luò)無法區(qū)分哪些是容器的網(wǎng)絡(luò)與流量、哪些是宿主機的網(wǎng)絡(luò)與流量。比如,如果要做一個高可用的時候,172.16.1.1 和 172.16.1.2 是擁有同樣功能的兩個容器,此時我們需要將兩者綁成一個 Group 對外提供服務(wù),而這個時候我們發(fā)現(xiàn)從外部看來兩者沒有相同之處,它們的 IP 都是借用宿主機的端口,因此很難將兩者歸攏到一起。推薦了解傳智播客linux云計算+運維開發(fā)課程。
在此基礎(chǔ)上,Kubernetes 提出了這樣一種機制:即每一個 Pod,也就是一個功能聚集小團伙應(yīng)有自己的“身份證”,或者說 ID。在 TCP 協(xié)議棧上,這個 ID 就是 IP。
這個 IP 是真正屬于該 Pod 的,外部世界不管通過什么方法一定要給它。對這個 Pod IP 的訪問就是真正對它的服務(wù)的訪問,中間拒絕任何的變造。比如以 10.1.1.1 的 IP 去訪問 10.1.2.1 的 Pod,結(jié)果到了 10.1.2.1 上發(fā)現(xiàn),它實際上借用的是宿主機的 IP,而不是源 IP,這樣是不被允許的。Pod 內(nèi)部會要求共享這個 IP,從而解決了一些功能內(nèi)聚的容器如何變成一個部署的原子的問題。
剩下的問題是我們的部署手段。Kubernetes 對怎么實現(xiàn)這個模型其實是沒有什么限制的,用 underlay 網(wǎng)絡(luò)來控制外部路由器進行導(dǎo)流是可以的;如果希望解耦,用 overlay 網(wǎng)絡(luò)在底層網(wǎng)絡(luò)之上再加一層疊加網(wǎng),這樣也是可以的。總之,只要達到模型所要求的目的即可。
Pod 究竟如何上網(wǎng)
容器網(wǎng)絡(luò)的網(wǎng)絡(luò)包究竟是怎么傳送的?
我們可以從以下兩個維度來看:
·協(xié)議層次
·網(wǎng)絡(luò)拓撲
1. 協(xié)議層次
它和 TCP 協(xié)議棧的概念是相同的,需要從兩層、三層、四層一層層地摞上去,發(fā)包的時候從右往左,即先有應(yīng)用數(shù)據(jù),然后發(fā)到了 TCP 或者 UDP 的四層協(xié)議,繼續(xù)向下傳送,加上 IP 頭,再加上 MAC 頭就可以送出去了。收包的時候則按照相反的順序,首先剝離 MAC 的頭,再剝離 IP 的頭,最后通過協(xié)議號在端口找到需要接收的進程。
2. 網(wǎng)絡(luò)拓撲
一個容器的包所要解決的問題分為兩步:第一步,如何從容器的空間 (c1) 跳到宿主機的空間 (infra);第二步,如何從宿主機空間到達遠端。
我個人的理解是,容器網(wǎng)絡(luò)的方案可以通過接入、流控、通道這三個層面來考慮。
·第一個是接入,就是說我們的容器和宿主機之間是使用哪一種機制做連接,比如 Veth + bridge、Veth + pair 這樣的經(jīng)典方式,也有利用高版本內(nèi)核的新機制等其他方式(如 mac/IPvlan 等),來把包送入到宿主機空間;
·第二個是流控,就是說我的這個方案要不要支持 Network Policy,如果支持的話又要用何種方式去實現(xiàn)。這里需要注意的是,我們的實現(xiàn)方式一定需要在數(shù)據(jù)路徑必經(jīng)的一個關(guān)節(jié)點上。如果數(shù)據(jù)路徑不通過該 Hook 點,那就不會起作用;
·第三個是通道,即兩個主機之間通過什么方式完成包的傳輸。我們有很多種方式,比如以路由的方式,具體又可分為 BGP 路由或者直接路由。還有各種各樣的隧道技術(shù)等等。最終我們實現(xiàn)的目的就是一個容器內(nèi)的包通過容器,經(jīng)過接入層傳到宿主機,再穿越宿主機的流控模塊(如果有)到達通道送到對端。
3. 一個最簡單的路由方案:Flannel-host-gw
這個方案采用的是每個 Node 獨占網(wǎng)段,每個 Subnet 會綁定在一個 Node 上,網(wǎng)關(guān)也設(shè)置在本地,或者說直接設(shè)在 cni0 這個網(wǎng)橋的內(nèi)部端口上。該方案的好處是管理簡單,壞處就是無法跨 Node 遷移 Pod。就是說這個 IP、網(wǎng)段已經(jīng)是屬于這個 Node 之后就無法遷移到別的 Node 上。
這個方案的精髓在于 route 表的設(shè)置,如上圖所示。接下來為大家一一解讀一下。
·第一條很簡單,我們在設(shè)置網(wǎng)卡的時候都會加上這一行。就是指定我的默認路由是通過哪個 IP 走掉,默認設(shè)備又是什么;
·第二條是對 Subnet 的一個規(guī)則反饋。就是說我的這個網(wǎng)段是 10.244.0.0,掩碼是 24 位,它的網(wǎng)關(guān)地址就在網(wǎng)橋上,也就是 10.244.0.1。這就是說這個網(wǎng)段的每一個包都發(fā)到這個網(wǎng)橋的 IP 上;
·第三條是對對端的一個反饋。如果你的網(wǎng)段是 10.244.1.0(上圖右邊的 Subnet),我們就把它的 Host 的網(wǎng)卡上的 IP (10.168.0.3) 作為網(wǎng)關(guān)。也就是說,如果數(shù)據(jù)包是往 10.244.1.0 這個網(wǎng)段發(fā)的,就請以 10.168.0.3 作為網(wǎng)關(guān)。
再來看一下這個數(shù)據(jù)包到底是如何跑起來的?
假設(shè)容器 (10.244.0.2) 想要發(fā)一個包給 10.244.1.3,那么它在本地產(chǎn)生了 TCP 或者 UDP 包之后,再依次填好對端 IP 地址、本地以太網(wǎng)的 MAC 地址作為源 MAC 以及對端 MAC。一般來說本地會設(shè)定一條默認路由,默認路由會把 cni0 上的 IP 作為它的默認網(wǎng)關(guān),對端的 MAC 就是這個網(wǎng)關(guān)的 MAC 地址。然后這個包就可以發(fā)到橋上去了。如果網(wǎng)段在本橋上,那么通過 MAC 層的交換即可解決。
這個例子中我們的 IP 并不屬于本網(wǎng)段,因此網(wǎng)橋會將其上送到主機的協(xié)議棧去處理。主機協(xié)議棧恰好找到了對端的 MAC 地址。使用 10.168.0.3 作為它的網(wǎng)關(guān),通過本地 ARP 探查后,我們得到了 10.168.0.3 的 MAC 地址。即通過協(xié)議棧層層組裝,我們達到了目的,將 Dst-MAC 填為右圖主機網(wǎng)卡的 MAC 地址,從而將包從主機的 eth0 發(fā)到對端的 eth0 上去。
所以大家可以發(fā)現(xiàn),這里有一個隱含的限制,上圖中的 MAC 地址填好之后一定是能到達對端的,但如果這兩個宿主機之間不是二層連接的,中間經(jīng)過了一些網(wǎng)關(guān)、一些復(fù)雜的路由,那么這個 MAC 就不能直達,這種方案就是不能用的。當(dāng)包到達了對端的 MAC 地址之后,發(fā)現(xiàn)這個包確實是給它的,但是 IP 又不是它自己的,就開始 Forward 流程,包上送到協(xié)議棧,之后再走一遍路由,剛好會發(fā)現(xiàn) 10.244.1.0/24 需要發(fā)到 10.244.1.1 這個網(wǎng)關(guān)上,從而到達了 cni0 網(wǎng)橋,它會找到 10.244.1.3 對應(yīng)的 MAC 地址,再通過橋接機制,這個包就到達了對端容器。
大家可以看到,整個過程總是二層、三層,發(fā)的時候又變成二層,再做路由,就是一個大環(huán)套小環(huán)。這是一個比較簡單的方案,如果中間要走隧道,則可能會有一條 vxlan tunnel 的設(shè)備,此時就不填直接的路由,而填成對端的隧道號。
Service 究竟如何工作
Service 其實是一種負載均衡 (Load Balance) 的機制。
我們認為它是一種用戶側(cè)(Client Side) 的負載均衡,也就是說 VIP 到 RIP 的轉(zhuǎn)換在用戶側(cè)就已經(jīng)完成了,并不需要集中式地到達某一個 NGINX 或者是一個 ELB 這樣的組件來進行決策。
它的實現(xiàn)是這樣的:首先是由一群 Pod 組成一組功能后端,再在前端上定義一個虛 IP 作為訪問入口。一般來說,由于 IP 不太好記,我們還會附贈一個 DNS 的域名,Client 先訪問域名得到虛 IP 之后再轉(zhuǎn)成實 IP。Kube-proxy 則是整個機制的實現(xiàn)核心,它隱藏了大量的復(fù)雜性。它的工作機制是通過 apiserver 監(jiān)控 Pod/Service 的變化(比如是不是新增了 Service、Pod)并將其反饋到本地的規(guī)則或者是用戶態(tài)進程。
一個 LVS 版的 Service
我們來實際做一個 LVS 版的 Service。LVS 是一個專門用于負載均衡的內(nèi)核機制。它工作在第四層,性能會比用 iptable 實現(xiàn)好一些。
假設(shè)我們是一個 Kube-proxy,拿到了一個 Service 的配置,如下圖所示:它有一個 Cluster IP,在該 IP 上的端口是 9376,需要反饋到容器上的是 80 端口,還有三個可工作的 Pod,它們的 IP 分別是 10.1.2.3, 10.1.14.5, 10.1.3.8。
它要做的事情就是:
第 1 步,綁定 VIP 到本地(欺騙內(nèi)核);
首先需要讓內(nèi)核相信它擁有這樣的一個虛 IP,這是 LVS 的工作機制所決定的,因為它工作在第四層,并不關(guān)心 IP 轉(zhuǎn)發(fā),只有它認為這個 IP 是自己的才會拆到 TCP 或 UDP 這一層。在第一步中,我們將該 IP 設(shè)到內(nèi)核中,告訴內(nèi)核它確實有這么一個 IP。實現(xiàn)的方法有很多,我們這里用的是 ip route 直接加 local 的方式,用 Dummy 啞設(shè)備上加 IP 的方式也是可以的。
第 2 步,為這個虛 IP 創(chuàng)建一個 IPVS 的 virtual server;
告訴它我需要為這個 IP 進行負載均衡分發(fā),后面的參數(shù)就是一些分發(fā)策略等等。virtual server 的 IP 其實就是我們的 Cluster IP。
第 3 步,為這個 IPVS service 創(chuàng)建相應(yīng)的 real server。
我們需要為 virtual server 配置相應(yīng)的 real server,就是真正提供服務(wù)的后端是什么。比如說我們剛才看到有三個 Pod,于是就把這三個的 IP 配到 virtual server 上,完全一一對應(yīng)過來就可以了。Kube-proxy 工作跟這個也是類似的。只是它還需要去監(jiān)控一些 Pod 的變化,比如 Pod 的數(shù)量變成 5 個了,那么規(guī)則就應(yīng)變成 5 條。如果這里面某一個 Pod 死掉了或者被殺死了,那么就要相應(yīng)地減掉一條。又或者整個 Service 被撤銷了,那么這些規(guī)則就要全部刪掉。所以它其實做的是一些管理層面的工作。
啥?負載均衡還分內(nèi)部外部
最后我們介紹一下 Service 的類型,可以分為以下 4 類。
1. ClusterIP
集群內(nèi)部的一個虛擬 IP,這個 IP 會綁定到一堆服務(wù)的 Group Pod 上面,這也是默認的服務(wù)方式。它的缺點是這種方式只能在 Node 內(nèi)部也就是集群內(nèi)部使用。
2. NodePort
供集群外部調(diào)用。將 Service 承載在 Node 的靜態(tài)端口上,端口號和 Service 一一對應(yīng),那么集群外的用戶就可以通過 : 的方式調(diào)用到 Service。
3. LoadBalancer
給云廠商的擴展接口。像阿里云、亞馬遜這樣的云廠商都是有成熟的 LB 機制的,這些機制可能是由一個很大的集群實現(xiàn)的,為了不浪費這種能力,云廠商可通過這個接口進行擴展。它首先會自動創(chuàng)建 NodePort 和 ClusterIP 這兩種機制,云廠商可以選擇直接將 LB 掛到這兩種機制上,或者兩種都不用,直接把 Pod 的 RIP 掛到云廠商的 ELB 的后端也是可以的。
4. ExternalName
擯棄內(nèi)部機制,依賴外部設(shè)施,比如某個用戶特別強,他覺得我們提供的都沒什么用,就是要自己實現(xiàn),此時一個 Service 會和一個域名一一對應(yīng)起來,整個負載均衡的工作都是外部實現(xiàn)的。
下圖是一個實例。它靈活地應(yīng)用了 ClusterIP、NodePort 等多種服務(wù)方式,又結(jié)合了云廠商的 ELB,變成了一個很靈活、極度伸縮、生產(chǎn)上真正可用的一套系統(tǒng)。
首先我們用 ClusterIP 來做功能 Pod 的服務(wù)入口。大家可以看到,如果有三種 Pod 的話,就有三個 Service Cluster IP 作為它們的服務(wù)入口。這些方式都是 Client 端的,如何在 Server 端做一些控制呢?
首先會起一些 Ingress 的 Pod(Ingress 是 K8s 后來新增的一種服務(wù),本質(zhì)上還是一堆同質(zhì)的 Pod),然后將這些 Pod 組織起來,暴露到一個 NodePort 的 IP,K8s 的工作到此就結(jié)束了。
任何一個用戶訪問 23456 端口的 Pod 就會訪問到 Ingress 的服務(wù),它的后面有一個 Controller,會把 Service IP 和 Ingress 的后端進行管理,最后會調(diào)到 ClusterIP,再調(diào)到我們的功能 Pod。前面提到我們?nèi)釉茝S商的 ELB,我們可以讓 ELB 去監(jiān)聽所有集群節(jié)點上的 23456 端口,只要在 23456 端口上有服務(wù)的,就認為有一個 Ingress 的實例在跑。
整個的流量經(jīng)過外部域名的一個解析跟分流到達了云廠商的 ELB,ELB 經(jīng)過負載均衡并通過 NodePort 的方式到達 Ingress,Ingress 再通過 ClusterIP 調(diào)用到后臺真正的 Pod。這種系統(tǒng)看起來比較豐富,健壯性也比較好。任何一個環(huán)節(jié)都不存在單點的問題,任何一個環(huán)節(jié)也都有管理與反饋。
本文總結(jié)
本節(jié)課的主要內(nèi)容就到此為止了,這里為大家簡單總結(jié)一下:
·大家要從根本上理解 Kubernetes 網(wǎng)絡(luò)模型的演化來歷,理解 PerPodPerIP 的用心在哪里;
·網(wǎng)絡(luò)的事情萬變不離其宗,按照模型從 4 層向下就是發(fā)包過程,反正層層剝離就是收包過程,容器網(wǎng)絡(luò)也是如此;
·Ingress 等機制是在更高的層次上(服務(wù)<->端口)方便大家部署集群對外服務(wù),通過一個真正可用的部署實例,希望大家把 Ingress+Cluster IP + PodIP 等概念聯(lián)合來看,理解社區(qū)出臺新機制、新資源對象的思考。
猜你喜歡: