k8s網絡概述


前言

在介紹k8s的網絡通信機制前,先介紹一下docker的網絡通信機制,不同節點pod間網絡通信,在docker里是這樣訪問的

即: Container1 snat-> 節點A (docker0網橋) -> 節點B(docker0網橋) ->dnat -> pod2

這樣的訪問方式會產生很大的系統開銷,因為需要產生兩級NAT轉換,container1訪問出去時需要經過eth0的SNAT,報文達到對端節點的eth0后,做一次DNAT,當Container2發送響應報文時,又需要本節點物理網卡做一次SNAT,對端節點物理網卡做一次DNAT。

同樣還會產生一個問題,Container1始終不知道它真正和誰在通信,它明明是訪問Container2的地址,當目標地址指向的確是對端節點的物理IP。

k8s網絡通信

下面再看看k8s是怎么解決網絡通信的。

k8s要解決的網絡通信模型有以下幾種場景:

  • 容器間通信,同一個pod內多個容器的通信
  • pod之間通信,又分為同節點pod之間的通信,和不同節點pod之間的通信
  • pod與Service通信
  • service與集群外部進行通信

(2)pod之間通信,k8s要求pod和pod通信時,從一個pod IP到另一個pod IP不需要經過任何的NAT轉換,要能夠直達 ,雙方所見的地址就是通信時的地址。

這里又分為同節點pod之間的通信,和不同節點pod之間的通信。

(3)pod與Service通信 podIP <--> clusterIP

(4)service與集群外部進行通信

Pod內部通信

pod內部給容器之間共享網絡命名空間,就好比一台機器上有多個進程,他們之間通信可以通過lo口來實現。

同節點Pod之間通信

同一個節點內Pod之間是通過cni0網橋直接進行通信,和docker的通信方式一樣。

不同節點Pod之間通信

Flannel網絡插件

這里先要介紹下CNI,k8s自身沒有提供網絡解決方案,但它定義了一種容器網絡接口,任何第三方的程序只要能解決上面這4種通信模型,那它就可以拿來做k8s的網絡接口。有點類似golang里接口的概念。

目前比較常用的網絡插件有flannel和calico,flannel的功能比較簡單,不具備復雜網絡的配置能力,calico是比較出色的網絡管理插件,單具備復雜網絡配置能力的同時,往往意味着本身的配置比較復雜,所以相對而言,比較小而簡單的集群使用flannel,考慮到日后擴容,未來網絡可能需要加入更多設備,配置更多策略,則使用calico更好。

kubelet會調用/etc/cni/net.d目錄下的網絡插件配置,由網絡插件實現地址分配、接口創建、網絡創建等功能 。

flannel有三種模式:

  • host gw

    把節點作為網關,那每個節點的pod網絡就應該都不一樣,A節點的pod要訪問B節點的pod時,先會發到本地網關,本地網關存有所有pod網絡的路由信息,會匹配到B節點的pod網絡並把數據發給B節點的網關,B節點網關也會檢查本地路由信息,發現是發給本地pod的請求就會把請求發給本地pod。
    此種方式需要各節點在同一個網絡中,數據包直接以路由形式發給對方而不要像vxlan一樣進行數據包的封裝,這種方式有缺點,節點數量很多時,每個節點維護的路由信息會很大,而且由於節點在同一個網絡,容易受到廣播包的影響。

  • vxlan

    當節點在同一個網絡中時,會自動降級為host gw模式通信,其中vxlan又分為兩種模式:

    1、vxlan(默認)

    2、directrouting,適用於宿主機同網段,不同網段時自動降級為vxlan

    參見kube-flannel的配置:

    kubectl edit  configmap kube-flannel-cfg -n kube-system
    

  • UDP: 這種方式性能最差的方式,這源於早期flannel剛出現時,Linux內核還不支持VxLAN,即沒有VxLAN核心模塊,因此flannel采用了這種方式,來實現隧道封裝。

這里只介紹Vxlan的實現方式

在k8s中,網橋變為cni0,Pod的數據包出去時,先經過cni0,再到flannel.1接口時會將數據包封裝成vxlan協議的數據包發給對端,對端flannel.1解包后發給本地Pod,最終的效果就是pod間通信就像在二層網絡一樣,容器直接可以直接使用pod ip進行通訊。

在跨節點的pod通信時,在物理網卡上抓包時可以看到OTV類型的包的,表示的是overlay數據包。
在同節點的pod通信時,不會產生overlay數據包。

flannel.1網卡:
pod跨節點通信時,物理網卡要做成疊加網絡的形式時,兩個物理網卡之間應各自有個疊加報文封裝的隧道,隧道的兩端通常稱為flannel.0/1,其地址很獨特,ip地址為10.244.0.0(舉例),掩碼為255.255.255.255, 而且它的mtu是1450,不像物理網卡一樣是1500,因為隧道要做疊加封裝,要有額外的開銷,所以要留出50給這個開銷。如下圖所示:

Pod網卡:

生成Pod時,會像docker一樣創建一對虛擬接口/網卡,也就是veth pair,一端連接到容器中,另一端橋接到vethxxxx網卡並橋接到cni0網橋,如下圖所示:

可以看到宿主機上的vethxxxx網卡都是橋接在cni0網卡上的。

通過ethtool -S (網卡名),可以看到peer的index,一對peer的index是相連的

測試

先在兩個node節點上分別創建一個PodA。PodB,模擬不同不同節點不同網絡的測試環境,然后在PodA上一直ping PodB的地址,然后在PodA所在的節點上抓包分析。

先抓取flannel.1網卡上的包:

可以看到兩個Pod是直接通過podIP訪問的。

再抓取物理網卡上的包:

tcpdump -i ens32 -nn host (PodB所在節點物理網卡IP)

可以看到兩個物理網卡之間通信有vxlan的包,並且包里可以看到是兩個pod之間的icmp包

再查看主機路由:

可以看到Pod跨節點訪問時的默認網關是fannel.1

如果改成directrouting模式,再次從物理網卡上抓包,可以看到pod之間不會走vxlan進行包封裝了,直接通過物理網卡通信

查看宿主機路由

Pod與Service通信

Pod與Service通信是通過Keube-Proxy模塊來實現的,它會監聽service的變化並管理sevice的Endpoints,該service對外暴露一個Virtual IP,也成為Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。

Pod和Service是兩種不同的網絡,那么怎么實現Service和Pod的互通呢?kube-proxy提供了三種模式,iptables、ipvs、userspace,這里我們只介紹iptable的實現。

因為service地址就是iptables里的一個虛擬IP,而iptables在每個節點上都會存在,所以外部請求轉發到任意一個物理節點,都能通過iptables規則把請求轉發到后端Endpoints。

Service與集群外部進行通信

可以通過nodePort獲取ingress將服務暴露出去。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM