容器网络Calico入门系列之同节点pod如何通信
一、 Calico同节点pod通信
在Calico网络环境下,k8s集群中的pod在同节点的通信方式都是一样的(除了ebpf后端模式),所以在Calico实践过程中,我们先来了解下同节点的pod是如何通信的。
Calico Architecture ipip Mode
podIP:10.244.244.58/32
是一个32位掩码的地址,和任何其他pod均不在同一个网段。如果需要相互通信,只能通过L3路由的方式实现(将linux主机当成了一个路由器)。L2通信则要求双方地址处于同一个网段
二、 Calico环境搭建(ipip 模式)
-
搭建环境脚本如下
1-setup-env.sh#!/bin/bash date set -v # 1.prep noCNI env cat <<EOF | kind create cluster --name=calico-ipip --image=kindest/node:v1.23.4 --config=- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: disableDefaultCNI: true nodes: - role: control-plane - role: worker - role: worker EOF # 2.remove taints controller_node_ip=`kubectl get node -o wide --no-headers | grep -E "control-plane" | awk -F " " '{print $6}'` kubectl taint nodes $(kubectl get nodes -o name | grep control-plane) node-role.kubernetes.io/master:NoSchedule- kubectl get nodes -o wide # 3. install CNI[Calico v3.23.5] kubectl apply -f calico.yaml #kubectl apply -f https://projectcalico.docs.tigera.io/archive/v3.23/manifests/calico.yaml # 4. install necessary tools for i in $(docker ps -a --format "table {{.Names}}" |grep calico-ipip) do echo $i docker cp /usr/bin/ping $i:/usr/bin/ping docker exec -it $i bash -c "sed -i -e 's/jp.archive.ubuntu.com\|archive.ubuntu.com\|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list" docker exec -it $i bash -c "apt-get -y update > /dev/null && apt-get -y install net-tools tcpdump lrzsz > /dev/null 2>&1" done # 4. wait all pods ready kubectl wait --timeout=100s --for=condition=Ready=true pods --all -A
默认安装就是
ipip:always
的模式,表示跨节点通信时,需要进行overlay的封装。
关于如何修改该项配置: 可以事先在yaml中修改,或者通过calicoctl进行修改。
集群环境搭建成功:
三: Calico同节点pod通信datapath分析
部署测试pod用于分析,自行选择业务镜像,最好能有ping 或者curl 命令
apiVersion: apps/v1
#kind: DaemonSet
kind: Deployment
metadata:
labels:
app: app
name: app
spec:
replicas: 4
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- image: swr.cn-north-4.myhuaweicloud.com/k8s-solution/nettool
name: nettoolbox
-
登录worker2节点上的一个pod
查看该pod 地址信息10.244.244.68/32
32位掩码与任何地址都不在同一个网段,与其他pod通信会选择路由的方式
查看该pod的路由信息: 如果需要访问其他地址,需要经过eth0接口,下一跳为169.254.1.1
如果该pod要访问地址为
10.244.244.67/32
的同节点pod,需要通过默认路由进行寻址。通过L3路由可以找到网关的Mac地址,只有知道网关的地址,才能进行数据包Packet的转发。因为Packet的封装需要知道 SrcIP SrcMac DstIP DstMac 四个关键信息。所以当前pod内的数据包的封装信息应该是:
SrcIP:10.244.244.68/32 SrcMac:42:46:38:5e:33:55
DstIP:10.244.244.67/32 DstMac:(网关169.254.1.1的mac地址)
那么如何知道网关的Mac地址呢:arping 169.254.1.1
DstMac地址现在已经获取到了:
ee:ee:ee:ee:ee:ee
,至于为什么是全e的一个mac地址,后面会在arpproxy中细说。现在信息均已完备 ==》pod内的eth0接口上的数据包可以完成封装了。
ip -d link show
:可以发现eth0这个接口是一个 vethpair设备,它的一端是在Pod eth0 网卡上,另外一端位于宿主node上的网卡上,将数据包由用户空间传递到rootns中
通过
ip r s
命令查看路由的时候,两条路由分别时默认路由,和网关路由,网关路由中的scope link 表示的是什么意思呢?
scope link:链路范围,适用于直接相连的子网或广播域上的单播和广播数据包。
-
登录calico-ipip-worker2 节点
SrcIP:10.244.244.68/32
DstIP:10.244.244.67/32
查看该节点上的网卡信息
可以发现
cali0db0293d5b3
网卡是 源pod eth0网卡的 peer。同时该网卡对应的Mac地址也是:ee:ee:ee:ee:ee:ee
查看该节点上的路由信息:
如果需要前往10.244.244.67
这个目的地址,需要经过出接口caliad71f050dc2
,该网卡对应的就是目的pod的eth0的 veth peer
由此可得: 在calico ipip模式的同节点pod通信的场景下: 一个数据包的传递需要经过两次L3路由的转发。(源pod 内查询路由表,获取封包数据例如目的mac地址,信息齐全之后,数据包转发到rootNs,然后根据路由表,转发到目的pod)
对比Flannel 同节点pod的通信,借助的是Linux Bridge 二层交换。
对于数据包的传递,二层交换机和三层路由分别有什么优缺点呢? 二层场景下,交换机需要根据数据包的目的地址进行泛洪(广播),会对交换机上的所有端口进行广播寻址,各个端口根据广播的内容进行回复,如果目的地址和自身匹配,就得回复广播。
三层会隔离广播,请求的目的地址是固定的,限制在指定的广播域中,其他设备不会收到广播。
通常来说基于硬件的转发效率是高于软件的,一般交换由硬件实现,路由 软件实现。虚拟化环境下,交换路由都是虚拟出来的,二者的效率相差不大。
- pod ping 测 169.254.1.1
登录pod,ping 测路由的网关地址: 可以发现 ping 不同该地址,但是arping 又可以解析出mac地址,为什么是 ee:ee:ee:ee:ee:ee ?
同时登录宿主机查询ip地址 169.254.1.1,也并未发现该地址,该地址是哪里来的呢?
抓包看一下相关信息: 发现目的Mac地址也确实是ee:ee:ee:ee:ee:ee
四: 分析169.254.1.1 从哪而来
参考:https://docs.tigera.io/calico/latest/reference/faq
-
为什么在node上看不到169.254.1.1这个IP地址?
Calico 为了避免干扰主机上的任何其他配置。 Calico 没有将网关地址169.254.1.1添加到每个pod实例对应的宿主机的网卡上,而是在接口上设置proxy_arp
标志。这使得主机的行为就像网关一样,响应 169.254.1.1 的 ARP,而无需实际将 IP 地址分配给接口。 -
关于全e的mac地址解释
- 由于内核无法生成永久 MAC 地址(设备重启后MAC地址会发生改变,增加了系统维护的复杂性),因此 Calico 会自行分配 MAC 地址。
- 由于 Calico 使用点对点路由接口(意思是说 userNS 中的pod eth0网卡 <==> rootNS calixxx 网卡,pod中出来的包,只有对应的calixx网卡才能收到),流量不会到达数据链路层,因此 MAC 地址永远不会被使用,因此对于所有 cali* 接口来说可以是相同的。(其实就是为了封包使用)。
Mac地址在三层转发的时候,只是本地有效。如果没有经过nat,数据包的ip地址不会发生改变,MAC地址会在每一跳中发生变化。
五、 延伸扩展
5.1 为什么Calico使用ProxyARP
基于上述实验,即同节点上的pod相互通信。我们重新思考数据包传输这个问题:
当一个数据包的目的地址不是本机,所以需要查询路由表,当查到路由表中的网关之后,需要获取网关的MAC地址,并将数据包的MAC地址修改成网关地址,然后发送到对应的网卡。
那么问题来了: 在容器里的网关是169.254.1.1,那网关的MAC地址是什么?
正常情况下,内核会对外发送ARP请求,去询问整个二层网络中谁拥有169.254.1.1这个IP地址,拥有这个IP地址的设备会将自己的MAC返回。但是现在的情况是,对于容器和主机,都没有169.254.1.1这个IP,甚至,在主机上的端口calixxx网卡,MAC地址也是一个无用的ee:ee:ee:ee:ee:ee。
所以,如果仅仅是目前的状况,容器和主机网络根本就无法通信!
我们在Calico的官方文档中,找到答案:
Why can't I see the 169.254.1.1 address mentioned above on my host?
Calico tries hard to avoid interfering with any other configuration on the host. Rather than adding the gateway address to the host side of each workload interface, Calico sets the proxy_arp flag on the interface. This makes the host behave like a gateway, responding to ARPs for 169.254.1.1 without having to actually allocate the IP address to the interface.
Calico利用了网卡的proxy_arp功能,具体的,是将/proc/sys/net/ipv4/conf/DEV/proxy_arp置为1,当设置这个标志之后,DEV设备就会看起来像一个网关,会响应所有的ARP请求,并将自己的MAC地址告诉客户端。
也就是说,当容器发送ARP请求时,主机DEV设备会告诉容器,我拥有169.254.1.1这个IP,我的MAC地址是XXX,这样,容器就可以顺利的将数据包发出来了,于是网络就通了。
查看calixxx网卡,可以发现确实启用了网卡的proxy_arp的功能
5.2 手动实现ProxyARP功能
例如要实现一个 自定义ns1中设备通过proxyARP访问外网,我们分解下步骤:
- 创建vethpair
主要是想打通userNS和rootNS通道ip netns add ns1 ip link add veth type veth peer name c-eth0 ip link set veth up ip link set c-eth0 netns ns1 ip netns exec ns1 ip link set c-eth0 up ip netns exec ns1 ip address add 1.1.1.2/24 dev c-eth0
-
创建路由表
ip netns exec ns1 ip route add 169.254.1.1 dev c-eth0 scope link ip netns exec ns1 ip route add default via 169.254.1.1 dev c-eth0
路由表的添加顺序一定不要错,否则添加失败。需要先告诉网关地址怎么走,然后才能添加默认路由
-
rootNS中对应的veth pair设备开启proxyarp
1
表示成功开启proxyARP
可以在userNS中 arping 169.254.1.1 看看回复的MAC地址是不是veth网卡的
查看rootNS中的veth网卡MAC地址:
完全一致
-
如果想在userNS 中访问rootNS中的网卡
需要在rootNS中添加回城路由,如果不添加,数据包传输出来就没法回去了。
ip route add 1.1.1.0/24 dev veth scope link
-
如果需要访问外网则要配置snat
iptables -t nat -A POSTROUTING -s 1.1.1.0/24 -j MASQUERADE
5.3 备注
在手动添加路由的时候: ip route add 169.254.1.1 dev c-eth0 scope link
其中一个重要参数是"scope"(作用域),它指定适用于路由表条目的网络范围。该参数可以设置为数值或者从"/etc/iproute2/rt_scopes"文件中获取的字符串。下面是几个常见的作用域类型
scope global:全局范围,适用于所有通过网关进行路由的单播流量。
scope link:链路范围,适用于直接相连的子网或广播域上的单播和广播数据包。
scope host:主机范围,适用于本地计算机上的本地接口流量。
如果路由表条目未指定作用域,则默认情况下,iproute2工具将使用以下作用域:
对于通过网关进行路由的单播流量,默认使用全局作用域(global)。
对于直接相连的子网或广播域上的流量,默认使用链路作用域(link)。
对于本地计算机上的本地接口流量,默认使用主机作用域(host)。
- 点赞
- 收藏
- 关注作者
评论(0)