Kubernetes 容器网络模型和典型实现
**導讀:**前文 Kubernetes 中的 ClusterIP、NodePort、LoadBalancer、Ingress 服務訪問方式比較中總結(jié)了服務接入訪問的主要方式,以及它們之間隱含關系。有了這些概念基礎后,K8s 應用開發(fā)和服務部署就容易很多了,但 Under the hood 服務訪問究竟是如何實現(xiàn)的呢?這篇內(nèi)容就 Kubernetes 的網(wǎng)絡模型和典型的容器網(wǎng)絡實現(xiàn),特別是阿里云自己的容器網(wǎng)絡插件(Terway)的方案做了一個較詳細的總結(jié)。
Pod 之間 Container-to-Container networking
Linux networking namespace 為進程通訊提供了一個邏輯網(wǎng)絡棧,包括 network devices、routes、firewall rules。Network namespace(NS)管理實際是為其中的所有進程提供了一個獨立的邏輯網(wǎng)絡 Stack。
缺省情況下,Linux 將每個進程掛載在 Root NS 下,這些進程通過 eth0 通往外面的世界。
在 Pod 世界里所有其中的容器共享一個 NS,這些容器都有相同的 IP 和 Port 空間,通過 localhost 訪問也是互通的。Shared storage 也是可以訪問的,通過 SharedVolume 掛載到容器中。如下一個 NS per pod 圖例:
同 Node 中 Pod-to-Pod networking
先看同一個 Node 下 Pod 之間的 networking 如何實現(xiàn)?答案是通過Virtual Ethernet Device (or veth pair)的兩塊 Virtual interfaces,每塊 veth 掛載在一個 NS 上,來實現(xiàn)跨 NS 的連接。比如,一塊掛在 Root NS(host)上,另一塊掛在 Pod NS 上,好比一根網(wǎng)線把兩個在不同網(wǎng)絡空間的 traffic 連接起來了,如圖:
有了veth pair這條網(wǎng)線,Pods 網(wǎng)絡可以連通到 Root NS 了,但在 Root NS 上如何實現(xiàn)對來自不同 Pod 的 packet 通訊呢?答案是通過Linux Ethernet Bridge,一個虛擬的 Layer2 網(wǎng)絡設備來實現(xiàn)不同 network segments 之間的 Ethernet packet switching。不得不提這個 old-school 協(xié)議:ARP,實現(xiàn)了 MAC 地址到 IP 地址的發(fā)現(xiàn)協(xié)議。Bridge 廣播 ethframe 到所有連接的設備(除發(fā)送者外),收到 ARP 回復后將 packet forward 到對應 veth 設備上。如圖:
跨 Node 之間 Pod-to-Pod networking
進入這部分之前,先提及 K8s 在其(Pod)networking 設計上的 3 個 fundamental requirements,任何 networking 部分的實現(xiàn)都必須遵循這三個需求。
- 在不使用 NAT 下,所有 Pods 都能和其它任何 Pods 通訊
- 在不使用 NAT 下,所有 Nodes 都能和所有 Pods 通訊
- Pod 所看到自己的 IP 和其它 Pods 看到它的 IP 一定是相同的
簡要來看,K8s 網(wǎng)絡模型要求 Pod IP 在整個網(wǎng)絡中都能通達。具體實現(xiàn)方案有三方面:
- Layer2(Switching)Solution
- Layer3(Routing)Solution,如,Calico, Terway
- Overlay Solution,如Flannel
這部分下文介紹,目前且認為 Pod IP 的網(wǎng)絡通達性是確保的。
在 Pod 獲得 IP 之前,kubelet 為每個 Node 分配一個 CIDR 地址段(Classless inter-domain routing),每個 Pod 在其中獲取唯一 IP,CIDR 地址塊的大小對應于每個 Node 的最大 Pod 數(shù)量(默認 110 個)。在 Pod IP 和跨 Node 網(wǎng)絡層部署成功后,從源 Pod1 到目的 Pod4 的通訊如圖:
Pod-to-Service networking
K8s Service 管理服務的 Pods 狀態(tài),在 Pod 有變化下管理對應 IP 的變化,并管理對外提供服務的 Virtual IP 到 Pod IPs 路由訪問,實現(xiàn)外部對服務 Virtual IP 的訪問路由到 Pod IP,以此屏蔽外部對服務后端的實現(xiàn)形態(tài)。所以在服務創(chuàng)建時,會對應生成一個 Virtual IP(也即是 Cluster IP),任何對該 Virtual IP 的訪問將打散路由到服務所屬的 Pods 上。
K8s 的服務是如何實現(xiàn)對 Virtual IP 的訪問負載均衡呢?答案是 netfilter 和 iptables。netfilters 是 Linux built-in networking framework,為 Linux 提供網(wǎng)絡包過濾、NAT 和 Port translation 等豐富的自定義 handler 實現(xiàn)。iptables 是運行在 Linux user-space 的規(guī)則管理系統(tǒng),為 netfilter 框架提供豐富的包轉(zhuǎn)發(fā)規(guī)則管理。
在 K8s 實現(xiàn)中 kube-proxy(node deamon)通過 watch apiserver 來獲得服務配置的變化,比如,服務的 Virtual IP 變化、Pod IP 變化(ie, pod up/down)。iptables 規(guī)則隨之變化并將請求路由到服務對應的 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)被引入成為第二種集群內(nèi)負載均衡方式。IPVS 同樣也是構(gòu)建基于 netfilter 之上,在創(chuàng)建服務定義時可指定使用 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
容器網(wǎng)絡模型在實現(xiàn)上是由 K8s 的節(jié)點 Pod 資源管控(kubelet)和遵從 Container Networking Interface(CNI)標準的插件共同協(xié)作完成的。CNI 插件程序在其中充當了"膠水"作用:各種容器網(wǎng)絡實現(xiàn)能在一致的操作接口下由 kubelet 統(tǒng)一管控調(diào)度。另外,多個容器網(wǎng)絡也能共存于一個集群內(nèi),為不同 Pod 的網(wǎng)絡需求提供服務,都是在 kubelet 的統(tǒng)一管控下完成。
Overlay networking: Flannel
Flannel 是 CoreOS 為 K8s networking 開發(fā)的解決方案,也是阿里云 ACK 產(chǎn)品支持的容器網(wǎng)絡解決方案。Flannel 的設計原理很簡潔,在 host 網(wǎng)絡之上創(chuàng)建另一個扁平網(wǎng)絡(所謂的 overlay),在其上地址空間中給每個 pod 容器設置一個 IP 地址,并用此實現(xiàn)路由和通訊。
主機內(nèi)容器網(wǎng)絡在 docker bridge docker0上完成通訊,不再贅述。主機間通訊使用內(nèi)核路由表和 IP-over-UDP 封裝進行實現(xiàn)。容器 IP 包流經(jīng) docker bridge 會轉(zhuǎn)發(fā)到flannel0網(wǎng)卡(TUN)設備上,進而流入到flanneld進程中。flanneld會對 packet 目標 IP 地址所屬的網(wǎng)段信息查詢其對應的下一跳主機 IP,容器子網(wǎng) CIDR 和所屬主機 IP 的映射(key-value)保存在 etcd 中,flanneld查詢得到 packet 目標 IP 所屬的主機 IP 地址后,會將 IP packet 封裝到一個 UDP payload 中并設置 UDP packet 目標地址為所得到的目標主機 IP,最后在 host 網(wǎng)絡中發(fā)送出 UDP packet。到達目標主機后,UDP packet 會流經(jīng)flanneld并在這里解封出 IP packet,再發(fā)送至flannel0、docker0最后到達目標容器 IP 地址上。下圖示意流程:
值得一提是,容器 CIDR 和下一跳主機 IP 的映射條目容量沒有特殊限制。在阿里云 ACK 產(chǎn)品上該條目容量需要在 VPC/vSwitch 控制面中進行分發(fā),考慮到整體性能因素,在數(shù)量上做了一定數(shù)量限制(缺省 48 個)。但在自建主機網(wǎng)絡部署中,該數(shù)量限制就不會明顯了,因為主機下一跳主機網(wǎng)絡在一個大二層平面上。
Flannel 新版本 backend 不建議采用 UDP 封裝方式,因為 traffic 存在 3 次用戶空間與內(nèi)核空間的數(shù)據(jù)拷貝,(如下圖)性能上存在比較大的損耗。新版本推薦用 VxLan 和云服務商版本的 backends 進行優(yōu)化。
L3 networking: Calico
Calico 是 L3 Routing 上非常流行容器網(wǎng)絡架構(gòu)方案。主要組件是 Felix,BIRD 和 BGP Route Reflector。Felix 和 BIRD 均是運行在 Node 上的 deamon 程序。架構(gòu)簡要:
Felix 完成網(wǎng)卡的管理和配置,包括 Routes programming 和 ACLs。實現(xiàn)路由信息對 Linux kernel FIB 的操作和 ACLs 的管理操作。由于 Felix 功能完整性和運行獨立性非常好,其功能作為 Off-the-shelf 被集成到阿里云 Terway 網(wǎng)絡插件中,實現(xiàn)其網(wǎng)絡策略功能。
BIRD(BGP client)完成內(nèi)核路由 FIB 條目向集群網(wǎng)絡側(cè)分發(fā),使其路由條目對所有網(wǎng)絡節(jié)點中可見,并實現(xiàn) BGP 路由協(xié)議功能。每一個 BGP client 會連接到網(wǎng)絡中其它 BGP client,這對規(guī)模較大的部署會是明顯的瓶頸(due to the N^2 increase nature)。鑒于該限制引入了 BGP Route Reflector 組件,實現(xiàn) BGP clients 路由信息在匯聚層上再進行分發(fā)(propagation)。在集群網(wǎng)站中 Reflector 組件可以部署多個,完全能于部署規(guī)模大小來決定。Reflector 組件僅僅執(zhí)行路由信令和條目的分發(fā),其中不涉及任何數(shù)據(jù)面流量。路由匯聚層分發(fā):
L3 networking:Terway
Terway 是阿里云自研 CNI 插件,提供了阿里云 VPC 互通和方便對接阿里云產(chǎn)品的基礎設施,沒有 overlay 網(wǎng)絡帶來的性能損耗,同時提供了簡單易用的 backend 功能。
Terway 功能上可分為三部分:1. CNI 插件,一個獨立的 binary 運行程序;2. Backend Server(也稱為daemon),程序以獨立 daemonSet 方式運行在每個 Node 上;3. Network Policy,完全集成了 Calico Felix 實現(xiàn)。
CNI 插件 binary 是通過 daemonSet 部署中 initContainer 安裝到所有節(jié)點上,實現(xiàn)了ADD、DEL、CHECK三個接口供 kubelet 調(diào)用。這里以一個 Pod 在創(chuàng)建過程中的網(wǎng)絡 setup 步驟來說明:
- 當一個 Pod 被調(diào)度到節(jié)點上時,kubelet 監(jiān)聽到 Pod 創(chuàng)建在自己節(jié)點上,通過 runtime(docker…)創(chuàng)建 sandbox 容器來打通所需 namespace。
- kubelet 調(diào)用插件 binary 的cmdAdd接口,插件程序?qū)涌趨?shù)設置檢查后,向 backendServer 發(fā)起AllocIP調(diào)用。
- backendServer 程序的networkService根據(jù) Pod 的網(wǎng)絡類型進行相應的 Pod IP 申請,支持三種網(wǎng)絡類型ENIMultiIP、VPCENI、VPCIP:
- ENIMultiIP是 eni 網(wǎng)卡帶多 IP 類型,由networkService中的ResourceManager在自己的 IP 地址池中進行 IP 地址分配
- VPCENI是為 Pod 創(chuàng)建和掛載一個 eni,由networkService中的allocateENI向阿里云 Openapi 發(fā)起對所屬 ecs 實例的 eni 創(chuàng)建、掛載,并獲得對應 eni IP 地址
- VPCIP是為 Pod 在 VPC 網(wǎng)絡平面上分配一個 IP 地址,這是在插件程序中通過調(diào)用ipam接口從 vpc 管控面獲取的 IP 地址
- 在 backendServer 返回AllocIP調(diào)用(IP 地址)結(jié)果后,插件調(diào)用不同網(wǎng)絡類型下的NetnsDriver``Setup接口實現(xiàn)來完成從容器網(wǎng)卡通往主機網(wǎng)卡的鏈路設置,其中:
- ENIMultiIP和VPCIP均是采用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接口調(diào)用:
- vethDriver
- rawNicDriver(主要實現(xiàn) VPC 平面網(wǎng)絡路由設置,包括缺省路由和網(wǎng)關等配置)
- ENIMultiIP和VPCIP均是采用vethDriver的鏈路模式,步驟包括:
綜上圖示:
為什么需要支持上述三種網(wǎng)絡類型?根本上是由阿里云 vpc 網(wǎng)絡基礎設施所決定,同時覆蓋阿里云主流應用對 vpc 網(wǎng)絡資源的使用場景需求。另一方面是對標 Amazon AWS 的容器網(wǎng)絡解決方案,在基于 VPC 和 ENI 的網(wǎng)絡設施上能支持同等功能。
ENI 多 IP、VPC ENI 和 VPC IP 的主要區(qū)別在于前兩者下的 Pod 網(wǎng)段和 VPC 網(wǎng)段是相同的,而 VPC IP 的網(wǎng)段和節(jié)點的宿主機網(wǎng)段不同。這樣使得在 ENI 網(wǎng)絡環(huán)境下的 IP 路由完全在 VPC 的 L2 網(wǎng)絡平面上進行,而 VPC IP 網(wǎng)絡需要在 VPC 路由表中進行配置 Pod 網(wǎng)段的下一跳主機,和 Flannel 的路由模式類似??梢钥闯?#xff0c;ENI 網(wǎng)絡能帶來更靈活的路由選擇和更好的路由性能。如下兩個截圖反映其不同路由特點:
VPC ENI 網(wǎng)絡:
VPC IP 網(wǎng)絡:
ENI 多 IP(1 個主 IP/多個輔助 IP)網(wǎng)絡下有 2 種路由模式:veth策略路由和ipvlan。兩者本質(zhì)區(qū)別在于使用不同的路由模式,前者使用veth pair的策略路由,后者使用ipvlan網(wǎng)絡路由。策略路由需要在節(jié)點上配置策略路由條目來保證輔助 IP 的流量經(jīng)過它所屬的彈性網(wǎng)卡。ipvlan實現(xiàn)了一個網(wǎng)卡虛擬出多個子網(wǎng)卡和不同的 IP 地址,eni 將其輔助 IP 綁定到這些虛擬出來的子網(wǎng)卡上形成一個與 vpc 平面打通的 L3 網(wǎng)絡。這種模式使 ENI 多 IP 的網(wǎng)絡結(jié)構(gòu)比較簡單,性能相對veth策略路由網(wǎng)絡也更好。兩種網(wǎng)絡模式切換通過配置即可完成(缺省是vethpair):
值得一提的是 Terway 還實現(xiàn)了 ENI 多 IP 地址資源池的管理和分配機制。networkService中的eniIPFactory為每個 eni 網(wǎng)卡創(chuàng)建一個 goroutine,該 eni 網(wǎng)卡上的 eniIP 的分配釋放都在這個 goroutine 中完成。在創(chuàng)建一個 eniIP 時掃描已經(jīng)存在的 eni 網(wǎng)卡,如該 eni 還存在空閑的 eniIP,該 goroutine 會通過ipResultChan返回給eniIPFactory一個分配的 IP。如果所有的 eni 網(wǎng)卡的 eniIP 都分配完畢,會先創(chuàng)建一個新的 eni 網(wǎng)卡和對應的 goroutine,首次創(chuàng)建 eni 網(wǎng)卡時無需做 IP 分配,直接返回 eni 網(wǎng)卡主 IP 即可。eniIP 釋放是逆向的,在 eni 網(wǎng)卡的最后一個 eniIP 釋放時,整個 eni 網(wǎng)卡資源會釋放掉。
另外,有一個startCheckIdleTickergoroutine 會定期掃描地址池的MaxPoolSize和MinPoolSize水位,在低于和高出水位閥值時會對地址池 eniIP 資源進行進行創(chuàng)建和釋放,使得地址池 IP 資源處于一個可控水位范圍中。為了保證資源狀態(tài)一致性,有一個startGarbageCollectionLoopgoroutine會定期掃描 IP 地址是否在用或過期狀態(tài),如檢測到會進行資源 GC 操作。最后,Pod 資源狀態(tài)數(shù)據(jù)都持久化在本地的一個boltDB文件中/var/lib/cni/terway/pod.db,即使 Pod 已經(jīng)在 apiServer 中刪除,GetPod會從本地boltDB中讀取副本數(shù)據(jù)。在 Pod 已經(jīng)刪除但副本還存在 DB 的情況下,GC goroutine檢 測到會執(zhí)行清理。截圖簡述:
總結(jié)下,從這些 backend 功能可以看到 Terway 為阿里云 vpc 網(wǎng)絡資源設施連通性做了很好的封裝,讓基于阿里云 Kubernetes 的應用開發(fā)和部署帶來更加簡便和高效的優(yōu)點。
總結(jié)
以上是生活随笔為你收集整理的Kubernetes 容器网络模型和典型实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Service Mesh 在超大规模场景
- 下一篇: Sentinel-Go 集成 Nacos