kubernetes CNI(Container Network Inferface)

daemon365發表於2024-04-20

為什麼需要 CNI

在 kubernetes 中,pod 的網路是使用 network namespace 隔離的,但是我們有時又需要互相訪問網路,這就需要一個網路外掛來實現 pod 之間的網路通訊。CNI 就是為了解決這個問題而誕生的。CNI 是 container network interface 的縮寫,它是一個規範,定義了容器執行時如何配置網路。CNI 外掛是實現了 CNI 規範的二進位制檔案,它可以被容器執行時呼叫,來配置容器的網路。

Docker 網路

基礎

計算機五層網路如下:

如果我們想把 pod 中的網路對外,首先想到的就是七層代理,比如nginx,但是我們並不知道 pod 裡的網路一定是 http,甚至他可能不是tcp。所以我們像做一些網路操作,就不能在五層做了,只能在二三四層做。

Docker 實驗

當我們在物理機上啟動 docker daemon 不需要啟動任何容器的時候,使用 ip a 命令檢視網路卡,發現多了一個 docker0

4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:9b:65:e1:01 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

docker0 是一個 linux Bridge 裝置,這個可以理解成一個虛擬的交換機,用來做二層網路的轉發。當我們啟動一個容器的時候,docker 會為這個容器建立一個 veth pair 裝置,一個埠掛載在容器的 network namespace 中,另一個埠掛載在 docker0 上。這樣容器就可以和 docker0 上的其他容器通訊了。

docker run -d --rm -it ubuntu:22.04 sleep 3000

在物理機上檢視 ip a

8: veth6bc75d9@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether d6:87:ca:5c:54:51 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::d487:caff:fe5c:5451/64 scope link
       valid_lft forever preferred_lft forever

docker 容器裡面 ip a

7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

再啟動一個 docker

docker run --name test -d --rm -it ubuntu:22.04 sleep 3000
# ip a
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

這樣兩個容器就可以透過 docker0 通訊了。

root@b19a3dc4b32d:/# ping  172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.055 ms

通訊方式

CNI 網路

當兩個 pod 在同一 node 上的時候,我們可以使用像上述 docker 的 bridge 的方式通訊是沒問題的。但是 kubernetes 是這個多節點的叢集,當 pod 在不同的 node 上的時候,直接通訊肯定不行了,這時候我們需要一些辦法來解決這個問題。

UDP 封包

當 pod 在不同節點上的時候,兩個 pod 不可以直接通訊,那最簡單的方式就是透過 udp 封包,把整個網路包使用 udp 封包起來,然後第二個節點再解包,然後發給網橋。

整個過程就是 node1 上的 pod 把網路包封裝,然後由於 process 再封裝發給 node2,node2 再解包,然後發給 pod2。

process 是 cni 實現的程序,很多 cni 都實現 udp 封包的方式,比如 flannel,cailco 等。

至於我們怎麼知道目標 ip (pod 的 ip) 是在哪臺主機上,這個就有很多中方式了,比如把每臺機器發配 ip 分配不同的網段,甚至於把這些對應關係寫到 etcd 中。

VXLAN

上述的 udp 封包方式,是可以滿足基本需求但是。cni 建立的 process 程序是一個使用者態的程序,每個包要在 node1 上從核心態 copy 到使用者態,然後再封包,再 copy 到核心態,再發給 node2,再從核心態 copy 到使用者態,再解包,再 copy 到核心態,再發給 pod2。這樣的方式效率很低。所以我們使用一種更加高效的方式,就是 vxlan。

VXLAN 是什麼?

VXLAN(Virtual Extensible LAN)是一種網路虛擬化技術,用於解決大規模雲端計算環境中的網路隔離、擴充套件性和靈活性問題。VXLAN 允許網路工程師在現有的網路架構上建立一個邏輯網路層,這可以使得資料中心的網路設計變得更加靈活和可擴充套件。

為什麼效能會高?

VXLAN 是在核心態實現的,原理和 udp 封包一樣,只不過是在核心態實現的,資料包不會在核心態和使用者態之間 copy,所以效率會高很多。

ip 路由

就算是 vxlan,也是需要封包和解包的,這樣的方式效率還是不夠高,所以我們可以使用 ip 路由的方式。

ip 路由故名思意,就是使用路由表來實現 pod 之間的通訊。這樣的方式效率最高,但是配置比較複雜,需要配置路由表。

而且路由表跳轉是二層網路實現的,所以又要要求所有 node 在同一個二層網路中。

檢視 node1 上的 container 的是裝置

ip a
2: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 66:5e:d8:8d:86:ba brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.10.184.69/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::645e:d8ff:fe8d:86ba/64 scope link
       valid_lft forever preferred_lft forever

這個和主機上是對應的是一個 veth pair 裝置,一個埠掛載在容器的 network namespace 中,一邊掛載在主機上。

# 主機
ip a
10: calia78b8700057@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-0da431c8-dd8b-ca68-55e6-40b04acf78d6
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

當 pod 中的資料包來到主機 檢視 node1 上的路由表 會命中一下這條路由 這條的意思是跳到192.168.229.102節點使用 ens33 裝置

ip r
172.10.190.0/26 via 192.168.229.102 dev ens33 proto bird

當 資料包來到 node2 上的時候 我們看下 node2 的路由表

ip r
172.10.190.2 dev calie28ee63d6b0 scope link
ip a
7: calie28ee63d6b0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-dd892c92-1826-f648-2b8c-d22618311ca9
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

這個裝置是 veth pair 裝置,對應的容器內的

ip a
2: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:a6:2f:97:58:28 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.10.190.2/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::f8a6:2fff:fe97:5828/64 scope link
       valid_lft forever preferred_lft forever

這樣node2上的 172.10.190.2 pod 就可以收到資料包了。

路由跳轉

路由跳轉是怎麼實現的?

路由跳轉是透過路由表來實現的,它作用在二層上,所以當跳轉的時候,直接修改資料包的目標 mac 地址(如果知道的話是使用 ARP 協議獲得)。

所以當我們訪問百度的時候,獲得百度的ip的時候,資料包會經過很多路由器,每個路由器都會修改資料包的目標 mac 地址,這樣資料包就可以到達百度的伺服器了。

Felix

那麼主機上的路由表是怎麼來的呢?

這個就是 cni 的實現了,cni 會呼叫 felix 這個程序,felix 會根據 cni 的配置來配置路由表。

BGP

那麼 node1 怎麼知道對應的 pod ip 在哪個 node 上呢?

這個就是 BGP 協議了,BGP 是一個路由協議,用來告訴 node1 對應的 pod ip 在哪個 node 上。

這個協議很重,之前都是用到網際網路上,比如我們剛才距離的百度的時候,經過那麼多路由器,每個路由器怎麼知道要跳到哪,他們之間就是透過 BGP 協議來告訴對方自己的路由表,再經過一系列的學習最佳化。

ip in ip

剛才也說過了,ip 路由是最高效的,是因為它作用在二層網路上,這就需要保證所有的 node 在同一個二層網路上。但是有時候我們的 node 不在同一個二層網路上,這時候我們可以使用 ip in ip。

簡單來說就是如果 node 之間在一個二層網路上,那麼就直接使用 ip 路由,如果不在,那麼就使用 ip in ip,把資料包封裝起來,然後再發給對應的 node。

相關文章