開發與維運

Kubernetes 容器網絡模型和典型實現

導讀:前文 Kubernetes 中的 ClusterIP、NodePort、LoadBalancer、Ingress 服務訪問方式比較中總結了服務接入訪問的主要方式,以及它們之間隱含關係。有了這些概念基礎後,K8s 應用開發和服務部署就容易很多了,但 Under the hood 服務訪問究竟是如何實現的呢?這篇內容就 Kubernetes 的網絡模型和典型的容器網絡實現,特別是阿里雲自己的容器網絡插件(Terway)的方案做了一個較詳細的總結。

Pod 之間 Container-to-Container networking

Linux networking namespace 為進程通訊提供了一個邏輯網絡棧,包括 network devices、routes、firewall rules。Network namespace(NS)管理實際是為其中的所有進程提供了一個獨立的邏輯網絡 Stack。

缺省情況下,Linux 將每個進程掛載在 Root NS 下,這些進程通過 eth0 通往外面的世界。

1.png

在 Pod 世界裡所有其中的容器共享一個 NS,這些容器都有相同的 IP 和 Port 空間,通過 localhost 訪問也是互通的。Shared storage 也是可以訪問的,通過 SharedVolume 掛載到容器中。如下一個 NS per pod 圖例:

2.png

同 Node 中 Pod-to-Pod networking

先看同一個 Node 下 Pod 之間的 networking 如何實現?答案是通過Virtual Ethernet Device (or veth pair)的兩塊 Virtual interfaces,每塊 veth 掛載在一個 NS 上,來實現跨 NS 的連接。比如,一塊掛在 Root NS(host)上,另一塊掛在 Pod NS 上,好比一根網線把兩個在不同網絡空間的 traffic 連接起來了,如圖:

3.png

有了veth pair這條網線,Pods 網絡可以連通到 Root NS 了,但在 Root NS 上如何實現對來自不同 Pod 的 packet 通訊呢?答案是通過Linux Ethernet Bridge,一個虛擬的 Layer2 網絡設備來實現不同 network segments 之間的 Ethernet packet switching。不得不提這個 old-school 協議:ARP,實現了 MAC 地址到 IP 地址的發現協議。Bridge 廣播 ethframe 到所有連接的設備(除發送者外),收到 ARP 回覆後將 packet forward 到對應 veth 設備上。如圖:

4.png

跨 Node 之間 Pod-to-Pod networking

進入這部分之前,先提及 K8s 在其(Pod)networking 設計上的 3 個 fundamental requirements,任何 networking 部分的實現都必須遵循這三個需求。

  • 在不使用 NAT 下,所有 Pods 都能和其它任何 Pods 通訊
  • 在不使用 NAT 下,所有 Nodes 都能和所有 Pods 通訊
  • Pod 所看到自己的 IP 和其它 Pods 看到它的 IP 一定是相同的

簡要來看,K8s 網絡模型要求 Pod IP 在整個網絡中都能通達。具體實現方案有三方面:

  • Layer2(Switching)Solution
  • Layer3(Routing)Solution,如,Calico, Terway
  • Overlay Solution,如Flannel

這部分下文介紹,目前且認為 Pod IP 的網絡通達性是確保的。

在 Pod 獲得 IP 之前,kubelet 為每個 Node 分配一個 CIDR 地址段(Classless inter-domain routing),每個 Pod 在其中獲取唯一 IP,CIDR 地址塊的大小對應於每個 Node 的最大 Pod 數量(默認 110 個)。在 Pod IP 和跨 Node 網絡層部署成功後,從源 Pod1 到目的 Pod4 的通訊如圖:

5.png

Pod-to-Service networking

K8s Service 管理服務的 Pods 狀態,在 Pod 有變化下管理對應 IP 的變化,並管理對外提供服務的 Virtual IP 到 Pod IPs 路由訪問,實現外部對服務 Virtual IP 的訪問路由到 Pod IP,以此屏蔽外部對服務後端的實現形態。所以在服務創建時,會對應生成一個 Virtual IP(也即是 Cluster IP),任何對該 Virtual IP 的訪問將打散路由到服務所屬的 Pods 上。

