所屬技術領域:
k8s
| 名詞定義 |
k8s對Pods之間如何進行組網通信提出了要求,k8s對叢集的網絡有以下要求:
• 所有的Pods之間可以在不使用NAT網絡位址轉換的情況下互相通信
• 所有的Nodes之間可以在不使用NAT網絡位址轉換的情況下互相通信
• 每個Pod自己看到的自己的ip和其他Pod看到的一緻
k8s網絡模型設計基礎原則:每個Pod都擁有一個獨立的 IP位址,而且 假定所有 Pod 都在一個可以直接連通的、扁平的網絡空間中 。 是以不管它們是否運作在同 一 個 Node (主控端)中,都要求它們可以直接通過對方的 IP 進行通路。設計這個原則的原因 是,使用者不需要額外考慮如何建立 Pod 之間的連接配接,也不需要考慮将容器端口映射到主機端口等問題。
由于 Kubemetes Kubernetes的網絡模型假設 Pod 之間通路時使用的是對方 Pod 的實際位址,是以一個
Pod 内部的應用程式看到的自己的 IP 位址和端口與叢集内其他 Pod 看到的一樣。它們都是 Pod 實際配置設定的IP位址 (從dockerO上配置設定的)。将IP位址和端口在Pod内部和外部都保持一緻, 我們可以不使用 NAT 來進行轉換,位址空間也自然是平的。
鑒于上面這些要求,我們需要解決四個不同的網絡問題::
• Docker容器和Docker容器之間的網絡
• Pod與Pod之間的網絡
• Pod與Service之間的網絡
• Internet與Service之間的網絡
| 發展曆程 |

