一文明白calico的IPIP網(wǎng)絡(luò)模式
前言
本文主要分析k8s中網(wǎng)絡(luò)組件calico的 IPIP網(wǎng)絡(luò)模式。旨在理解IPIP網(wǎng)絡(luò)模式下產(chǎn)生的calixxxx,tunl0等設(shè)備以及跨節(jié)點網(wǎng)絡(luò)通信方式??赡芸粗悬c枯燥,但是請花幾分鐘時間堅持看完,如果看到后面忘了前面,請反復(fù)看兩遍,這幾分鐘時間一定你會花的很值。
一、calico介紹
Calico是Kubernetes生態(tài)系統(tǒng)中另一種流行的網(wǎng)絡(luò)選擇。雖然Flannel被公認為是最簡單的選擇,但Calico以其性能、靈活性而聞名。Calico的功能更為全面,不僅提供主機和pod之間的網(wǎng)絡(luò)連接,還涉及網(wǎng)絡(luò)安全和管理。Calico CNI插件在CNI框架內(nèi)封裝了Calico的功能。
Calico是一個基于BGP的純?nèi)龑拥木W(wǎng)絡(luò)方案,與OpenStack、Kubernetes、AWS、GCE等云平臺都能夠良好地集成。Calico在每個計算節(jié)點都利用Linux Kernel實現(xiàn)了一個高效的虛擬路由器vRouter來負責(zé)數(shù)據(jù)轉(zhuǎn)發(fā)。每個vRouter都通過BGP1協(xié)議把在本節(jié)點上運行的容器的路由信息向整個Calico網(wǎng)絡(luò)廣播,并自動設(shè)置到達其他節(jié)點的路由轉(zhuǎn)發(fā)規(guī)則。Calico保證所有容器之間的數(shù)據(jù)流量都是通過IP路由的方式完成互聯(lián)互通的。Calico節(jié)點組網(wǎng)時可以直接利用數(shù)據(jù)中心的網(wǎng)絡(luò)結(jié)構(gòu)(L2或者L3),不需要額外的NAT、隧道或者Overlay Network,沒有額外的封包解包,能夠節(jié)約CPU運算,提高網(wǎng)絡(luò)效率。
此外,Calico基于iptables還提供了豐富的網(wǎng)絡(luò)策略,實現(xiàn)了Kubernetes的Network Policy策略,提供容器間網(wǎng)絡(luò)可達性限制的功能。
calico官網(wǎng):https://www.projectcalico.org/
二、calico架構(gòu)及核心組件
架構(gòu)圖如下:
calico核心組件:
- Felix:運行在每個需要運行workload的節(jié)點上的agent進程。主要負責(zé)配置路由及 ACLs(訪問控制列表) 等信息來確保 endpoint 的連通狀態(tài),保證跨主機容器的網(wǎng)絡(luò)互通;
- etcd:強一致性、高可用的鍵值存儲,持久存儲calico數(shù)據(jù)的存儲管理系統(tǒng)。主要負責(zé)網(wǎng)絡(luò)元數(shù)據(jù)一致性,確保Calico網(wǎng)絡(luò)狀態(tài)的準確性;
- BGP Client(BIRD):讀取Felix設(shè)置的內(nèi)核路由狀態(tài),在數(shù)據(jù)中心分發(fā)狀態(tài)。
- BGP Route Reflector(BIRD):BGP路由反射器,在較大規(guī)模部署時使用。如果僅使用BGP Client形成mesh全網(wǎng)互聯(lián)就會導(dǎo)致規(guī)模限制,因為所有BGP client節(jié)點之間兩兩互聯(lián),需要建立N^2個連接,拓撲也會變得復(fù)雜。因此使用reflector來負責(zé)client之間的連接,防止節(jié)點兩兩相連。
三、calico工作原理
Calico把每個操作系統(tǒng)的協(xié)議棧認為是一個路由器,然后把所有的容器認為是連在這個路由器上的網(wǎng)絡(luò)終端,在路由器之間跑標(biāo)準的路由協(xié)議——BGP的協(xié)議,然后讓它們自己去學(xué)習(xí)這個網(wǎng)絡(luò)拓撲該如何轉(zhuǎn)發(fā)。所以Calico方案其實是一個純?nèi)龑拥姆桨福簿褪钦f讓每臺機器的協(xié)議棧的三層去確保兩個容器,跨主機容器之間的三層連通性。
四、calico的兩種網(wǎng)絡(luò)方式
1)IPIP
把 IP 層封裝到 IP 層的一個 tunnel。它的作用其實基本上就相當(dāng)于一個基于IP層的網(wǎng)橋!一般來說,普通的網(wǎng)橋是基于mac層的,根本不需 IP,而這個 ipip 則是通過兩端的路由做一個 tunnel,把兩個本來不通的網(wǎng)絡(luò)通過點對點連接起來。ipip 的源代碼在內(nèi)核 net/ipv4/ipip.c 中可以找到。
2)BGP
邊界網(wǎng)關(guān)協(xié)議(Border Gateway Protocol, BGP)是互聯(lián)網(wǎng)上一個核心的去中心化自治路由協(xié)議。它通過維護IP路由表或‘前綴’表來實現(xiàn)自治系統(tǒng)(AS)之間的可達性,屬于矢量路由協(xié)議。BGP不使用傳統(tǒng)的內(nèi)部網(wǎng)關(guān)協(xié)議(IGP)的指標(biāo),而使用基于路徑、網(wǎng)絡(luò)策略或規(guī)則集來決定路由。因此,它更適合被稱為矢量性協(xié)議,而不是路由協(xié)議。
五、IPIP網(wǎng)絡(luò)模式分析
由于個人環(huán)境中使用的是IPIP模式,因此接下來這里分析一下這種模式。
- # kubectl get po -o wide -n paas | grep hello
- demo-hello-perf-d84bffcb8-7fxqj 1/1 Running 0 9d 10.20.105.215 node2.perf <none> <none>
- demo-hello-sit-6d5c9f44bc-ncpql 1/1 Running 0 9d 10.20.42.31 node1.sit <none> <none>
進行ping測試
這里在demo-hello-perf這個pod中ping demo-hello-sit這個pod。
- root@demo-hello-perf-d84bffcb8-7fxqj:/# ping 10.20.42.31
- PING 10.20.42.31 (10.20.42.31) 56(84) bytes of data.
- 64 bytes from 10.20.42.31: icmp_seq=1 ttl=62 time=5.60 ms
- 64 bytes from 10.20.42.31: icmp_seq=2 ttl=62 time=1.66 ms
- 64 bytes from 10.20.42.31: icmp_seq=3 ttl=62 time=1.79 ms
- ^C
- --- 10.20.42.31 ping statistics ---
- 3 packets transmitted, 3 received, 0% packet loss, time 6ms
- rtt min/avg/max/mdev = 1.662/3.015/5.595/1.825 ms
進入pod demo-hello-perf中查看這個pod中的路由信息
- root@demo-hello-perf-d84bffcb8-7fxqj:/# route -n
- Kernel IP routing table
- Destination Gateway Genmask Flags Metric Ref Use Iface
- 0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
- 169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
根據(jù)路由信息,ping 10.20.42.31,會匹配到第一條。
第一條路由的意思是:去往任何網(wǎng)段的數(shù)據(jù)包都發(fā)往網(wǎng)關(guān)169.254.1.1,然后從eth0網(wǎng)卡發(fā)送出去。
demo-hello-perf所在的node node2.perf 宿主機上路由信息如下:
- # route -n
- Kernel IP routing table
- Destination Gateway Genmask Flags Metric Ref Use Iface
- 0.0.0.0 172.16.36.1 0.0.0.0 UG 100 0 0 eth0
- 10.20.42.0 172.16.35.4 255.255.255.192 UG 0 0 0 tunl0
- 10.20.105.196 0.0.0.0 255.255.255.255 UH 0 0 0 cali4bb1efe70a2
- 169.254.169.254 172.16.36.2 255.255.255.255 UGH 100 0 0 eth0
- 172.16.36.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
- 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
可以看到一條Destination為 10.20.42.0的路由。
意思是:當(dāng)ping包來到master節(jié)點上,會匹配到路由tunl0。該路由的意思是:去往10.20.42.0/26的網(wǎng)段的數(shù)據(jù)包都發(fā)往網(wǎng)關(guān)172.16.35.4。因為demo-hello-perf的pod在172.16.36.5上,demo-hello-sit的pod在172.16.35.4上。所以數(shù)據(jù)包就通過設(shè)備tunl0發(fā)往到node節(jié)點上。
demo-hello-sit所在的node node1.sit 宿主機上路由信息如下:
- # route -n
- Kernel IP routing table
- Destination Gateway Genmask Flags Metric Ref Use Iface
- 0.0.0.0 172.16.35.1 0.0.0.0 UG 100 0 0 eth0
- 10.20.15.64 172.16.36.4 255.255.255.192 UG 0 0 0 tunl0
- 10.20.42.31 0.0.0.0 255.255.255.255 UH 0 0 0 cali04736ec14ce
- 10.20.105.192 172.16.36.5 255.255.255.192 UG 0 0 0 tunl0
當(dāng)node節(jié)點網(wǎng)卡收到數(shù)據(jù)包之后,發(fā)現(xiàn)發(fā)往的目的ip為10.20.42.31,于是匹配到Destination為10.20.42.31的路由。
該路由的意思是:10.20.42.31是本機直連設(shè)備,去往設(shè)備的數(shù)據(jù)包發(fā)往cali04736ec14ce
為什么這么奇怪會有一個名為cali04736ec14ce的設(shè)備呢?這是個啥玩意兒呢?
其實這個設(shè)備就是veth pair的一端。在創(chuàng)建demo-hello-sit 時calico會給demo-hello-sit創(chuàng)建一個veth pair設(shè)備。一端是demo-hello-sit 的網(wǎng)卡,另一端就是我們看到的cali04736ec14ce
接著驗證一下。我們進入demo-hello-sit 的pod,查看到 4 號設(shè)備后面的編號是:122964
- root@demo-hello-sit--6d5c9f44bc-ncpql:/# ip a
- 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
- 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
- link/ipip 0.0.0.0 brd 0.0.0.0
- 4: eth0@if122964: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1380 qdisc noqueue state UP group default
- link/ether 9a:7d:b2:26:9b:17 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 10.20.42.31/32 brd 10.20.42.31 scope global eth0
- valid_lft forever preferred_lft forever
然后我們登錄到demo-hello-sit這個pod所在的宿主機查看
- # ip a | grep -A 5 "cali04736ec14ce"
- 122964: cali04736ec14ce@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1380 qdisc noqueue state UP group default
- link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 16
- inet6 fe80::ecee:eeff:feee:eeee/64 scope link
- valid_lft forever preferred_lft forever
- 120918: calidd1cafcd275@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1380 qdisc noqueue state UP group default
- link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 2
發(fā)現(xiàn)pod demo-hello-sit中 的另一端設(shè)備編號和這里在node上看到的cali04736ec14ce編號122964是一樣的
所以,node上的路由,發(fā)送cali04736ec14ce網(wǎng)卡設(shè)備的數(shù)據(jù)其實就是發(fā)送到了demo-hello-sit的這個pod中去了。到這里ping包就到了目的地。
注意看 demo-hello-sit這個pod所在的宿主機的路由,有一條 Destination為10.20.105.192的路由
- # route -n
- Kernel IP routing table
- Destination Gateway Genmask Flags Metric Ref Use Iface
- ...
- 0.0.0.0 172.16.35.1 0.0.0.0 UG 100 0 0 eth0
- 10.20.105.192 172.16.36.5 255.255.255.192 UG 0 0 0 tunl0
- ...
再查看一下demo-hello-sit的pod中路由信息,和demo-hello-perf的pod中是一樣的。
所以綜合上述例子來看,IPIP的網(wǎng)絡(luò)模式就是將IP網(wǎng)絡(luò)封裝了一層。特點就是所有pod的數(shù)據(jù)流量都從隧道tunl0發(fā)送,并且tunl0這里增加了一層傳輸層的封包操作。
六、抓包分析
在demo-hello-perf這個pod中ping demo-hello-sit這個pod,接著在demo-hello-sit這個pod所在的宿主機進行tcpdump
- # tcpdump -i eth0 -nn -w icmp_ping.cap
- tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
在demo-hello-perf這個pod中進行ping demo-hello-sit的操作
- root@demo-hello-perf-d84bffcb8-7fxqj:/# ping 10.20.42.31
- PING 10.20.42.31 (10.20.42.31) 56(84) bytes of data.
- 64 bytes from 10.20.42.31: icmp_seq=1 ttl=62 time=5.66 ms
- 64 bytes from 10.20.42.31: icmp_seq=2 ttl=62 time=1.68 ms
- 64 bytes from 10.20.42.31: icmp_seq=3 ttl=62 time=1.61 ms
- ^C
- --- 10.20.42.31 ping statistics ---
- 3 packets transmitted, 3 received, 0% packet loss, time 6ms
- rtt min/avg/max/mdev = 1.608/2.983/5.659/1.892 ms
結(jié)束抓包后下載icmp_ping.cap到本地windows進行抓包分析
能看到該數(shù)據(jù)包一共5層,其中IP(Internet Protocol)所在的網(wǎng)絡(luò)層有兩個,分別是pod之間的網(wǎng)絡(luò)和主機之間的網(wǎng)絡(luò)封裝。
紅色框選的是兩個pod所在的宿主機,藍色框選的是兩個pod的ip,src表示發(fā)起ping操作的pod所在的宿主機ip以及發(fā)起ping操作的pod的ip,dst表示被ping的pod所在的宿主機ip及被ping的pod的ip
根據(jù)數(shù)據(jù)包的封裝順序,應(yīng)該是在demo-hello-perf ping demo-hello-sit的ICMP包外面多封裝了一層主機之間的數(shù)據(jù)包。
可以看到每個數(shù)據(jù)報文共有兩個IP網(wǎng)絡(luò)層,內(nèi)層是Pod容器之間的IP網(wǎng)絡(luò)報文,外層是宿主機節(jié)點的網(wǎng)絡(luò)報文(2個node節(jié)點)。之所以要這樣做是因為tunl0是一個隧道端點設(shè)備,在數(shù)據(jù)到達時要加上一層封裝,便于發(fā)送到對端隧道設(shè)備中。
兩層封包的具體內(nèi)容如下:
Pod間的通信經(jīng)由IPIP的三層隧道轉(zhuǎn)發(fā),相比較VxLAN的二層隧道來說,IPIP隧道的開銷較小,但其安全性也更差一些。
七、pod到svc的訪問
查看service
- # kubectl get svc -o wide -n paas | grep hello
- demo-hello-perf ClusterIP 10.10.255.18 <none> 8080/TCP 10d appEnv=perf,appName=demo-hello
- demo-hello-sit ClusterIP 10.10.48.254 <none> 8080/TCP 10d appEnv=sit,appName=demo-hello
在pod demo-hello-sit 的宿主機上抓包
- # tcpdump -i eth0 -nn -w svc.cap
- tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
測試訪問,在demo-hello-sit中curl demo-hello-perf的svc的地址和端口
- root@demo-hello-perf-d84bffcb8-7fxqj:/# curl -I http://10.10.48.254:8080/actuator/health
- HTTP/1.1 200
- Content-Type: application/vnd.spring-boot.actuator.v3+json
- Transfer-Encoding: chunked
- Date: Fri, 30 Apr 2021 01:42:56 GMT
- root@demo-hello-perf-d84bffcb8-7fxqj:/# curl -I http://10.10.48.254:8080/actuator/health
- HTTP/1.1 200
- Content-Type: application/vnd.spring-boot.actuator.v3+json
- Transfer-Encoding: chunked
- Date: Fri, 30 Apr 2021 01:42:58 GMT
- root@demo-hello-perf-d84bffcb8-7fxqj:/# curl -I http://10.10.48.254:8080/actuator/health
- HTTP/1.1 200
- Content-Type: application/vnd.spring-boot.actuator.v3+json
- Transfer-Encoding: chunked
- Date: Fri, 30 Apr 2021 01:42:58 GMT
結(jié)束抓包,下載svc.cap文件放到wireshark中打開查看

可以看到wireshark中Src和Dst的結(jié)果。任然是和上面pod中訪問pod的ip地址一樣。這里Src和Dst任然是兩個pod的宿主機的內(nèi)網(wǎng)ip和兩個pod自己的ip地址。是用ipip的方式進行通信的。
通過以上例子演示,應(yīng)該就看明白了IPIP網(wǎng)絡(luò)模式的通信方式了吧!