前言
本文主要分析 K8s 中网络组件 calico 的 IPIP网络模式。旨在理解 IPIP 网络模式下产生的 calixxxx,tunl0 等设备以及跨节点网络通信方式。可能看着有点枯燥,但是请花几分钟时间坚持看完,如果看到后面忘了前面,请反复看两遍,这几分钟时间一定你会花的很值。
一、Calico介绍
Calico 是 Kubernetes 生态系统中另一种流行的网络选择。虽然 Flannel 被公认为是最简单的选择,但 Calico 以其性能、灵活性而闻名。Calico 的功能更为全面,不仅提供主机和 Pod 之间的网络连接,还涉及网络安全和管理。Calico CNI 插件在 CNI 框架内封装了 Calico 的功能。Calico 是一个基于BGP的纯三层的网络方案,与 OpenStack、Kubernetes、AWS、GCE 等云平台都能够良好地集成。Calico 在每个计算节点都利用 Linux Kernel 实现了一个高效的虚拟路由器 vRouter 来负责数据转发。每个 vRouter 都通过 BGP1 协议把在本节点上运行的容器的路由信息向整个 Calico 网络广播,并自动设置到达其他节点的路由转发规则。Calico 保证所有容器之间的数据流量都是通过IP路由的方式完成互联互通的。Calico节点组网时可以直接利用数据中心的网络结构(L2或者L3),不需要额外的NAT、隧道或者Overlay Network,没有额外的封包解包,能够节约CPU运算,提高网络效率。
此外,Calico 基于 iptables 还提供了丰富的网络策略,实现了 K8s 的 Network Policy策略,提供容器间网络可达性限制的功能。
calico官网:https://www.projectcalico.org/
二、Calico架构及核心组件
架构图如下:
Calico 核心组件:
- Felix:运行在每个需要运行 workload 的节点上的 agent 进程。主要负责配置路由及 ACLs(访问控制列表)等信息来确保 endpoint 的连通状态,保证跨主机容器的网络互通;
- etcd:强一致性、高可用的键值存储,持久存储calico数据的存储管理系统。主要负责网络元数据一致性,确保Calico网络状态的准确性;
- BGP Client(BIRD):读取Felix设置的内核路由状态,在数据中心分发状态。
- BGP Route Reflector(BIRD):BGP路由反射器,在较大规模部署时使用。如果仅使用BGP Client形成mesh全网互联就会导致规模限制,因为所有BGP client节点之间两两互联,需要建立N^2个连接,拓扑也会变得复杂。因此使用reflector来负责client之间的连接,防止节点两两相连。
三、Calico工作原理
Calico 把每个操作系统的协议栈认为是一个路由器,然后把所有的容器认为是连在这个路由器上的网络终端,在路由器之间跑标准的路由协议------BGP的协议,然后让它们自己去学习这个网络拓扑该如何转发。所以 Calico 方案其实是一个纯三层的方案,也就是说让每台机器的协议栈的三层去确保两个容器,跨主机容器之间的三层连通性。
四、Calico的两种网络方式
1)IPIP 把 IP 层封装到 IP 层的一个 tunnel。它的作用其实基本上就相当于一个基于IP层的网桥!一般来说,普通的网桥是基于mac层的,根本不需 IP,而这个 ipip 则是通过两端的路由做一个 tunnel,把两个本来不通的网络通过点对点连接起来。ipip 的源代码在内核 net/ipv4/ipip.c 中可以找到。2)BGP边界网关协议(Border Gateway Protocol, BGP)是互联网上一个核心的去中心化自治路由协议。它通过维护IP路由表或'前缀'表来实现自治系统(AS)之间的可达性,属于矢量路由协议。BGP不使用传统的内部网关协议(IGP)的指标,而使用基于路径、网络策略或规则集来决定路由。因此,它更适合被称为矢量性协议,而不是路由协议。
五、IPIP网络模式分析
由于个人环境中使用的是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
根据路由信息,ping 10.20.42.31
,会匹配到第一条。第一条路由的意思是:去往任何网段的数据包都发往网关169.254.1.1,然后从eth0网卡发送出去。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
的路由。
意思是:当 ping 包来到 master 节点上,会匹配到路由 tunl0。该路由的意思是:去往 10.20.42.0/26 的网段的数据包都发往网关172.16.35.4。因为demo-hello-perf的pod在172.16.36.5上,demo-hello-sit的pod在172.16.35.4上。所以数据包就通过设备tunl0发往到node节点上。
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
当 node 节点网卡收到数据包之后,发现发往的目的 IP 为 10.20.42.31,于是匹配到 Dst 为 10.20.42.31 的路由。
该路由的意思是 :10.20.42.31 是本机直连设备,去往设备的数据包发往cali04736ec14ce。为什么这么奇怪会有一个名为 cali04736ec14ce 的设备呢?这是个啥玩意儿呢?其实这个设备就是 veth pair 的一端。在创建demo-hello-sit
时 calico 会给demo-hello-sit
创建一个 veth pair 设备。一端是demo-hello-sit
的网卡,另一端就是我们看到的 cali04736ec14ce。接着验证一下。我们进入 demo-hello-sit 的 Pod,查看到 4 号设备后面的编号是: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
发现 pod demo-hello-sit中 的另一端设备编号和这里在 node 上看到的 cali04736ec14ce 编号 122964 是一样的所以,node上的路由,发送 cali04736ec14ce 网卡设备的数据其实就是发送到了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 的网络模式就是将IP网络封装了一层。特点就是所有 Pod的数据流量都从隧道 tunl0 发送,并且 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
结束抓包后下载 icmp_ping.cap 到本地 windows 进行抓包分析能看到该数据包一共5层,其中IP(Internet Protocol)所在的网络层有两个,分别是Pod 之间的网络和主机之间的网络封装。
红色框选的是两个 Pod 所在的宿主机,蓝色框选的是两个 Pod 的 IP,src 表示发起 ping 操作的 Pod 所在的宿主机 ip 以及发起 ping 操作的 pod 的ip,dst 表示被ping的pod 所在的宿主机 ip 及被 ping 的 pod 的 ip。根据数据包的封装顺序,应该是在 demo-hello-perf ping demo-hello-sit 的 ICMP 包外面多封装了一层主机之间的数据包。
可以看到每个数据报文共有两个IP网络层,内层是 Pod 容器之间的 IP 网络报文,外层是宿主机节点的网络报文(2个node节点)。之所以要这样做是因为 tunl0 是一个隧道端点设备,在数据到达时要加上一层封装,便于发送到对端隧道设备中。两层封包的具体内容如下:
Pod 间的通信经由 IPIP 的三层隧道转发,相比较 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`
结束抓包,下载svc.cap文件放到wireshark中打开查看
可以看到wireshark中Src和Dst的结果。任然是和上面pod中访问pod的ip地址一样。这里Src和Dst任然是两个pod的宿主机的内网ip和两个pod自己的ip地址。是用ipip的方式进行通信的。通过以上例子演示,应该就看明白了IPIP网络模式的通信方式了吧!