注:本文基于Calico v3.20.1版本编写
1 关于Calico
flannel是overlay网络, 主要工作在L2(VXLAN),calico主要是L3,通过BGP路由协议在机器之间传送报文。
2 安装Calico(IPIP模式)
因为之前有安装flannel,因此需要先删除,
kubectl delete -f kube-flannel.yml
同时需要删除flannel.1和cni0这两个网络设备,
ip link delete cni0
ip link delete flannel.1
之后就可以根据官网安装了,
curl https://docs.projectcalico.org/manifests/calico.yaml -O
kubectl apply -f calico.yaml
是的,就是这么简单,然后就可以在机器上看到新生成的网络设备,
cali00dc074ce54: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
...
tunl0: flags=193<UP,RUNNING,NOARP> mtu 1480
因为默认情况下,calico使用的是ipip隧道模式,这一点我们可以从calico的日志里看出,
2021-10-02 05:04:26.657 [INFO][9] startup/startup.go 651: CALICO_IPV4POOL_NAT_OUTGOING is true (defaulted) through environment variable
2021-10-02 05:04:26.657 [INFO][9] startup/startup.go 992: Ensure default IPv4 pool is created. IPIP mode: Always, VXLAN mode: Never
2021-10-02 05:04:26.718 [INFO][9] startup/startup.go 1002: Created default IPv4 pool (10.244.0.0/16) with NAT outgoing true. IPIP mode: Always, VXLAN mode: Never
所以多了一个tunl0设备,
[root@master home]# ip -d link show tunl0
9: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip ...
与此同时,host上也多了两条通往node的路由记录,
[root@master calico]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 ens33
10.244.104.0 192.168.0.112 255.255.255.192 UG 0 0 0 tunl0
10.244.166.128 192.168.0.111 255.255.255.192 UG 0 0 0 tunl0
...
3 Calico ipip模式工作原理
所谓ipip,也就是IP in IP,即在IP报文的基础上,又封装了一个IP头,和overlay网络相似。
我们以两个node上的两个容器为例,说一下报文的流动过程。
报文从Pod A发出,根据路由发往容器中的网关169.254.1.1,
[root@centos-bc2sm /]# 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
但是你会发现,host上并没有这设备,这就是calico独特之处。那报文到底是如何发送出去的呢?
我们先看下网关通向的二层地址,即mac地址,
[root@centos-bc2sm /]# arp -n
Address HWtype HWaddress Flags Mask Iface
169.254.1.1 ether ee:ee:ee:ee:ee:ee C eth0
可以看到,容器中网关的arp地址是ee:ee:ee:ee:ee:ee,所以现在的问题就变成,哪个网卡的mac地址是这个。
我们在host上查看所有网卡,会发现,cali的所有网卡的mac地址都是ee:ee:ee:ee:ee:ee,
[root@node2 ~]# ifconfig | grep -E "flags|ether"
cali0a4fde325ea: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
cali43af9e40d9b: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
cali61a9554ce92: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
...
另外,我们知道容器中的网卡用的是veth的pair虚拟网卡,一端连接到容器,另一端连着宿主机。所以我们需要查看下容器里网卡编号,
[root@centos-bc2sm /]# ethtool -S eth0
NIC statistics:
peer_ifindex: 14
然后host中编号为14的即为另一端,
[root@node2 ~]# ip link show
...
14: cali0a4fde325ea@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 4
因此,容器中的报文就通过cali0a4fde325ea到达宿主机。
然后根据宿主机的路由,报文发往tunl0
10.244.166.128 192.168.0.111 255.255.255.192 UG 0 0 0 tunl0
报文到达tunl0时,会再次封装一层ip报文头,把目标node的ip作为目的ip,然后通过ens33将报文发出。
对端node收到报文后,解开外层ip报文头,发现是ipip报文,会转交给tunl0, tunl0把内层ip报文头解开,查找路由,就发送给cali7dadcb96356,
10.244.166.140 0.0.0.0 255.255.255.255 UH 0 0 0 cali7dadcb96356
而这个cali7dadcb96356正是目标容器的veth pair网络设备的另一端。
[root@node1 ~]# ip link show cali7dadcb96356
14: cali7dadcb96356@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
[root@centos-t854j /]# ethtool -S eth0
NIC statistics:
peer_ifindex: 14
最终报文到达目标pod。
对于封装后的报文,我们可以通过tcpcump看下报文信息,
而对于同个node的情况,就不需要经过tunl0设备了,因为报文到达host上时,直接根据路由规则就可以转发到对应的cali设备。
4 几个疑问
4.1 容器内网关为169.254.1.1,为什么报文会到宿主机cali网络设备
我们通过arp命令查询mac缓存是逆推而来,而实际上,在发送报文时,会先通过arp广播报文,查询网关的mac地址,由于cali网络设备都开启了arp代理,因此在接收到arp请求时,就会响应该请求,把自己的mac地址回复给ARP广播请求发送者,这样报文就会发往该cali网络设备,同时也就有了这条arp缓存记录。
[root@node2 ~]# cat /proc/sys/net/ipv4/conf/cali0a4fde325ea/proxy_arp
1
所以,理论上容器内的网关设置为什么并不重要,最终由于arp代理,报文到会发送到宿主机的cali设备上。
4.2 为什么cali网络设备mac地址都为ee:ee:ee:ee:ee:ee
因为有一些系统无法在启动时给网络设备分配好mac地址,并且cali的mac地址并不会在实际的链路层报文中使用,因此calico将所有cali网络设备的mac地址都设置为全e,便于管理。
4.3 calico的cali网络设备和flannel的docker0网桥设备有和不同
flannel中所有容器都连接到docker0上,意味着所有容器都可以互通。而对于cali网络设备,每个容器连接到不同的host cali设备中,就能通过网络策略规则实现容器间的隔离,这就为多租户的场景提供了可能。
4.4 每个host是如何获取其他host的路由信息
这是通过BGP协议获取的,calico会在每个host上运行felix和bird进程,
[root@node2 ~]# ps aux | grep -iE "felix|bird"
root 4204 0.0 0.0 4236 400 ? Ss Oct08 0:00 runsv felix
root 4207 0.0 0.0 4236 400 ? Ss Oct08 0:00 runsv bird
root 4208 0.0 0.0 4236 400 ? Ss Oct08 0:00 runsv bird6
root 4213 4.7 2.4 1510880 45552 ? Sl Oct08 105:50 calico-node -felix
root 4412 0.0 0.0 1712 516 ? S Oct08 1:45 bird6 -R -s /var/run/calico/bird6.ctl -d -c /etc/calico/confd/config/bird6.cfg
root 4414 0.0 0.0 1828 780 ? S Oct08 1:51 bird -R -s /var/run/calico/bird.ctl -d -c /etc/calico/confd/config/bird.cfg
其中,felix进程负责监听etcd事件,获取路由、ARP 管理、ACL 管理和同步、状态上报等;而bird进程则负责把felix注入的路由信息通过BGP协议发送给其他机器,这样整个集群中都能获取到相关node的路由信息。
参考资料:
- https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises
- https://docs.projectcalico.org/reference/faq
- https://docs.projectcalico.org/reference/architecture/overview