calico网络流量剖析
跨节点不同pod通信流程图
pod(nginx) pod(busybox)
eth0 eth0
(172.20.139.94 fa:ed:2c:cc:c9:cd) (172.20.247.1 1e:a4:fa:76:7e:95)
| |
| |
cali1342368ccea cali12d4a061371
| |
| |
tunl0(172.20.139.64) tunl0(172.20.247.0)
| |
| |
node ens33(192.168.8.203 00:16:3e:1e:ea:ea) node ens33(192.168.8.202 00:16:3e:1e:fa:fb)
| icmp(1)-ipip(4) |
|____________________________<<<_______________________|
查看相邻节点的信息
# calicoctl node status
Calico process is running.
IPv4 BGP status
+-----------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+-----------------+-------------------+-------+----------+-------------+
| 192.168.8.202 | node-to-node mesh | up | 12:49:54 | Established |
| 192.168.8.203 | node-to-node mesh | up | 12:49:54 | Established |
| 192.168.8.204 | node-to-node mesh | up | 12:49:54 | Established |
+-----------------+-------------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
实战抓包观察calico的流量
↑--- endpoints ---↓
ingress ---> service ----------- pods
service --- kube-proxy(ipvs)
# kubectl create deployment nginx --image=nginx:1.21.6
# kubectl expose deployment nginx --port=80 --target-port=80
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6f648b8457-7ld9v 1/1 Running 0 10m 172.20.139.94 192.168.8.203 <none> <none>
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 21d
nginx ClusterIP 10.68.6.226 <none> 80/TCP 10m
# ipvsadm -ln |grep -A1 10.68.6.226
TCP 10.68.6.226:80 rr
-> 172.20.139.94:80 Masq 1 0 0
# ipvs会配置一个虚拟网卡kube-ipvs0,实际了类似于nginx upstream的功能,只要有新增svc,就会在k8s集群里面的每台node下的kube-ipvs0配置一段inet
# ip a|grep kube-ipvs0
4: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
inet 10.68.0.1/32 scope global kube-ipvs0
inet 10.68.0.2/32 scope global kube-ipvs0
inet 10.68.107.220/32 scope global kube-ipvs0
inet 10.68.229.156/32 scope global kube-ipvs0
inet 10.68.6.226/32 scope global kube-ipvs0
# 至此 service 到 pod这一段的旅程完成,然后开始重头戏,实际到pod的流量旅程
# 注:
在 Kubernetes 中,Service 是一个虚拟的抽象层,负责将请求转发到指定的 Pod 上。因此,Service 的流量实际上是通过 Kubernetes 集群内部的网络组件进行转发的,而不是直接流经宿主机。
如果您需要捕获 Kubernetes 中 Service 的流量,可以通过选择一个 Pod,在其中启动抓包工具的方式来实现。在启动抓包工具时,需要注意安全性和特权模式的问题
# calico默认采用ipip协议来在多节点间传输数据,在每台节点上面会生成一个虚拟网卡 tunl0,它用来在一个IP报文中封装新的IP头部,下面我们实际以跨节点的pod通信来解释这一切
# ip a|grep tunl0
12: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
inet 172.20.84.128/32 scope global tunl0
# kubectl exec -it nginx-6f648b8457-7ld9v -- bash
root@nginx-6f648b8457-7ld9v:/# cat /sys/class/net/eth0/iflink
9
# 安装route
# apt install net-tools
# 确保测试pod调度在和nginx不同的node上面
# kubectl run busybox --rm -it --image=registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2 -- sh
If you don't see a command prompt, try pressing enter.
/ # ping 172.20.139.94
PING 172.20.139.94 (172.20.139.94): 56 data bytes
64 bytes from 172.20.139.94: seq=0 ttl=62 time=2.243 ms
# 在运行 nginx的node上抓包
# tcpdump -n -s0 -i ens33 ip host 192.168.8.202 -w 1.pcap
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox 1/1 Running 0 5m58s 172.20.247.1 192.168.8.202 <none> <none>
nginx-6f648b8457-7ld9v 1/1 Running 0 10m 172.20.139.94 192.168.8.203 <none> <none>
# 用 Wireshark 分析 1.pcap, 设置过滤条件 ip.addr == 172.20.139.94
# 然后点开最下面的 Internet Protocol Version 4, Src: 192.168.8.201, Dst: 192.168.8.202
# 可以看到节点间通信采用的协议: Protocol: IPIP (4)
"Protocol: IPIP (4)" 表示使用的协议是 IPIP(IP封装)协议,并且指定了协议版本为 4。
IPIP 是一种网络封装协议,它允许在一个IP数据包中封装另一个IP数据包。它在网络中创建了一个隧道,通过在外层IP数据包中封装内层IP数据包来传递数据。这种封装允许在不同的网络中传输IP数据包,并且可以在不同的网络层之间进行路由。
在您提供的数据包信息中,“Protocol: IPIP (4)” 表示该数据包使用了 IPIP 协议,并且指定了协议版本为 4。这意味着数据包的源IP地址和目的IP地址是通过 IPIP 封装传输的,而封装后的数据包使用的是 IPv4 协议。
通过使用 IPIP 封装,可以实现不同网络之间的传输和连接,同时保持原始IP数据包的完整性。然而,需要注意的是,IPIP 封装会增加一定的开销和网络负载,因此在使用时需要权衡其性能影响和实际需求。
calico支持bgp和ipip两种协议,bgp只能运行在l2层,ipip支持l2+l3层,通常我们所用的云平台vpc网络,都是l3层,只知道对端的ip,不知道其mac信息。
calico生产实战排错案例
1、谷歌云google cloud自建K8S集群节点间通信问题
谷歌云创建虚拟机自建K8S(如果CNI组件用的calico,协议是IPIP的话),需要在防火墙中放开IPIP协议
# 问题现象
service以及跨节点的POD IP无法通信,tcpdump抓包对应的IP地址什么包都没收到
# 解决步骤
在 Google Cloud 上,要开放 IPIP(IP in IP)协议以允许在虚拟机之间进行通信,你需要创建一个自定义防火墙规则。请遵循以下步骤创建此规则:
登录到 Google Cloud Console ↗。
在左侧导航菜单中,选择 "VPC 网络" > "防火墙"。
单击 "创建防火墙规则" 按钮。
为防火墙规则提供一个描述性名称,例如 "allow-ipip"。
在 "网络" 下拉菜单中,选择你要应用此规则的 VPC 网络。
在 "优先级" 字段中,输入一个数字(例如 1000),以表示此规则的优先级。较低的数字表示较高的优先级。
在 "方向" 下拉菜单中,选择 "入站"。
在 "动作" 下拉菜单中,选择 "允许"。
在 "来源过滤器" 下拉菜单中,选择 "IP 地址范围"。在 "来源 IP 地址范围" 字段中,输入允许 IPIP 通信的 IP 地址范围,例如 "10.128.0.0/9"(用于允许来自 Google Cloud 内部所有 VM 的 IPIP 通信)。
在 "协议和端口" 部分,选择 "指定协议和端口"。在协议字段中输入 "4"(IPIP 协议的 IP 协议号),并将端口字段留空。
单击 "创建" 按钮,完成防火墙规则的创建。
现在,你已经创建了一个防火墙规则,允许 Google Cloud 虚拟机之间的 IPIP 协议通信。
2、calico-node无法正常运行
calico-node无法正常运行,报错如下:
calico/node is not ready: BIRD is not ready: BGP not established with x.x.x.x,y.y.y.y...
问题原因是calico node名称冲突
扩容工具扩容调用API扩容机器,会给扩容的机器以AWS-na-k8s-temp[1-9]来命名,集群上存在之前扩容的一台机器名称为aws-na-k8s-temp1,昨晚扩容了三台机器,名称为aws-na-k8s-temp1、aws-na-k8s-temp2、aws-na-k8s-temp3
然后就是这两台同样主机名(aws-na-k8s-temp1)的机器,在calico里面注册节点名称时就会产生冲突 ,导致冲突节点只能有一个注册进来
## 解决问题时相关命令记录
# 删除冲突的已注册calico节点
calicoctl delete node aws-na-k8s-temp1
# 给冲突机器重命名下hostname
hostnamectl set-hostname ip-172-31-7-227
# 将新名称写入calico的节点名称配置里面(注意不能有\n换行符)
echo -n ip-172-31-7-227 > /var/lib/calico/nodename
# 然后删除问题的calico-node使其重启
kubectl -n kube-system delete pod calico-node-xf99f
# 观察是否正常
# calicoctl get node
NAME
aws-na-k8s-temp2
aws-na-k8s-temp3
ip-172-31-1-50
ip-172-31-2-247
ip-172-31-7-227
# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+----------+-------------+
| 172.31.8.108 | node-to-node mesh | up | 13:00:30 | Established |
| 172.31.3.33 | node-to-node mesh | up | 13:08:09 | Established |
| 172.31.2.247 | node-to-node mesh | up | 02:40:43 | Established |
| 172.31.7.227 | node-to-node mesh | up | 02:40:49 | Established |
+--------------+-------------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
# 后续扩容改进操作
扩容好的机器要记得把hostname改成唯一的名称(相关扩容脚本已更新),记得操作前先把节点驱逐打污点:
kubectl drain 172.31.3.33 --delete-local-data --ignore-daemonsets