先说结论
做了 ARP 宣告。因此,用了 macvlan L2 层网络插件的 K8s 网络,不需要担心 IPAM 分配策略不佳的问题:同一个 IP,短时间内,回收又分配出去。
为什么要分析?
首先,macvlan 是 K8s CNI 的一个网络插件,它主要是用来配置 underlay 大 L2 层网络。其实,真正的 L2 层网络,从 OSI 网络架构来看,是数据链路层,通信靠 MAC 地址。
我们知道,凡是依赖 L2 层网络的主机,在物理层用 MAC ,靠的是交换机那一侧转发表。然而,从容器网络来看,通信靠的 IP 地址。因此,容器环境中,存在自己的 ARP 表。ARP 存储的是 IP 和 MAC 的映射关系。
那么问题来了:
- 如果 2 个 服务 A 和 B 通信,我们假设 A->B 的本质,是 a1 容器访问 b1 容器。
- a1 上,关于 b1 的 ARP 关系为:[b1-ip : b1-mac]。
- 现在,B 服务重新发布了,b1 容器变为了 b2 容器。假如:b1 销毁释放了 b1 的 IP,b2 创建,重新拿到了之前的 b1 的 IP。
- 现象就是:A->B ,成了 a1->b2,且,a1 调用 b2 的 IP 还没变。
- 问题:a1 原来存的 ARP 表为:[b1-ip : b1-mac] ,a1 调用 b2 还用原来 IP,只要 a1 ARP 表没更新,a1->b2 网络一定不通!!!
当然,这个问题,并不限于 macvlan 插件,只有在容器与宿主机在同一个 L2 层网络,都会面临这个问题。
那么,这个问题,应该怎么解?有 2 种方式:
- IPAM 分配策略做优化,IP 被回收后,放回 IP 池,下次不会立即分配,而是有序分配。目的是:保证 IP 分配策略周期 【大于】在 ARP 缓存时间周期。
- 容器网络在配置的时候,重新做 ARP 宣告。这样一来,上面的问题,即便 b2 拿到了原来 b1 的 IP,但重新宣告后,a1 容器的 ARP 表关系就从 [b1-ip : b1-mac] 变为了 [b1-ip : b2-mac]。这样一来,网络就通了。
其实这个问题,和 LVS VIP 切换之后,重新做 ARP 宣告,道理是一样的。
特别注意:
如果只从 IPAM 上,做 IP 池有序分配,其实也无法完全避免问题。因为,ARP 表中记录到期是存在一个策略的,这个策略可能导致 ARP 记录不更新:
Linux 为了性能考虑,缓存策略比较复杂,其中一个就是,如果收到来其他主机发阿里的来自 MAC 地址的网络通信包,那么 ARP 就会认为这个 MAC 地址还“健在”,进而,继续延后对这个 ARP 记录的更新,因此会导致其 ARP 这个记录一直无法更新,一直持有错误的 IP->MAC 的映射关系。
下面,我们会做以下几个事情:
- 模拟 Kubernetes CNI 环境(省的装 K8s了)
- 模拟容器网络,并用 cnitool 模拟 CNI 流程,为容器网络做配置
- macvlan 插件源码分析
- tcpdump 抓包的确有 ARP 宣告。
模拟 Kubernetes 的 CNI 环境
CNI 插件的测试过程,不需要一定安装一个 K8s 出来,走 K8s CNI 流程来测试。CNI 官方 repo 中,提供了 cnitool 工具来测试 CNI 的插件
1、创建 Linux 虚拟机(cnitool、macvlan、bridge 等网络插件,需要 Linux 环境)
2、安装 Golang 环境(方便安装 cnitool)
1 | // 安装 Golang yum repo |
3、配置 Golang 环境
1 | // 配置 GOPATH |
1 | vim ~/.bash_profile |
4、下载 CNI 项目
1 | mkdir -p /root/golang/src/github.com/containernetworking |
5、安装 cnitool
1 | go install github.com/containernetworking/cni/cnitool |
基于 Golang 特性,安装之后,实际上是按照到了 /root/golang/bin 目录,而这个目录,我们在之前,已经添加到了 $PATH 中。
6、配置 CNI 配置文件(基于 macvlan)
1 | // 创建 cni 目录 |
添加如下内容:
1 | { |
特别说明几个点:
- CNI 配置文件的文件名是 10-cnitest.conf ,CNI 配置内容中的 name 也是 cnitest ,这俩要对应。
- macvlan 是虚拟化网络。它的特性就是,需要一个父接口,我们的虚拟上网卡是 ens33(有些人可能是 eth0),所以,CNI 配置文件中的 master 指定的就是父接口,配置文件中的 type 得写为 macvlan。
- ipam 部分就是网络分配部分了。包括了:ip range 范围、网关、路由表。这个得和你的虚拟机适配。我的虚拟机网关是 192.168.20.2 所以我的 IPAM 也得这样配置。
模拟容器网络,并用 cnitool 模拟 CNI 流程,为容器网络做配置
1、创建空白虚拟网络
首先得创建一个空白的容器网络空间
1 | ip netns add testing |
2、执行 cnitool 配置这个空白网络空间
1 | cnitool add cnitest /var/run/netns/testing |
①:cnitool add cnitest 这个 cnitest 指的是刚才配置的 CNI 配置文件:/etc/cni/net.d/10-cnitest.conf 中定义的 cni 名称 cnitest,上面提过这一点。
②:/var/run/netns/testing 这个是我们之前创建的空白网络空间。
3、执行完上面的命令后,如果输出类似下面的内容,就是成功了
1 | [root@localhost bin] |
4、验证之前创建的空白容器网络 testing 是否已填充好网络信息
1 | // 进入容器网络 |
5、验证 IPAM 也分配了 IP
IPAM 我们用的 host-local,host-local 插件,默认会把分配的 IP 信息,放到下面目录
1 | /var/lib/cni/networks/cnitest |
注意:因为我们的 cni 名称是 cnitest,所以会放到 /var/lib/cni/networks/ 的子目录 cnitest 下。如果 cni 名称是 cni0,则会放到 /var/lib/cni/networks/cni0 下。
1 | [root@localhost cnitest] |
6、清理 cnitool 配置的容器网络(可选)
1 | cnitool del cnitest /var/run/netns/testing |
分析 macvlan 的 ARP 宣告
1、CNI 流程简述
其实 CNI 只有 2 个调用流程
- 创建 Pod 的时候调用 ——> 一般对应 CNI 插件的 cmdAdd 方法
- 销毁 Pod 的时候调用 ——> 一般对应 CNI 插件的 cmdDel 方法
CNI 创建的流程中,其实也会经过 3 个流程:
- 通过具体的插件(比如 macvlan 网络插件),为空白的容器网络空间,配置网络。
- 配置的过程中,从 IPAM 插件,获取一个【网络信息】,这个网络信息包括:IP、路由表等信息。
- 从 IPAM 拿到网络信息后,继续为容器网络做相关配置。
注意:CNI 的流程是,先为 pause 容器创建网络空间并做一些简单配置,然后才调用 IPAM 获取更完整的网络配置信息,进一步做网络配置。取 IPAM 信息,并非 CNI 的第一个步骤。
2、 CNI 插件 macvlan 源码分析
直接上源码:
1 | ```c |
抓包验证使用了 macvlan CNI 插件,新创建容器网络是否有 ARP 报文
1、在虚拟机外(mac 主机)抓包
1 | sudo tcpdump -i any -nn arp |
2、在虚拟机内,使用 cnitool 为 testing 虚拟网络空间配置网络
1 | // 为了防止之前的配置有影响,先移除 cnitool 配置的虚拟网络 |
执行后结果:
1 | [root@localhost ~] |
3、主机抓包结果:
结果符合 macvlan 源码行为,的确做了 ARP 宣告。
4、查看 mac 主机的 ARP 表
1 | ➜ ~ arp -a |grep 192.168.20.212 |
ARP 表存在此记录,且 mac 地址符合 cni 结果。