3. 覆寫網絡 - Overlay Network
覆寫網絡(overlay network)是将TCP資料包裝在另一種網絡包裡面進行路由轉發和通信的技術。Overlay網絡不是預設必須的,但是它們在特定場景下非常有用。比如當我們沒有足夠的IP空間,或者網絡無法處理額外路由,抑或當我們需要Overlay提供的某些額外管理特性。一個常見的場景是當雲提供商的路由表能處理的路由數是有限制時,例如AWS路由表最多支援50條路由才不至于影響網絡性能。是以如果我們有超過50個Kubernetes節點,AWS路由表将不夠。這種情況下,使用Overlay網絡将幫到我們。
本質上來說,Overlay就是在跨節點的本地網絡上的包中再封裝一層包。你可能不想使用Overlay網絡,因為它會帶來由封裝和解封所有封包引起的時延和複雜度開銷。通常這是不必要的,是以我們應當在知道為什麼我們需要它時才使用它。
為了了解Overlay網絡中流量的流向,我們拿Flannel做例子,它是CoreOS 的一個開源項目。Flannel通過給每台主控端配置設定一個子網的方式為容器提供虛拟網絡,它基于Linux TUN/TAP,使用UDP封裝IP包來建立overlay網絡,并借助etcd維護網絡的配置設定情況。

