Kubernetes 網絡架構及相關網卡


前言

因為 Kubernetes 的網絡可以使用第三方網絡插件,所以給我們提供了多樣化的網絡解決方案,讓我們可以根據自身情況選擇自己需要的網絡方案。

CNM & CNI 陣營:

  • 容器網絡發展到現在,形成了兩大陣營,就是 Docker 的 CNM 和 Google、CoreOS、Kuberenetes 主導的 CNI。首先明確一點,CNM 和 CNI 並不是網絡實現,他們是網絡規范和網絡體系,從研發的角度他們就是一堆接口,你底層是用 Flannel 也好、用 Calico 也好,他們並不關心,CNM 和 CNI 關心的是網絡管理的問題。

  • CNM (Docker LibnetworkContainer Network Model)

  • CNI(Container Network Interface)

Kubernetes 網絡設計模型:

  • 在 Kubernetes 網絡中存在兩種 IP(Pod IP 和 Service Cluster IP),Pod IP 地址是實際存在於某個網卡(可以是虛擬設備)上的,Service Cluster IP 它是一個虛擬 IP,是由 kube-proxy 使用 Iptables 規則重新定向到其本地端口,再均衡到后端 Pod 的。

基本原則:

  • 每個 Pod 都擁有一個獨立的 IP 地址(IPper Pod),而且假定所有的 pod 都在一個可以直接連通的、扁平的網絡空間中。

設計原因:

  • 用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器端口映射到主機端口等問題。

網絡要求:

  • 所有的容器都可以在不用 NAT 的方式下同別的容器通訊;所有節點都可在不用 NAT 的方式下同所有容器通訊;容器的地址和別人看到的地址是同一個地址。

K8S 網絡主要解決以下網絡通信問題:

  • 同一 pod 下容器與容器的通信;

  • 同一節點下不同的 pod 之間的容器間通信;

  • 不同節點下容器之間的通信;

  • 集群外部與內部組件的通信;

  • pod 與 service 之間的通信;

1、容器間通信:

同一個 Pod 的容器共享同一個網絡命名空間,它們之間的訪問可以用

localhost 地址 + 容器端口就可以訪問。

2、同一 Node 中 Pod 間通信:

同一 Node 中 Pod 的默認路由都是 docker0 的地址,由於它們關聯在同一個 docker0(cni0)網橋上,地址網段相同,所有它們之間應當是能直接通信的。

3、不同 Node 中 Pod 間通信:

不同 Node 中 Pod 間通信要滿足 2 個條件:

*Pod 的 IP 不能沖突* 將 Pod 的 IP 和所在的 Node 的 IP 關聯起來,通過這個關聯讓 Pod 可以互相訪問。所以就要用到 flannel 或者 calico 的網絡解決方案。

對於此場景,情況現對比較復雜一些,這就需要解決 Pod 間的通信問題。在 Kubernetes 通過 flannel、calico 等網絡插件解決 Pod 間的通信問題。以 flannel 為例說明在 Kubernetes 中網絡模型,flannel 是 kubernetes 默認提供網絡插件。Flannel 是由 CoreOs 團隊開發社交的網絡工具,CoreOS 團隊采用 L3 Overlay 模式設計 flannel, 規定宿主機下各個 Pod 屬於同一個子網,不同宿主機下的 Pod 屬於不同的子網。

查看宿主機網卡