K8s 的服務是如何實現對 Virtual IP 的訪問負載均衡呢?答案是 netfilter 和 iptables。netfilters 是 Linux built-in networking framework,為 Linux 提供網絡包過濾、NAT 和 Port translation 等豐富的自定義 handler 實現。iptables 是運行在 Linux user-space 的規則管理系統,為 netfilter 框架提供豐富的包轉發規則管理。

在 K8s 實現中 kube-proxy(node deamon)通過 watch apiserver 來獲得服務配置的變化,比如,服務的 Virtual IP 變化、Pod IP 變化(ie, pod up/down)。iptables 規則隨之變化並將請求路由到服務對應的 Pod 上,Pod IP 選取是隨機的,這樣看 iptables 起到了 Pod 負載均衡作用。在訪問請求 Return path 上,iptables 會做一次 SNAT 以替換 IP header 的 Pod IP 為服務 Virtual IP,這樣使得 Client 看起來請求僅在服務 Virtual IP 上進行通訊。

從 K8S v1.11 中 IPVS(IP Virtual Server)被引入成為第二種集群內負載均衡方式。IPVS 同樣也是構建基於 netfilter 之上,在創建服務定義時可指定使用 iptables 或 IPVS。IPVS 是特定適合於服務負載均衡的解決方案,提供了非常豐富的均衡算法應用場景。

使用 DNS

每個服務會設置一個 DNS 域名,kubelets為每個容器進行配置--cluster-dns=<dns-service-ip>,用以解析服務所對應 DNS 域名到對應的 Cluster IP 或 Pod IP。1.12 後 CoreDNS 成為缺省 DNS 方式。服務支持 3 種類型 DNS records(A record、CNAME、SRV records)。其中常用的是 A Records,比如,在cluster.local的 DNS name 下,A record 格式如pod-ip-address.my-namespace.pod.cluster.local,其中 Pod hostname和subdomain 字段可以設置為標準的 FQDN 格式,比如,custom-host.custom-subdomain.my-namespace.svc.cluster.local

CNI

容器網絡模型在實現上是由 K8s 的節點 Pod 資源管控(kubelet)和遵從 Container Networking Interface(CNI)標準的插件共同協作完成的。CNI 插件程序在其中充當了"膠水"作用:各種容器網絡實現能在一致的操作接口下由 kubelet 統一管控調度。另外,多個容器網絡也能共存於一個集群內,為不同 Pod 的網絡需求提供服務,都是在 kubelet 的統一管控下完成。

Overlay networking: Flannel

Flannel 是 CoreOS 為 K8s networking 開發的解決方案,也是阿里雲 ACK 產品支持的容器網絡解決方案。Flannel 的設計原理很簡潔,在 host 網絡之上創建另一個扁平網絡(所謂的 overlay),在其上地址空間中給每個 pod 容器設置一個 IP 地址,並用此實現路由和通訊。

主機內容器網絡在 docker bridge docker0上完成通訊,不再贅述。主機間通訊使用內核路由表和 IP-over-UDP 封裝進行實現。容器 IP 包流經 docker bridge 會轉發到flannel0網卡(TUN)設備上,進而流入到flanneld進程中。flanneld會對 packet 目標 IP 地址所屬的網段信息查詢其對應的下一跳主機 IP,容器子網 CIDR 和所屬主機 IP 的映射(key-value)保存在 etcd 中,flanneld查詢得到 packet 目標 IP 所屬的主機 IP 地址後,會將 IP packet 封裝到一個 UDP payload 中並設置 UDP packet 目標地址為所得到的目標主機 IP,最後在 host 網絡中發送出 UDP packet。到達目標主機後,UDP packet 會流經flanneld並在這裡解封出 IP packet,再發送至flannel0docker0最後到達目標容器 IP 地址上。下圖示意流程:

6.png

值得一提是,容器 CIDR 和下一跳主機 IP 的映射條目容量沒有特殊限制。在阿里雲 ACK 產品上該條目容量需要在 VPC/vSwitch 控制面中進行分發,考慮到整體性能因素,在數量上做了一定數量限制(缺省 48 個)。但在自建主機網絡部署中,該數量限制就不會明顯了,因為主機下一跳主機網絡在一個大二層平面上。

Flannel 新版本 backend 不建議採用 UDP 封裝方式,因為 traffic 存在 3 次用戶空間與內核空間的數據拷貝,(如下圖)性能上存在比較大的損耗。新版本推薦用 VxLan 和雲服務商版本的 backends 進行優化。

7.png

L3 networking: Calico

Calico 是 L3 Routing 上非常流行容器網絡架構方案。主要組件是 Felix,BIRD 和 BGP Route Reflector。Felix 和 BIRD 均是運行在 Node 上的 deamon 程序。架構簡要:

8.png

Felix 完成網卡的管理和配置,包括 Routes programming 和 ACLs。實現路由信息對 Linux kernel FIB 的操作和 ACLs 的管理操作。由於 Felix 功能完整性和運行獨立性非常好,其功能作為 Off-the-shelf 被集成到阿里雲 Terway 網絡插件中,實現其網絡策略功能。

BIRD(BGP client)完成內核路由 FIB 條目向集群網絡側分發,使其路由條目對所有網絡節點中可見,並實現 BGP 路由協議功能。每一個 BGP client 會連接到網絡中其它 BGP client,這對規模較大的部署會是明顯的瓶頸(due to the N^2 increase nature)。鑑於該限制引入了 BGP Route Reflector 組件,實現 BGP clients 路由信息在匯聚層上再進行分發(propagation)。在集群網站中 Reflector 組件可以部署多個,完全能於部署規模大小來決定。Reflector 組件僅僅執行路由信令和條目的分發,其中不涉及任何數據面流量。路由匯聚層分發:

9.png

L3 networking:Terway

Terway 是阿里雲自研 CNI 插件,提供了阿里雲 VPC 互通和方便對接阿里雲產品的基礎設施,沒有 overlay 網絡帶來的性能損耗,同時提供了簡單易用的 backend 功能。

Terway 功能上可分為三部分:1. CNI 插件,一個獨立的 binary 運行程序;2. Backend Server(也稱為daemon),程序以獨立 daemonSet 方式運行在每個 Node 上;3. Network Policy,完全集成了 Calico Felix 實現。

CNI 插件 binary 是通過 daemonSet 部署中 initContainer 安裝到所有節點上,實現了ADDDELCHECK三個接口供 kubelet 調用。這裡以一個 Pod 在創建過程中的網絡 setup 步驟來說明:

  • 當一個 Pod 被調度到節點上時,kubelet 監聽到 Pod 創建在自己節點上,通過 runtime(docker...)創建 sandbox 容器來打通所需 namespace。
  • kubelet 調用插件 binary 的cmdAdd接口,插件程序對接口參數設置檢查後,向 backendServer 發起AllocIP調用。
  • backendServer 程序的networkService根據 Pod 的網絡類型進行相應的 Pod IP 申請,支持三種網絡類型ENIMultiIPVPCENIVPCIP

    • ENIMultiIP是 eni 網卡帶多 IP 類型,由networkService中的ResourceManager在自己的 IP 地址池中進行 IP 地址分配
    • VPCENI是為 Pod 創建和掛載一個 eni,由networkService中的allocateENI向阿里雲 Openapi 發起對所屬 ecs 實例的 eni 創建、掛載,並獲得對應 eni IP 地址
    • VPCIP是為 Pod 在 VPC 網絡平面上分配一個 IP 地址,這是在插件程序中通過調用ipam接口從 vpc 管控面獲取的 IP 地址
  • 在 backendServer 返回AllocIP調用(IP 地址)結果後,插件調用不同網絡類型下的NetnsDriver`Setup`接口實現來完成從容器網卡通往主機網卡的鏈路設置,其中:

    • ENIMultiIPVPCIP均是採用vethDriver的鏈路模式,步驟包括:

      • Create veth pair
      • Add IP addr for container interface
      • Add routes
      • Host side namespace config
      • Add host routes and rules
    • VPCENI稍有不同是為每個 Pod 在 VPC 平面上綁定一個 eni,其中包括兩次NetnsDriver接口調用:

      • vethDriver
      • rawNicDriver(主要實現 VPC 平面網絡路由設置,包括缺省路由和網關等配置)

