Calico是一個用於容器、虛擬機和基於本機主機的工作負載的開源網絡和網絡安全解決方案。Calico支持廣泛的平台,包括Kubernetes, OpenShift, Docker EE, OpenStack和裸機服務。Calico支持多種網絡架構,其中IPIP和BGP兩種網絡架構較為常用。這里簡單說明一下這兩種模式。
這兩種模式下的calico所管理的容器內部聯通主機外部網絡的方法都是一樣的,用linux支持的veth-pair,一端在容器內部一般名稱是eth0@if66,這個66表示的是主機網絡命名空間下的66號ip link.另一端是主機網絡空間下的cali97e45806449,這個97e45806449是VethNameForWorkload函數利用容器屬性計算的加密字符的前11個字符。每個Calico容器內部的路由如下所示:
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
再去看Calico所有的veth-pair在主機空間的calixxx的MAC地址,無一例外都是ee:ee:ee:ee:ee:ee
, 這樣的配置簡化了操作,使得容器會把報文交給169.254.1.1來處理,但是這個地址是本地保留的地址也可以說是個無效地址,但是通過veth-pair會傳遞到對端calixxx上,注意,因為calixxx網卡開啟了arpproxy,所以它會代答所有的ARP請求,讓容器的報文都發到calixxx上,也就是發送到主機網絡棧,再有主機網絡棧的路由來送到下一站. 可以通過cat /proc/sys/net/ipv4/conf/calixxx/proxy_arp/
來查看,輸出都是1.
這里注意,calico要響應arp請求還需要具備三個條件,否則容器內的ARP顯示異常:
- 宿主機的arp代理得打開
- 宿主機需要有訪問目的地址的明確路由,這里我理解為宿主機要有默認路由
- 發送arp request的接口與接收arp request的接口不能是相同,即容器中的默認網關不能是calico的虛擬網關
IPIP模式
IPIP模式是calico的默認網絡架構,其實這也是一種overlay的網絡架構,但是比overlay更常用的vxlan模式相比更加輕量化。IPinIP就是把一個IP數據包又套在一個IP包里,即把 IP 層封裝到 IP 層的一個 tunnel它的作用其實基本上就相當於一個基於IP層的網橋!一般來說,普通的網橋是基於mac層的,根本不需 IP,而這個 ipip 則是通過兩端的路由做一個 tunnel,把兩個本來不通的網絡通過點對點連接起來.
IPIP通信原理
calico中用環境變量CALICO_IPV4POOL_IPIP來標識是否開啟IPinIP Mode. 如果該變量的值為Always那么就是開啟IPIP,如果關閉需要設置為Never(大小寫不敏感,代碼里有strings.ToLower操作)。
IPIP的calico-node啟動后會拉起一個linux系統的tunnel虛擬網卡tunl0, 並由二進制文件allocateip給它分配一個calico IPPool中的地址,log記錄在本機的/var/log/calico/allocate-tunnel-addrs/
目錄下。tunl0是linux支持的隧道設備接口,當有這個接口時,出這個主機的IP包就會本封裝成IPIP報文。同樣的,當有數據進來時也會通過該接口解封IPIP報文。然后IPIP模式的網絡通信模型如下圖所示。
上圖所示的通信如下:
- Pod-1 -> calixxx -> tunl0 -> eth0 <----> eth0 -> calixxx -> tunl0 -> Pod-2 1.c1訪問c2時,ip包會出現在calixxx
- 根據c1宿主機中的路由規則中的下一跳,使用tunl0設備將ip包發送到c2的宿主機
- tunl0是一種ip隧道設備,當ip包進入該設備后,會被Linux中的ipip驅動將該ip包直接封裝在宿主機網絡的ip包中,然后發送到c2的宿主機
- 進入c2的宿主機后,該ip包會由ipip驅動解封裝,獲取原始的ip包,然后根據c2宿主機中路由規則發送到calixxx。
如果此時查看calico相關信息如下所示:
[root@k8s-node-1 ~]# calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ippool 172.16.0.0/16 true Always Never false all()
[root@k8s-x86-1 ~]#
[root@k8s-x86-1 ~]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+------------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+------------+-------------+
| 172.18.8.130 | node-to-node mesh | up | 2022-03-18 | Established |
+--------------+-------------------+-------+------------+-------------+
其中calicoctl get ippool -o wide還可以用calicoctl get ippool -o yaml來查看更加詳細的信息。
tunl0 最后把IP包通過哪個網卡傳送出本主機,需要看本機(假設本機主機名是A)的calico是通過哪個網卡和外部建立BGP連接的。可以通過查看上面的calicoctl node status來查看A的對端(假設是B),然后再到對端B上可以看到它和A的那個網卡建立了BGP連接。這個可以通過配置calico-node daemonset的IP autodetect相關屬性來指定那塊網卡來建立BGP連接(詳見我的另一篇關於calico部署踩坑記錄的博文)
c)。
IPIP通信實例
這里通過兩個例子來說明IPIP是如何封包的,一個是nodeA上的POD到nodeB上的POD,另一個是主機nodeA直接到nodeB上的POD.先根據前面的原理來推測一下這種情況下數據包如何走的呢?封包解包的流程如何呢?
1. nodeA上的POD到nodeB上的POD
主機名 | 容器名 | 容器IP |
---|---|---|
x86-1(172.18.8.130) | net.perf-7db66c46c7-8jgnr | 10.161.252.45 |
x86-2(172.18.8.129) | net.nginx-database-7964bc9748-vbk5k | 10.161.37.200 |
從容器perf去ping容器nginx,在各個關節口抓到的報文如下所示
從上圖可以看出在tunl0上可以抓到的報文還是可以看到容器IP的,當tunl0封包后從eno3網口送出本機時就看不是容器IP了,能看到封包后的IP即eno3的IP也是建立BGP鏈接的網卡的IP. 可以通過命令ip -d link show tunl0
來查看tunl0的詳細信息。下圖是一個k8s計算節點上的calico-node和另一個節點的calico-node建立了IPIP tunnel的tunl0信息:
2. nodeA到nodeB上的POD
非計算節點(172.18.8.210) 安裝了calico,其 tunl0 IP為 10.161.99.128
x86-1(172.18.8.130)上的Pod: net.perf-7db66c46c7-8jgnr,IP 為10.161.252.45
從172.18.8.210是直接ping容器perf
從上圖可以看出包頭被改為了tunl0的IP,並被hostIP封包了。
下面,簡述一下calico-node代碼中如何給它創建的容器配置 IP address, route 的過程,網絡架構還是上面的那個圖:
# k8s-1 [echo 1 > /proc/sys/net/ipv4/conf/cali-xxx/proxy_arp]
ip link add cali-xxx type veth peer name eth0
ip netns add ns0
ip link set eth0 netns ns0
ip netns exec ns0 ip a add 192.168.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 cali-xxx up
ip route add 192.168.1.2 dev cali-xxx scope link
ip route add 192.168.5.2/26 via 172.12.1.11 dev tunl0
# k8s-2 [echo 1 > /proc/sys/net/ipv4/conf/cali-yyy/proxy_arp]
ip link add cali-yyy type veth peer name eth0
ip netns add ns1
ip link set eth0 netns ns1
ip netns exec ns1 ip a add 192.168.5.2/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 cali-yyy up
ip route add 192.168.5.2 dev cali-yyy scope link
ip route add 192.168.1.2/26 via 172.12.1.10 dev tunl0
BGP模式
Calico常用的模式也是它的王牌模式BGP模式,雖然說calico有BGP模式和IPIP模式但是並不是說IPIP模式就不用建立BGP連接了,IPIP模式也是需要建立BGP連接的(可以通過抓取179端口的報文驗證),只不過建立BGP鏈接的目標比較清晰,就是對端的tunl0對應的網卡。BGP模式對於IPIP模式的優點也並不是簡單的描述為“可以跨節點通信”,IPIP模式也是可以跨節點通信的,只要兩個節點能互相連通就可以。而BGP的優點是可以通過指定BGP的對端Peer,一般是交換機,那么只要能接入這個交換機的host或者PC都能通過路由的方式連通calico的其他節點上的容器。也就是說BGP的可擴展的網絡拓撲更靈活。
在使用BGP時,第一步需要做的就是梳理好你的網絡拓撲,然后根據這個拓撲再去進行相關的配置。這里以一個較為簡單的例子來說明,就是所有的計算節點都鏈接到同一個交換機,即它們的BGP Peer都是同一個交換機,也就是中心型拓撲。這里暫且不考慮高可用性,僅舉例說明。這里使用的BGP為IBGP.
接下來,就是要對calico進行配置,因為每個節點都和交換機建立BGP鏈接,那么每個節點的配置都是一樣的。首先,修改calico-node daemonset的配置,其中最重要的配置如下所示:
- name: IP
value: autodetect
- name: IP_AUTODETECTION_METHOD
value: cidr=10.20.0.0/24
- name: CALICO_IPV4POOL_IPIP
value: "Off"
這段配置的含義是關閉IPinIP模式,並讓calico-node自動探測建立BGP的網卡,條件是這個網卡的IP地址是10.20.0.0/24段的。注意,這里的IP值要是autodetect才能自動匹配。另外,還需要指定BGP configurateion (BGP AS number)和 BGP Peer.這里簡單示例一下,更多信息可參考https://projectcalico.docs.tigera.io/reference/resources/bgpconfig
[root@master-1]# cat bgpcfg.yaml
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: false
asNumber: 64512
[root@master-1]# cat bgppeer.yaml
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: node1-peer
spec:
# node is the node hostname
node: master-1
# peerIP is the IP of BGP connection remote peer
peerIP: 10.20.0.1
asNumber: 64512
[root@master-1 ]#
以上的bgpconfiguraton和bgppeer YAML文件都用calicoctl apply -f xxx 來生效. 最終和交換機10.20.0.1建立BGP鏈接。查看calico-node 的狀態為:
[root@master-1]# kubectl exec -ti -n kube-system calicoctl-77vms calicoctl node status
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
Calico process is running.
IPv4 BGP status
+--------------+---------------+-------+------------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+---------------+-------+------------+-------------+
| 10.20.0.1 | node specific | up | 2022-05-05 | Established |
+--------------+---------------+-------+------------+-------------+
IPv6 BGP status
No IPv6 peers found.
這種模式需要對端的交換機支持BGP協議,並且設置BGP的as number要和calico-node的AS number一致(因為是IBGP),另外,因為BGP鏈接是兩端對等的關系,所以交換機也需要指定BGP Peer否則主機端的BGP鏈接建立不起來, 這里以華為交換機為例,說明一下交換機端的配BGP配置方法:
vlan 10
quit
interface vlanif 10 #創建vlan 10
ip address 10.20.0.1 24 #vlan 10 的IP是10.20.0.1,也是交換機發布BGP連接的IP
display interface brief #看是哪個口插上線的,假如是22和24端口
interface GigabitEthernet 0/0/22 #下面三個命令是把22端口加到vlan10中來,這樣10.20.0.2才能和10.20.0.1通
port link-type access
port default vlan 10
# 發起bgp連接
bgp 64512
peer 10.20.0.2 as-number 64512 #向10.20.0.2發起bgp連接
peer 10.20.0.3 as-number 64512 #當添加新節點時,需要在交換機上添加該peer
display bgp peer #連接狀態顯示為Established才說明連接建立完成
display bgp routing-table #查看bgp路由
至此,calico的BGP應該就可以工作了。BGP模式下數據包的路徑單純依靠路由進行轉發,網絡拓撲更自由,IPinIP模式則是有個封包的過程,因此也是一種overlay的方式,封好的包再按照路由進行轉發,到達目的節點還有個解包的過程,因此比BGP模式稍微低效一些,但是封包比vxlan添加的字段少因此比vxlan高效一些。但IPinIP模式部署起來相對簡單,各有利弊。
另外,需要說明的一點是,容器使用的網絡技術中基本都是通過veth-pair把容器和外界連通起來的,然后外面或者通過直接路由(BGP)或者通過overlay(vxlan、IPinIP等)的方式再出宿主機。而僅僅veth-pair就會造成10%的網絡延遲(QPS大約減少5%),這是因為雖然 veth 是一個虛擬的網絡接口,但是在接收數據包的操作上,這個虛擬接口和真實的網路接口並沒有太大的區別。這里除了沒有硬件中斷的處理,其他操作都差不多,特別是軟中斷(softirq)的處理部分其實就和真實的網絡接口是一樣的.在對外發送數據的時候,peer veth 接口都會 raise softirq 來完成一次收包操作,這樣就會帶來數據包處理的額外開銷。如果要減小容器網絡延時,就可以給容器配置 ipvlan/macvlan 的網絡接口來替代 veth 網絡接口。Ipvlan/macvlan 直接在物理網絡接口上虛擬出接口,在發送對外數據包的時候可以直接通過物理接口完成,沒有節點內部類似 veth 的那種 softirq 的開銷。容器使用 ipvlan/maclan 的網絡接口,它的網絡延時可以非常接近物理網絡接口的延時。對於延時敏感的應用程序,我們可以考慮使用 ipvlan/macvlan 網絡接口的容器。不過,由於 ipvlan/macvlan 網絡接口直接掛載在物理網絡接口上,對於需要使用 iptables 規則的容器,比如 Kubernetes 里使用 service 的容器,就不能工作了。這就需要你結合實際應用的需求做個判斷,再選擇合適的方案。