1 packet dropped by kernel
[root@node12 /]# ifconfig
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.3.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::1c39:e0ff:fefd:bc01  prefixlen 64  scopeid 0x20<link>
        ether 1e:39:e0:fd:bc:01  txqueuelen 1000  (Ethernet)
        RX packets 215461844  bytes 49255741900 (45.8 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 210219198  bytes 99380196541 (92.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:3a:fe:ce:d7  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 18.16.200.142  netmask 255.255.255.0  broadcast 18.16.200.255
        inet6 fe80::4e56:dbf1:fdb1:bbd9  prefixlen 64  scopeid 0x20<link>
        ether 52:54:00:fb:f2:44  txqueuelen 1000  (Ethernet)
        RX packets 271704820  bytes 128411272052 (119.5 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 234410728  bytes 75423059029 (70.2 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.3.0  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::8087:1ff:fe29:ed7c  prefixlen 64  scopeid 0x20<link>
        ether 82:87:01:29:ed:7c  txqueuelen 0  (Ethernet)
        RX packets 52059332  bytes 8640899916 (8.0 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 38918760  bytes 6548881752 (6.0 GiB)
        TX errors 0  dropped 20 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1  (Local Loopback)
        RX packets 23487  bytes 1343648 (1.2 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 23487  bytes 1343648 (1.2 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth0b52cfb8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet6 fe80::bca4:22ff:fea7:349  prefixlen 64  scopeid 0x20<link>
        ether be:a4:22:a7:03:49  txqueuelen 0  (Ethernet)
        RX packets 5345585  bytes 2672632806 (2.4 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5451377  bytes 3371977575 (3.1 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth3867ca45: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet6 fe80::7035:a5ff:fecd:e4d0  prefixlen 64  scopeid 0x20<link>
        ether 72:35:a5:cd:e4:d0  txqueuelen 0  (Ethernet)
        RX packets 4210336  bytes 3083434674 (2.8 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4364899  bytes 5908811645 (5.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0網卡

Docker 安裝時會自動在 host 上創建三個網絡:none,host,和bridge;詳細說明可參考其它文檔。

我們可用 docker network ls 命令查看:

[root@node12 /]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
0ee6eb650f66        bridge              bridge              local
30a3e66f751c        host                host                local
2d1ae28eb78b        none                null                local

基於DRIVER是bridge的網絡都會有一個對應的linux bridge被創建:

在默認環境中,一個名為docker0的linux bridge自動被創建好了,其上有一個 docker0 內部接口,IP地址為172.17.0.1/16(掩碼為255.255.0.0)

再用docker network inspect指令查看bridge網絡:其Gateway就是網卡/接口docker0的IP地址:172.17.0.1。

[root@node12 /]# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "0ee6eb650f66797ca4dde49db3985a4006affdf69f2e208791e11dd3f977a9ab",
        "Created": "2019-12-27T09:25:56.921594934+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {},
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

這時就會出現如何識別docker0的虛擬網卡和容器的對應關系,例如,圖示中有兩個容器和docker0中的兩個接口:

安裝Linux網橋管理工具bridge-utils

[root@node12 /]# yum install bridge-utils

[root@node12 /]# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0         8000.3a1d7362b4ee       no              veth65f9
                                             vethdda6
                                             vetha596

flannel網絡

flannel簡介

Flannel是CoreOS團隊針對Kubernetes設計的一個網絡規划服務,簡單來說,它的功能是讓集群中的不同節點主機創建的Docker容器都具有全集群唯一的虛擬IP地址。

在默認的Docker配置中,每個節點上的Docker服務會分別負責所在節點容器的IP分配。這樣導致的一個問題是,不同節點上容器可能獲得相同的內外IP地址。並使這些容器之間能夠之間通過IP地址相互找到,也就是相互ping通。

Flannel的設計目的就是為集群中的所有節點重新規划IP地址的使用規則,從而使得不同節點上的容器能夠獲得“同屬一個內網”且”不重復的”IP地址,並讓屬於不同節點上的容器能夠直接通過內網IP通信。

Flannel實質上是一種“覆蓋網絡(overlaynetwork)”,也就是將TCP數據包裝在另一種網絡包里面進行路由轉發和通信,目前已經支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等數據轉發方式,默認的節點間數據通信方式是UDP轉發。

flannel網絡

flannel 的 IP 地址是通過 Etcd 管理的,在 k8s 初始化的時候指定 pod 大網的網段 --pod-network-cidr=10.244.0.0/16,flanneld 可以直接通過 Etcd 管理,如果啟動的時候指定了 --kube-subnet-mgr,可以直接通過 k8s 的 apiserver 來獲得一個小網段的租期,通過 kubectl get -o jsonpath='{.spec.podCIDR}' 可以獲取對應節點的 CIDR 表示的網段,flannel 是以節點為單元划分小網段的,每個節點上的 pod 在這個例子當中是划分一個 10.244.x.0/24 的網段,所以總共能分配 255 個節點,每個節點上可以分配 253 個 pod。結構如下圖所示,每個節點上都會有一個 flanneld 用於管理自己網段的租期。

img

可以通過在 host 上 cat /run/flannel/subnet.env 查看同步下來的信息,例如:

[root@node12 /]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.3.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

說明當前節點分配的網段是 10.244.0.1/24。在每個節點上因為已經確定了網段,用 ipam 就可以管理這一范圍 ip 地址的分配,所以本身 pod 的 IP 分配和中心 Etcd 沒有太多聯系。

容器間跨宿主如何運行

如下圖所示,集群范圍內的網絡地址空間為10.1.0.0/16,Machine A獲取的subnet為10.1.15.0/24,且其中的兩個容器IP分別為10.1.15.2/24和10.1.15.3/24,兩者都在10.1.15.0/24這一子網范圍內,對於下方的Machine B同理。

如果上方Machine A中IP地址為10.1.15.2/24的容器要與下方Machine B中IP地址為10.1.16.2/24的容器進行通信,封包是如何進行轉發的。從上文可知,每個主機的flanneld會將自己與所獲取subnet的關聯信息存入etcd中,例如,subnet 10.1.15.0/24所在主機可通過IP 192.168.0.100訪問,subnet 10.1.16.0/24可通過IP 192.168.0.200訪問。反之,每台主機上的flanneld通過監聽etcd,也能夠知道其他的subnet與哪些主機相關聯。如上圖,Machine A上的flanneld通過監聽etcd已經知道subnet 10.1.16.0/24所在的主機可以通過Public 192.168.0.200訪問,而且熟悉docker橋接模式的同學肯定知道,目的地址為10.1.16.2/24的封包一旦到達Machine B,就能通過cni0網橋轉發到相應的pod,從而達到跨宿主機通信的目的。

因此,flanneld只要想辦法將封包從Machine A轉發到Machine B就OK了,而上文中的backend就是用於完成這一任務。不過,達到這個目的的方法是多種多樣的,所以我們也就有了很多種backend. 在這里我們舉例介紹的是最簡單的一種方式hostgw : 因為Machine A和Machine B處於同一個子網內,它們原本就能直接互相訪問。因此最簡單的方法是:在Machine A中的容器要訪問Machine B的容器時,我們可以將Machine B看成是網關,當有封包的目的地址在subnet 10.1.16.0/24范圍內時,就將其直接轉發至B即可。而這通過圖中那條紅色標記的路由就能完成,對於Machine B同理可得。由此,在滿足仍有subnet可以分配的條件下,我們可以將上述方法擴展到任意數目位於同一子網內的主機。而任意主機如果想要訪問主機X中subnet為S的容器,只要在本主機上添加一條目的地址為R,網關為X的路由即可。

veth網卡

Linux container 中用到一個叫做veth的東西,這是一種新的設備,專門為 container 所建。veth 從名字上來看是 Virtual ETHernet 的縮寫,它的作用很簡單,就是要把從一個 network namespace 發出的數據包轉發到另一個 namespace。veth 設備是成對的,一個是 container 之中,另一個在 container 之外,即在真實機器上能看到的。
VETH設備總是成對出現,送到一端請求發送的數據總是從另一端以請求接受的形式出現。創建並配置正確后,向其一端輸入數據,VETH會改變數據的方向並將其送入內核網絡子系統,完成數據的注入,而在另一端則能讀到此數據。(Namespace,其中往veth設備上任意一端上RX到的數據,都會在另一端上以TX的方式發送出去)veth工作在L2數據鏈路層,veth-pair設備在轉發數據包過程中並不串改數據包內容。

veth設備特點

  • veth和其它的網絡設備都一樣,一端連接的是內核協議棧
  • veth設備是成對出現的,另一端兩個設備彼此相連
  • 一個設備收到協議棧的數據發送請求后,會將數據發送到另一個設備上去

cni0網卡

查看cni0網卡對應的橋接網卡

[root@node12 /]# brctl show
bridge name	bridge id		STP enabled	interfaces
cni0		8000.1e39e0fdbc01	no		veth07cd1879
							veth0b52cfb8
							veth4238d279
							veth6389b6d5
							veth724d6510
							veth78b32a65
							veth96aa8f2a
							vethad79f5c0
							vethb4ae9fae
							vethcaba2513
							vethd9bb4726

參考:

flannel 網絡架構

Linux-虛擬網絡設備-veth pair

flanneld,flannel和cni逐步深入

Kubernetes 網絡 -flannel 網絡插件詳解

Kubernetes中的網絡解析——以flannel為例

容器虛擬網卡與網橋docker0虛擬網卡的veth pair的配對

技術干貨|深入理解flannel

k8s網絡之Flannel網絡


免責聲明!

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



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