Kubernetes Node with route table
(通過Flannel Overlay網絡進行跨節點的Pod-to-Pod通信)
這裡我們注意到它和之前我們看到的設施是一樣的,隻是在root netns中新增了一個虛拟的以太網裝置,稱為flannel0。它是虛拟擴充網絡Virtual Extensible LAN(VXLAN)的一種實作,但是在Linux上,它隻是另一個網絡接口。
從pod1到pod4(在不同節點)的資料包的流向類似如下:
1.它由pod1中netns的eth0網口離開,通過vethxxx進入root netns。
2.然後被傳到cbr0,cbr0通過發送ARP請求來找到目标位址。
3.資料封裝
*a. 由于本節點上沒有Pod擁有pod4的IP位址,是以網橋把資料包發送給了flannel0,因為節點的路由表上flannel0被配成了Pod網段的目标位址。
*b. flanneld daemon和Kubernetes apiserver或者底層的etcd通信,它知道所有的Pod IP,并且知道它們在哪個節點上。是以Flannel建立了Pod IP和Node IP之間的映射(在使用者空間)。flannel0取到這個包,并在其上再用一個UDP包封裝起來,該UDP標頭部的源和目的IP分别被改成了對應節點的IP,然後發送這個新包到特定的VXLAN端口(通常是8472)。
Packet-in-packet encapsulation
(notice the packet is encapsulated from 3c to 6b in previous diagram)
盡管這個映射發生在使用者空間,真實的封裝以及資料的流動發生在核心空間,是以仍然是很快的
*c. 封裝後的包通過eth0發送出去,因為它涉及了節點間的路由流量。
4.包帶着節點IP資訊作為源和目的位址離開本節點。
5.雲提供商的路由表已經知道了如何在節點間發送封包,是以該封包被發送到目标位址node2。
6.資料解包
*a. 包到達node2的eth0網卡,由于目标端口是特定的VXLAN端口,核心将封包發送給了 flannel0。
*b. flannel0解封封包,并将其發送到 root 命名空間下。從這裡開始,封包的路徑和我們之前在Part 1 中看到的非Overlay網絡就是一緻的了。
*c. 由于IP forwarding開啟着,核心按照路由表将封包轉發給了cbr0。
7.網橋擷取到了包,發送ARP請求,發現目标IP屬于vethyyy。
8.包跨過管道對到達pod4
這就是Kubernetes中Overlay網絡的工作方式,雖然不同的實作還是會有細微的差别。有個常見的誤區是,當我們使用Kubernetes,我們就不得不使用Overlay網絡。事實是,這完全依賴于特定場景。是以請確定在确實需要的場景下才使用。
4. 動态叢集
由于Kubernetes(更通用的說法是分布式系統)天生具有不斷變化的特性,是以它的Pod(以及Pod的IP)總是在改變。變化的原因可以是針對不可預測的Pod或節點崩潰而進行的滾動更新和擴充。這使得Pod IP不能直接用于通信。
我們看一下Kubernetes Service,它是一個虛拟IP,并伴随着一組Pod IP作為Endpoint(通過标簽選擇器識别)。它們充當虛拟負載均衡器,其IP保持不變,而後端Pod IP可能會不斷變化。
Kubernetes service對象中的label選擇器
整個虛拟IP的實作實際上是一組iptables(最新版本可以選擇使用IPVS,但這是另一個讨論)規則,由Kubernetes元件kube-proxy管理。 這個名字現在實際上是誤導性的。 它在v 1.0之前确實是一個代理,并且由于其實作是核心空間和使用者空間之間的不斷複制,它變得非常耗費資源并且速度較慢。 現在,它隻是一個控制器,就像Kubernetes中的許多其它控制器一樣,它watch api server的endpoint的更改并相應地更新iptables規則。
Iptables DNAT
有了這些iptables規則,每當資料包發往Service IP時,它就進行DNAT(DNAT=目标網絡位址轉換)操作,這意味着目标IP從Service IP更改為其中一個Endpoint - Pod IP - 由iptables随機選擇。這可確定負載均勻分布在後端Pod中。
conntrack表中的5元組條目
當這個DNAT發生時,這個資訊存儲在conntrack中——Linux連接配接跟蹤表(iptables規則5元組轉譯并完成存儲:protocol,srcIP,srcPort,dstIP,dstPort)。 這樣當請求回來時,它可以un-DNAT,這意味着将源IP從Pod IP更改為Service IP。 這樣,用戶端就不用關心背景如何處理資料包流。
是以通過使用Kubernetes Service,我們可以使用相同的端口而不會發生任何沖突(因為我們可以将端口重新映射到Endpoint)。 這使服務發現變得非常容易。 我們可以使用内部DNS并對服務主機名進行寫死。 我們甚至可以使用Kubernetes提供的service主機和端口的環境變量來完成服務發現。
專家建議: 采取第二種方法,你可節省不必要的DNS調用,但是由于環境變量存在建立順序的局限性(環境變量中不包含後來建立的服務),推薦使用DNS來進行服務名解析。
4.1 出站流量
到目前為止我們讨論的Kubernetes Service是在一個叢集内工作。但是,在大多數實際情況中,應用程式需要通路一些外部api/website。
通常,節點可以同時具有私有IP和公共IP。對于網際網路通路,這些公共和私有IP存在某種1:1的NAT,特别是在雲環境中。
對于從節點到某些外部IP的普通通信,源IP從節點的專用IP更改為其出站資料包的公共IP,入站的響應資料包則剛好相反。但是,當Pod發出與外部IP的連接配接時,源IP是Pod IP,雲提供商的NAT機制不知道該IP。是以它将丢棄具有除節點IP之外的源IP的資料包。
是以你可能也猜對了,我們将使用更多的iptables!這些規則也由kube-proxy添加,執行SNAT(源網絡位址轉換),即IP MASQUERADE(IP僞裝)。它告訴核心使用此資料包發出的網絡接口的IP,代替源Pod IP同時保留conntrack條目以進行反SNAT操作。
4.2 入站流量
到目前為止一切都很好。Pod可以互相交談,也可以通路網際網路。但我們仍然缺少關鍵部分 - 為使用者請求流量提供服務。截至目前,有兩種主要方法可以做到這一點:
* NodePort /雲負載均衡器(L4 - IP和端口)
将服務類型設定為NodePort預設會為服務配置設定範圍為30000-33000的nodePort。即使在特定節點上沒有運作Pod,此nodePort也會在每個節點上打開。此NodePort上的入站流量将再次使用iptables發送到其中一個Pod(該Pod甚至可能在其它節點上!)。
雲環境中的LoadBalancer服務類型将在所有節點之前建立雲負載均衡器(例如ELB),命中相同的nodePort。
* Ingress(L7 - HTTP / TCP)
許多不同的工具,如Nginx,Traefik,HAProxy等,保留了http主機名/路徑和各自後端的映射。通常這是基于負載均衡器和nodePort的流量入口點,其優點是我們可以有一個入口處理所有服務的入站流量,而不需要多個nodePort和負載均衡器。
4.3 網絡政策
可以把它想象為Pod的安全組/ ACL。 NetworkPolicy規則允許/拒絕跨Pod的流量。确切的實作取決于網絡層/CNI,但大多數隻使用iptables。
結束語
目前為止就這樣了。 在前面的部分中,我們研究了Kubernetes網絡的基礎以及overlay網絡的工作原理。 現在我們知道Service抽象是如何在一個動态叢集内起作用并使服務發現變得非常容易。我們還介紹了出站和入站流量的工作原理以及網絡政策如何對叢集内的安全性起作用。