| 技術特點 |
容器和容器之間的網絡
在k8s中每個Pod中管理着一組Docker容器,這些Docker容器共享同一個網絡命名空間。
Pod中的每個Docker容器擁有與Pod相同的IP和port位址空間,并且由于他們在同一個網絡命名空間,他們之間可以通過localhost互相通路。
什麼機制讓同一個Pod内的多個docker容器互相通信那?其實是使用Docker的一種網絡模型:–net=container
container模式指定新建立的Docker容器和已經存在的一個容器共享一個網絡命名空間,而不是和主控端共享。新建立的Docker容器不會建立自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、端口範圍等
每個Pod容器有有一個pause容器其有獨立的網絡命名空間,在Pod内啟動Docker容器時候使用 –net=container就可以讓目前Docker容器加入到Pod容器擁有的網絡命名空間(pause容器)
Pod與Pod之間的網絡
k8s中,每個Pod擁有一個ip位址,不同的Pod之間可以直接使用該改ip與彼此進行通訊
在同一個Node上,從Pod的視角看,它存在于自己的網絡命名空間中,并且需要與該Node上的其他網絡命名空間上的Pod進行通信。
那麼是如何做到的?這多虧了使用linux虛拟以太網裝置或者說是由兩個虛拟接口組成的veth對使不同的網絡命名空間連結起來,這些虛拟接口分布在多個網絡命名空間上(這裡是指多個Pod上)。
為了讓多個Pod的網絡命名空間連結起來,我們可以讓veth對的一端連結到root網絡命名空間(主控端的),另一端連結到Pod的網絡命名空間。
每對Veth就像一根接插電纜,連接配接兩側并允許流量在它們之間流動;這種veth對可以推廣到同一個Node上任意多的Pod上,如上圖這裡展示使用veth對連結每個Pod到虛拟機的root網絡命名空間。
下面我們看如何使用網橋裝置來讓通過veth對連結到root命名空間的多個Pod進行通信。
linux以太網橋(Linux Ethernet bridge)是一個虛拟的2層網絡裝置,目的是把多個以太網段連結起來,網橋維護了一個轉發表,通過檢查轉發表通過它傳輸的資料包的目的地并決定是否将資料包傳遞到連接配接到網橋的其他網段,網橋代碼通過檢視網絡中每個以太網裝置特有的MAC位址來決定是傳輸資料還是丢棄資料。
網橋實作了ARP協定用來根據給定的ip位址找到對應機器的資料鍊路層的mac位址,一開始轉發表為空,當一個資料幀被網橋接受後,網橋會廣播該幀到所有的連結裝置(除了發送方裝置),并且把響應這個廣播的裝置記錄到轉發表;随後發往相同ip位址的流量會直接從轉發表查找正确的mac位址,然後轉發包到對應的裝置。
如上圖顯示了兩個Pod通過veth對連結到root網絡命名空間,并且通過網橋進行通信
3.1 同一個Node中的Pod之間的一次通信
鑒于每個Pod有自己獨立的網絡命名空間,我們使用虛拟以太網裝置把多個Pod的命名空間連結到了root命名空間,并且使用網橋讓多個Pod之間進行通信,下面我們看如何在兩個pod之間進行通信:
通過網橋這裡把veth0和veth1組成為一個以太網,他們直接是可以直接通信的,另外這裡通過veth對讓pod1的eth0和veth0、pod2的eth0和veth1關聯起來,進而讓pod1和pod2互相通信。
Pod 1通過自己預設的以太網裝置eth0發送一個資料包,eth0把資料傳遞給veth0,資料包到達網橋後,網橋通過轉發表把資料傳遞給veth1,然後虛拟裝置veth1直接把包傳遞給Pod2網絡命名空間中的虛拟裝置eth0.
3.2 不同Node中的Pod之間通信
k8s網絡模型需要每個pod必須通過ip位址可以進行通路,每個pod的ip位址總是對網絡中的其他pod可見,并且每個pod看待自己的ip與别的pod看待的是一樣的(雖然他沒規定如何實作),下面我們看不同Node間Pod如何互動
k8s中每個叢集中的每個Node都會被配置設定了一個CIDR塊(無類别域間路由選擇,把網絡字首都相同的連續位址組成的位址組稱為CIDR位址塊)用來給該Node上的Pod配置設定IP位址。(保證pod的ip不會沖突)
另外還需要把pod的ip與所在的nodeip關聯起來()
如上圖Node1(vm1)上的Pod1與Node2(vm2)上Pod4之間進行互動。
首先pod1通過自己的以太網裝置eth0把資料包發送到關聯到root命名空間的veth0上,然後資料包被Node1上的網橋裝置cbr0接受到,網橋查找轉發表發現找不到pod4的Mac位址,則會把包轉發到預設路由(root命名空間的eth0裝置),然後資料包經過eth0就離開了Node1,被發送到網絡。
資料包到達Node2後,首先會被root命名空間的eth0裝置,然後通過網橋cbr0把資料路由到虛拟裝置veth1,最終資料表會被流轉到與veth1配對的另外一端(pod4的eth0)
每個Node都知道如何把資料包轉發到其内部運作的Pod,當一個資料包到達Node後,其内部資料流就和Node内Pod之間的流轉類似了。
對于如何來配置網絡,k8s在網絡這塊自身并沒有實作網絡規劃的具體邏輯,而是制定了一套CNI(Container Network Interface)接口規範,開放給社群來實作。
例如AWS,亞馬遜為k8s維護了一個容器網絡插件,使用CNI插件來讓亞馬遜VPC(虛拟私有雲)環境中的Node與Node直接進行互動.
CoreOS的Flannel是k8s中實作CNI規範較為出名的一種實作。
| 相關詞 |
Flannel網絡模型
簡單說flanneld是配置不同的node節點為vtep,然後利用vxlan封包進行跨叢集通信的。CNI插件,就是配置pod的網卡關聯到vxlan隧道上(學名 tunnel)。每個pod都有獨立的ip位址,就像我們有獨立的電話号碼一樣,隻要在這張網裡,那你就可以撥号(ping一下試試)和另外的pod通信。
Pause容器
如果不細心,可能不會發現每個pod都會建立一個pause容器,pause容器和網絡有什麼關系呢。
這個容器負責
在pod中擔任Linux命名空間共享的基礎;
啟用pid命名空間,開啟init程序。
我們都知道同一個pod裡面的容器共享命名空間,Linux 核心中就提供了這六種 namespace 隔離的系統調用,如下表所示。
pause容器就接管了pod裡面其他容器的網絡,同一個Pod裡的容器之間僅需通過localhost就能互相通信。。
VXLan
VXLAN 協定格式 VXLAN 全稱是 Virtual eXtensible Local Area Network,虛拟可擴充的區域網路。它是一種 overlay 技術,通過三層網絡來搭建虛拟二層網絡,其幀格式為:
從這個封包中可以看到 3 部分:
最外層的 UDP 協定封包用來在底層網絡上傳輸,也就是 vtep 之間互相通信的基礎;
中間是 VXLAN 頭部,vtep 接受到封包之後,去除前面的 UDP 協定部分。根據這部分來處理 VXLAN 的邏輯,主要是根據 VNI 發送到最終的虛拟機;
最裡面是原始的封包,也就是虛拟機看到的封包内容。
VTEP(VXLAN Tunnel Endpoints):VXLAN 網絡的邊緣裝置,用來進行 VXLAN 封包的處理(封包和解包)。vtep 可以是網絡裝置(比如交換機),也可以是一台機器(比如虛拟化叢集中的主控端);
VNI(VXLAN Network Identifier):VNI 是每個 VXLAN 的辨別,是個 24 位整數,一共有 2^24 = 16,777,216(一千多萬),一般每個 VNI 對應一個租戶,也就是說使用 VXLAN 搭建的公有雲,理論上可以支撐千萬級别的租戶;
Tunnel:隧道是一個邏輯上的概念,在 VXLAN 模型中并沒有具體的實體實體相對應。隧道可以看做是一種虛拟通道,VXLAN 通信雙方(圖中的虛拟機)認為自己是在直接通信,并不知道底層網絡的存在。從整體來說,每個 VXLAN 網絡像是為通信的虛拟機搭建了一個單獨的通信通道,也就是隧道。
Flannel
Flannel 是 CoreOS 團隊針對 Kubernetes 設計的一個網絡規劃實作。簡單來說,它的功能有以下幾點:
使叢集中的不同 Node 主機建立的 Docker 容器都具有全叢集唯一的虛拟 IP 位址;
建立一個覆寫網絡(overlay network),這個覆寫網絡會将資料包原封不動的傳遞到目标容器中。覆寫網絡是建立在另一個網絡之上并由其基礎設施支援的虛拟網絡。覆寫網絡通過将一個分組封裝在另一個分組内來将網絡服務與底層基礎設施分離。在将封裝的資料包轉發到端點後,将其解封裝;
建立一個新的虛拟網卡 flannel0 接收 docker 網橋的資料,通過維護路由表,對接收到的資料進行封包和轉發(VXLAN);
路由資訊一般存放到 etcd 中:多個 Node 上的 Flanneld 依賴一個 etcd cluster 來做集中配置服務,etcd 保證了所有 Node 上 Flannel 所看到的配置是一緻的。同時每個 Node 上的 Flannel 都可以監聽 etcd 上的資料變化,實時感覺叢集中 Node 的變化;
Flannel 首先會在 Node 上建立一個名為 flannel0 的網橋(VXLAN 類型的裝置),并且在每個 Node 上運作一個名為 Flanneld 的代理。每個 Node 上的 Flannel 代理會從 etcd 上為目前 Node 申請一個 CIDR 位址塊用來給該 Node 上的 Pod 配置設定位址;
Flannel 緻力于給 Kubernetes 叢集中的 Node 提供一個三層網絡,它并不控制 Node 中的容器是如何進行組網的,僅僅關心流量如何在 Node 之間流轉。
如上圖, IP 為 10.1.15.2 的 Pod1 與另外一個 Node 上 IP 為 10.1.20.3 的 Pod2 進行通信;
首先 Pod1 通過 veth 對把資料包發送到 docker0 虛拟網橋,網橋通過查找轉發表發現 10.1.20.3 不在自己管理的網段,就會把資料包轉發給預設路由(這裡為 flannel0 網橋);
flannel0 網橋是一個 VXLAN 裝置,flannel0 收到資料包後,由于自己不是目的 IP 位址 10.1.20.3,也要嘗試将資料包重新發送出去。資料包沿着網絡協定棧向下流動,在二層時需要封二層以太包,填寫目的 MAC 位址,這時一般應該發出 arp:”who is 10.1.20.3″。但 VXLAN 裝置的特殊性就在于它并沒有真正在二層發出這個 arp 包,而是由 linux kernel 引發一個”L3 MISS”事件并将 arp 請求發到使用者空間的 Flannel 程式中;
Flannel 程式收到”L3 MISS”核心事件以及 arp 請求 (who is 10.1.20.3) 後,并不會向外網發送 arp request,而是嘗試從 etcd 查找該位址比對的子網的 vtep 資訊,也就是會找到 Node2 上的 flannel0 的 MAC 位址資訊。Flannel 将查詢到的資訊放入 Node1 host 的 arp cache 表中,flannel0 完成這項工作後,Linux kernel 就可以在 arp table 中找到 10.1.20.3 對應的 MAC 位址并封裝二層以太包了: <缺圖>
由于是 Vlanx 裝置,flannel0 還會對上面的包進行二次封裝,封裝新的以太網 MAC 幀:
Node 上 2 的 eth0 接收到上述 VXLAN 包,kernel 将識别出這是一個 VXLAN 包,于是拆包後将 packet 轉給 Node 上 2 的 flannel0。flannel0 再将這個資料包轉到 docker0,繼而由 docker0 傳輸到 Pod2 的某個容器裡。
如上圖,總的來說就是建立 VXLAN 隧道,通過 UDP 把 IP 封裝一層直接送到對應的節點,實作了一個大的 VLAN。
适用場景:
- 同一Pod内的網絡通信。在同一個Pod内的容器共享同一個網絡命名空間,共享同一個Linux協定棧。是以對于網絡的各類操作,就和它們在同一台機器上一樣,它們可以用localhost位址直接通路彼此的端口。其實這和傳統的一組普通程式運作的環境是完全一樣的,傳統的程式不需要針對網絡做特别的修改就可以移植了。這樣做的結果是簡單、安全和高效,也能減少将已經存在的程式從實體機或者虛拟機移植到容器下運作的難度。
-
Pod1到Pod2的網絡,分兩種情況。Pod1與Pod2不在同一台主機與Pod1與Pod2在同一台主機。
• 先說Pod1與Pod2不在同一台主機。Pod的位址是與docker0在同一個網段的,但docker0網段與主控端網卡是兩個完全不同的IP網段,并且不同Node之間的通信隻能通過主控端的實體網卡進行。将Pod的IP和所在Node的IP關聯起來,通過這個關聯讓Pod可以互相通路。
• Pod1與Pod2在同一台主機。Pod1和Pod2在同一台主機的話,由Docker0網橋直接轉發請求到Pod2,不需要經過Flannel。
- Pod到Service的網絡。建立一個Service時,相應會建立一個指向這個Service的域名,域名規則為{服務名}.{namespace}.svc.{叢集名稱}。之前Service IP的轉發由iptables和kube-proxy負責,目前基于性能考慮,全部為iptables維護和轉發。iptables則由kubelet維護。Service僅支援UDP和TCP協定,是以像ping的ICMP協定是用不了的,是以無法ping通Service IP。
- Pod到外網。Pod向外網發送請求,查找路由表, 轉發資料包到主控端的網卡,宿主網卡完成路由選擇後,iptables執行Masquerade,把源IP更改為宿主網卡的IP,然後向外網伺服器發送請求。
-
叢集外部通路Pod或Service
由于Pod和Service是Kubernetes叢集範圍内的虛拟概念,是以叢集外的用戶端系統無法通過Pod的IP位址或者Service的虛拟IP位址和虛拟端口号通路到它們。為了讓外部用戶端可以通路這些服務,可以将Pod或Service的端口号映射到主控端,以使得用戶端應用能夠通過實體機通路容器應用。
總結:Flannel實作了對Kubernetes網絡的支援,但是它引入了多個網絡元件,在網絡通信時需要轉到flannel0網絡接口,再轉到使用者态的flanneld程式,到對端後還需要走這個過程的反過程,是以也會引入一些網絡的時延損耗。另外Flannel預設的底層通信協定是UDP。UDP本身是非可靠協定,雖然兩端的TCP實作了可靠傳輸,但在大流量、高并發應用場景下還需要反複調試,確定不會出現傳輸品質的問題。特别是對網絡依賴重的應用,需要評估對業務的影響。
資料來源:
延伸閱讀:
k8s網絡模型