Calico 網路通訊原理揭祕

米開朗基楊發表於2019-07-30

Calico 是一個純三層的資料中心網路方案,而且無縫整合像 OpenStack 這種 Iaas 雲架構,能夠提供可控的 VM、容器、裸機之間的 IP 通訊。為什麼說它是純三層呢?因為所有的資料包都是通過路由的形式找到對應的主機和容器的,然後通過 BGP 協議來將所有路由同步到所有的機器或資料中心,從而完成整個網路的互聯。

簡單來說,Calico 在主機上建立了一堆的 veth pair,其中一端在主機上,另一端在容器的網路名稱空間裡,然後在容器和主機中分別設定幾條路由,來完成網路的互聯。

1. Calico 網路模型揭祕

下面我們通過具體的例子來幫助大家理解 Calico 網路的通訊原理。任意選擇 k8s 叢集中的一個節點作為實驗節點,進入容器 A,檢視容器 A 的 IP 地址:

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if771: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1440 qdisc noqueue state UP
    link/ether 66:fb:34:db:c9:b4 brd ff:ff:ff:ff:ff:ff
    inet 172.17.8.2/32 scope global eth0
       valid_lft forever preferred_lft forever

這裡容器獲取的是 /32 位主機地址,表示將容器 A 作為一個單點的區域網。

瞄一眼容器 A 的預設路由:

$ ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

現在問題來了,從路由表可以知道 169.254.1.1 是容器的預設閘道器,但卻找不到任何一張網路卡對應這個 IP 地址,這是個什麼鬼?

莫慌,先回憶一下,當一個資料包的目的地址不是本機時,就會查詢路由表,從路由表中查到閘道器後,它首先會通過 ARP 獲得閘道器的 MAC 地址,然後在發出的網路資料包中將目標 MAC 改為閘道器的 MAC,而閘道器的 IP 地址不會出現在任何網路包頭中。也就是說,沒有人在乎這個 IP 地址究竟是什麼,只要能找到對應的 MAC 地址,能響應 ARP 就行了。

想到這裡,我們就可以繼續往下進行了,可以通過 ip neigh 命令檢視一下本地的 ARP 快取:

$ ip neigh
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee REACHABLE

這個 MAC 地址應該是 Calico 硬塞進去的,而且還能響應 ARP。但它究竟是怎麼實現的呢?

我們先來回想一下正常情況,核心會對外傳送 ARP 請求,詢問整個二層網路中誰擁有 169.254.1.1 這個 IP 地址,擁有這個 IP 地址的裝置會將自己的 MAC
地址返回給對方。但現在的情況比較尷尬,容器和主機都沒有這個 IP 地址,甚至連主機上的埠 calicba2f87f6bb,MAC 地址也是一個無用的 ee:ee:ee:ee:ee:ee。按道理容器和主機網路根本就無法通訊才對呀!所以 Calico 是怎麼做到的呢?

這裡我就不繞彎子了,實際上 Calico 利用了網路卡的代理 ARP 功能。代理 ARP 是 ARP 協議的一個變種,當 ARP 請求目標跨網段時,閘道器裝置收到此 ARP 請求,會用自己的 MAC 地址返回給請求者,這便是代理 ARP(Proxy ARP)。舉個例子:

Calico 網路通訊原理揭祕

上面這張圖中,電腦傳送 ARP 請求伺服器 8.8.8.8 的 MAC 地址,路由器(閘道器)收到這個請求時會進行判斷,由於目標 8.8.8.8 不屬於本網段(即跨網段),此時便返回自己的介面 MAC 地址給 PC,後續電腦訪問伺服器時,目標 MAC 直接封裝為 MAC254。

現在我們知道,Calico 本質上還是利用了代理 ARP 撒了一個“善意的謊言”,下面我們來確認一下。

檢視宿主機的網路卡資訊和路由資訊:

$ ip addr
...
771: calicba2f87f6bb@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 14
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever
...

$ ip route 
...
172.17.8.2 dev calicba2f87f6bb scope link
...

檢視是否開啟代理 ARP:

$ cat /proc/sys/net/ipv4/conf/calicba2f87f6bb/proxy_arp
1

如果還不放心,可以通過 tcpdump 抓包驗證一下:

$ tcpdump -i calicba2f87f6bb -e -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on calicba2f87f6bb, link-type EN10MB (Ethernet), capture size 262144 bytes