綜上圖示:

10.png

為什麼需要支持上述三種網絡類型?根本上是由阿里雲 vpc 網絡基礎設施所決定,同時覆蓋阿里雲主流應用對 vpc 網絡資源的使用場景需求。另一方面是對標 Amazon AWS 的容器網絡解決方案,在基於 VPC 和 ENI 的網絡設施上能支持同等功能。

ENI 多 IP、VPC ENI 和 VPC IP 的主要區別在於前兩者下的 Pod 網段和 VPC 網段是相同的,而 VPC IP 的網段和節點的宿主機網段不同。這樣使得在 ENI 網絡環境下的 IP 路由完全在 VPC 的 L2 網絡平面上進行,而 VPC IP 網絡需要在 VPC 路由表中進行配置 Pod 網段的下一跳主機,和 Flannel 的路由模式類似。可以看出,ENI 網絡能帶來更靈活的路由選擇和更好的路由性能。如下兩個截圖反映其不同路由特點:

VPC ENI 網絡:

11.png

VPC IP 網絡:

12.png

ENI 多 IP(1 個主 IP/多個輔助 IP)網絡下有 2 種路由模式:veth策略路由ipvlan。兩者本質區別在於使用不同的路由模式,前者使用veth pair的策略路由,後者使用ipvlan網絡路由。策略路由需要在節點上配置策略路由條目來保證輔助 IP 的流量經過它所屬的彈性網卡。ipvlan實現了一個網卡虛擬出多個子網卡和不同的 IP 地址,eni 將其輔助 IP 綁定到這些虛擬出來的子網卡上形成一個與 vpc 平面打通的 L3 網絡。這種模式使 ENI 多 IP 的網絡結構比較簡單,性能相對veth策略路由網絡也更好。兩種網絡模式切換通過配置即可完成(缺省是vethpair):

值得一提的是 Terway 還實現了 ENI 多 IP 地址資源池的管理和分配機制。networkService中的eniIPFactory為每個 eni 網卡創建一個 goroutine,該 eni 網卡上的 eniIP 的分配釋放都在這個 goroutine 中完成。在創建一個 eniIP 時掃描已經存在的 eni 網卡,如該 eni 還存在空閒的 eniIP,該 goroutine 會通過ipResultChan返回給eniIPFactory一個分配的 IP。如果所有的 eni 網卡的 eniIP 都分配完畢,會先創建一個新的 eni 網卡和對應的 goroutine,首次創建 eni 網卡時無需做 IP 分配,直接返回 eni 網卡主 IP 即可。eniIP 釋放是逆向的,在 eni 網卡的最後一個 eniIP 釋放時,整個 eni 網卡資源會釋放掉。

另外,有一個startCheckIdleTickergoroutine 會定期掃描地址池的MaxPoolSizeMinPoolSize水位,在低於和高出水位閥值時會對地址池 eniIP 資源進行進行創建和釋放,使得地址池 IP 資源處於一個可控水位範圍中。為了保證資源狀態一致性,有一個startGarbageCollectionLoopgoroutine會定期掃描 IP 地址是否在用或過期狀態,如檢測到會進行資源 GC 操作。最後,Pod 資源狀態數據都持久化在本地的一個boltDB文件中/var/lib/cni/terway/pod.db,即使 Pod 已經在 apiServer 中刪除,GetPod會從本地boltDB中讀取副本數據。在 Pod 已經刪除但副本還存在 DB 的情況下,GC goroutine檢 測到會執行清理。截圖簡述:

13.png

總結下,從這些 backend 功能可以看到 Terway 為阿里雲 vpc 網絡資源設施連通性做了很好的封裝,讓基於阿里雲 Kubernetes 的應用開發和部署帶來更加簡便和高效的優點。

Leave a Reply

Your email address will not be published. Required fields are marked *