14:27:13.565539 ee:ee:ee:ee:ee:ee > 0a:58:ac:1c:ce:12, ethertype IPv4 (0x0800), length 4191: 10.96.0.1.443 > 172.17.8.2.36180: Flags [P.], seq 403862039:403866164, ack 2023703985, win 990, options [nop,nop,TS val 331780572 ecr 603755526], length 4125
14:27:13.565613 0a:58:ac:1c:ce:12 > ee:ee:ee:ee:ee:ee, ethertype IPv4 (0x0800), length 66: 172.17.8.2.36180 > 10.96.0.1.443: Flags [.], ack 4125, win 2465, options [nop,nop,TS val 603758497 ecr 331780572], length 0

總結:

  1. Calico 通過一個巧妙的方法將 workload 的所有流量引導到一個特殊的閘道器 169.254.1.1,從而引流到主機的 calixxx 網路裝置上,最終將二三層流量全部轉換成三層流量來轉發。
  2. 在主機上通過開啟代理 ARP 功能來實現 ARP 應答,使得 ARP 廣播被抑制在主機上,抑制了廣播風暴,也不會有 ARP 表膨脹的問題。

2. 模擬組網

既然我們已經掌握了 Calico 的組網原理,接下來就可以手動模擬驗證了。架構如圖所示:

Calico 網路通訊原理揭祕

先在 Host0 上執行以下命令:

$ ip link add veth0 type veth peer name eth0
$ ip netns add ns0
$ ip link set eth0 netns ns0
$ ip netns exec ns0 ip a add 10.20.1.2/24 dev eth0
$ ip netns exec ns0 ip link set eth0 up
$ ip netns exec ns0 ip route add 169.254.1.1 dev eth0 scope link
$ ip netns exec ns0 ip route add default via 169.254.1.1 dev eth0
$ ip link set veth0 up
$ ip route add 10.20.1.2 dev veth0 scope link
$ ip route add 10.20.1.3 via 192.168.1.16 dev ens192
$ echo 1 > /proc/sys/net/ipv4/conf/veth0/proxy_arp

在 Host1 上執行以下命令:

$ ip link add veth0 type veth peer name eth0
$ ip netns add ns1
$ ip link set eth0 netns ns1
$ ip netns exec ns1 ip a add 10.20.1.3/24 dev eth0
$ ip netns exec ns1 ip link set eth0 up
$ ip netns exec ns1 ip route add 169.254.1.1 dev eth0 scope link
$ ip netns exec ns1 ip route add default via 169.254.1.1 dev eth0
$ ip link set veth0 up
$ ip route add 10.20.1.3 dev veth0 scope link
$ ip route add 10.20.1.2 via 192.168.1.32 dev ens192
$ echo 1 > /proc/sys/net/ipv4/conf/veth0/proxy_arp

網路連通性測試:

# Host0
$ ip netns exec ns1 ping 10.20.1.3
PING 10.20.1.3 (10.20.1.3) 56(84) bytes of data.
64 bytes from 10.20.1.3: icmp_seq=1 ttl=62 time=0.303 ms
64 bytes from 10.20.1.3: icmp_seq=2 ttl=62 time=0.334 ms

實驗成功!

具體的轉發過程如下:

  1. ns0 網路空間的所有資料包都轉發到一個虛擬的 IP 地址 169.254.1.1,傳送 ARP 請求。
  2. Host0 的 veth 端收到 ARP 請求時通過開啟網路卡的代理 ARP 功能直接把自己的 MAC 地址返回給 ns0。
  3. ns0 傳送目的地址為 ns1 的 IP 資料包。
  4. 因為使用了 169.254.1.1 這樣的地址,Host 判斷為三層路由轉發,查詢本地路由 10.20.1.3 via 192.168.1.16 dev ens192 傳送給對端 Host1,如果配置了 BGP,這裡就會看到 proto 協議為 BIRD。
  5. 當 Host1 收到 10.20.1.3 的資料包時,匹配本地的路由表 10.20.1.3 dev veth0 scope link,將資料包轉發到對應的 veth0 端,從而到達 ns1。
  6. 回程類似

通過這個實驗,我們可以很清晰地掌握 Calico 網路的資料轉發流程,首先需要給所有的 ns 配置一條特殊的路由,並利用 veth 的代理 ARP 功能讓 ns 出來的所有轉發都變成三層路由轉發,然後再利用主機的路由進行轉發。這種方式不僅實現了同主機的二三層轉發,也能實現跨主機的轉發。


Calico 網路通訊原理揭祕

相關文章