参考: kube-ovn 功能
配置外部网络¶
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: ovn-vpc-external-network
spec:
protocol: IPv4
provider: ovn-vpc-external-network.kube-system
cidrBlock: 192.168.0.0/24
gateway: 192.168.0.1 # IP address of the physical gateway
excludeIps:
- 192.168.0.1..192.168.0.10
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ovn-vpc-external-network
namespace: kube-system
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "kube-ovn",
"server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
"provider": "ovn-vpc-external-network.kube-system"
}
}'
- 该 Subnet 用来管理可用的外部地址,网段内的地址将会通过 Macvlan 分配给 VPC 网关,请和网络管理员沟通给出可用的物理段 IP。
kind: VpcNatGateway
apiVersion: kubeovn.io/v1
metadata:
name: gw1
spec:
vpc: test-vpc-1
subnet: net1
lanIp: 10.0.1.254
selector:
- "kubernetes.io/hostname: kube-ovn-worker"
- "kubernetes.io/os: linux"
externalSubnets:
- ovn-vpc-external-network
noDefaultEIP: false
vpc:该 VpcNatGateway 所属的 VPC。subnet:为 VPC 内某个 Subnet 名,VPC 网关 Pod 会在该子网下用lanIp来连接租户网络。lanIp:subnet内某个未被使用的 IP,VPC 网关 Pod 最终会使用该 IP。当 VPC 配置路由需要指向当前 VpcNatGateway 时nextHopIP需要设置为这个lanIp。selector:VpcNatGateway Pod 的节点选择器,格式和 Kubernetes 中的 NodeSelector 格式相同。externalSubnets:VPC 网关使用的外部网络,如果不配置则默认使用ovn-vpc-external-network,当前版本只支持配置一个外部网络。noDefaultEIP:VPC 网关的附属网卡是否绑定默认 EIP,默认为false兼容 v1.15 前行为模式,在非 BGP 网关模式下推荐设置为true来节省 Underlay 地址。
noDefaultEIP 是如何实现的
代码非常简单,如果为 false,就设置路由,如果为 true 就没有路由。
没有路由,就是 cni add 的时候不需要配置路由。
/Users/ftwhmg/g/plugins/plugins/main/macvlan/macvlan.go 详细分析一下这个 cni, 如果 ipam 没有给这个 cni 路由,它还会在 cniAdd 的时候配置上路由么? 如果没有 ip 会报错么? 还是没有 ip 么?
⏺ 我来详细分析这个 macvlan CNI 插件的实现。
⏺ Read(plugins/main/macvlan/macvlan.go) ⎿ Read 591 lines
⏺ 现在让我详细分析一下这个 macvlan CNI 插件,并回答你的两个问题。
Macvlan CNI 插件详细分析
核心功能
这是一个标准的 CNI 插件,用于创建 macvlan 网络接口。Macvlan 允许在单个物理网卡上创建多个虚拟网卡,每个都有独立的 MAC 地址。
主要组件
-
配置结构 (NetConf): - Master: 父接口名称(如果未指定,自动选择默认路由接口) - Mode: macvlan 模式(bridge/private/vepa/passthru) - MTU: 最大传输单元 - Mac: MAC 地址(可选) - LinkContNs: 父接口是否在容器内 - BcQueueLen: 广播队列长度
-
核心流程 (cmdAdd):
-
第295行 判断是否为 Layer 3 模式: isLayer3 := n.IPAM.Type != ""
-
第303-306行 创建 macvlan 接口(无论是否有 IPAM)
-
第323-363行 如果配置了 IPAM(Layer 3 模式): - 调用 IPAM 插件获取 IP 配置 - 检查 IPAM 是否返回了 IP - 将 IP 和路由信息保存到 result - 调用 ipam.ConfigureIface 配置接口
第364-380行 如果没有 IPAM(Layer 2 模式): - 只将接口状态设置为 UP - 不配置任何 IP 和路由
回答你的问题
- 如果 IPAM 没有给路由,还会配置路由吗?
不会。关键代码在 macvlan.go:348:
result.Routes = ipamResult.Routes
然后在 macvlan.go:359 调用:
return ipam.ConfigureIface(args.IfName, result)
路由完全来自 IPAM 插件的返回结果。如果 ipamResult.Routes 为空,那么 result.Routes 也为空,ConfigureIface 就不会配置任何路由。
macvlan 插件本身不会主动添加任何路由,它只是将 IPAM 返回的路由信息传递给 ipam.ConfigureIface 函数。
- 如果 IPAM 没有返回 IP,还会配置 IP 吗?
不会,而且会报错。关键代码在 macvlan.go:343-345:
if len(ipamResult.IPs) == 0 { return errors.New("IPAM plugin returned missing IP config") }
如果 IPAM 插件配置了(n.IPAM.Type != "")但没有返回 IP 地址,cmdAdd 会直接返回错误:"IPAM plugin returned missing IP config"。
总结:
- 如果没有配置 IPAM(n.IPAM.Type == ""),插件工作在 Layer 2 模式,只创建接口并设置 UP 状态,不配置 IP 和路由
- 如果配置了 IPAM,则必须返回至少一个 IP 地址,否则报错
- 路由完全依赖 IPAM 返回,macvlan 插件不会自己添加任何路由
- IP 配置也完全依赖 IPAM 返回,macvlan 插件自己不生成 IP
所有的 IP 和路由配置都是通过 ipam.ConfigureIface(args.IfName, result) 函数完成的,这个函数位于 pkg/ipam 包中。
如果没有配置 IPAM(n.IPAM.Type == ""),插件工作在 Layer 2 模式,只创建接口并设置 UP 状态,不配置 IP 和路由, Layer 2 模式 是指什么?
⏺ 很好的问题!让我解释一下 Layer 2 和 Layer 3 的区别,以及在这个 macvlan CNI 插件中的含义。
OSI 网络模型分层
Layer 2(数据链路层):
- 工作在 MAC 地址层面
- 负责同一局域网内的设备通信
- 使用以太网帧(Ethernet Frame)传输数据
- 典型设备:交换机(Switch)
- 协议:Ethernet、ARP
Layer 3(网络层):
- 工作在 IP 地址层面
- 负责跨网络的路由和通信
- 使用 IP 数据包传输数据
- 典型设备:路由器(Router)
- 协议:IP、ICMP
在 Macvlan CNI 中的含义
Layer 2 模式(无 IPAM)
当没有配置 IPAM 时,macvlan 插件只做这些事:
- 创建 macvlan 网络接口(有独立的 MAC 地址)
- 设置接口状态为 UP
- 不配置 IP 地址
- 不配置路由
这时容器的网络接口就像一个纯二层设备,可以:
- 发送和接收以太网帧
- 参与二层广播
- 使用其他方式获取 IP(比如 DHCP)
- 或者根本不需要 IP,只做二层桥接
使用场景:
容器可以通过 DHCP 获取 IP
或者用于纯二层网络,如桥接模式
或者后续手动配置 IP
Layer 3 模式(有 IPAM)
当配置了 IPAM 时,插件会:
- 创建 macvlan 网络接口
- 调用 IPAM 插件获取 IP 地址和路由
- 配置 IP 地址到接口(通过 ipam.ConfigureIface)
- 配置路由表
- 设置接口为 UP
这时容器的网络接口是一个完整的三层设备,可以直接进行 IP 通信。
代码对比:
// Layer 2 模式 (macvlan.go:364-380)
if isLayer3 {
// ... Layer 3 配置
} else {
// Layer 2 模式:只设置接口 UP
err = netns.Do(func(_ ns.NetNS) error {
macvlanInterfaceLink, err := netlinksafe.LinkByName(args.IfName)
if err != nil {
return fmt.Errorf("failed to find interface name %q: %v", macvlanInterface.Name, err)
}
// 只做这一件事:设置 UP
if err := netlink.LinkSetUp(macvlanInterfaceLink); err != nil {
return fmt.Errorf("failed to set %q UP: %v", args.IfName, err)
}
return nil
})
}
// Layer 3 模式 (macvlan.go:323-363)
if isLayer3 {
// 调用 IPAM 获取 IP 配置
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
// 检查必须有 IP
if len(ipamResult.IPs) == 0 {
return errors.New("IPAM plugin returned missing IP config")
}
// 保存 IP 和路由
result.IPs = ipamResult.IPs
result.Routes = ipamResult.Routes
// 配置 IP 和路由到接口
err = netns.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, result)
})
}
配置示例
Layer 2 配置(无 IPAM):
{
"cniVersion": "1.0.0",
"name": "mynet",
"type": "macvlan",
"master": "eth0",
"mode": "bridge"
}
结果:容器内有 eth0 接口,但没有 IP 地址
Layer 3 配置(有 IPAM):
{
"cniVersion": "1.0.0",
"name": "mynet",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
结果:容器内有 eth0 接口,配置了 10.1.2.x/24 的 IP 地址和默认路由
总结
- Layer 2 模式 = 只有网络接口,没有 IP,像一个交换机端口
- Layer 3 模式 = 有网络接口 + IP 地址 + 路由,可以直接通信
- 区分标志:isLayer3 := n.IPAM.Type != ""(macvlan.go:295)
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ovn-vpc-external-network
namespace: kube-system
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "kube-ovn",
"server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
"provider": "ovn-vpc-external-network.kube-system"
}
}' 我们使用该模式,所以ip 肯定是有的
⏺ 对的!你的理解完全正确。基于这个配置:
ipam: {
"type": "kube-ovn",
"server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
"provider": "ovn-vpc-external-network.kube-system"
}
工作流程
- macvlan 检测到 IPAM: isLayer3 := n.IPAM.Type != "" // "kube-ovn" != "" => true
- 调用 kube-ovn IPAM(macvlan.go:325): r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) // 实际执行: /opt/cni/bin/kube-ovn < stdin配置
- kube-ovn 返回 IP 配置: - 从 kube-ovn-daemon(通过 socket)获取 IP 地址 - 根据 provider: "ovn-vpc-external-network.kube-system" 确定使用哪个网络 - 返回分配的 IP、Gateway、Routes 等信息
- macvlan 检查并配置(macvlan.go:343-359): if len(ipamResult.IPs) == 0 { return errors.New("IPAM plugin returned missing IP config") }
result.IPs = ipamResult.IPs // IP 地址 result.Routes = ipamResult.Routes // 路由(由 kube-ovn 决定)
// 配置到接口 return ipam.ConfigureIface(args.IfName, result)
关键点
✅ IP 肯定有:
- kube-ovn 会从其管理的 Subnet 中分配 IP
- 如果分配失败或没返回 IP,macvlan 会报错并中止
❓ 路由取决于 kube-ovn:
- kube-ovn 返回什么路由,就配置什么路由
- 通常 kube-ovn 会返回:
- 默认路由(如果配置了 gateway)
- Subnet 路由
- 其他自定义路由
🔍 如果你想确认 kube-ovn 返回了什么路由,可以:
- 查看容器内的路由: kubectl exec -it -- ip route
- 查看 kube-ovn Subnet 配置: kubectl get subnet ovn-vpc-external-network -n kube-system -o yaml
- 查看是否配置了 gateway、gatewayType 等字段
- 查看 CNI 日志:
在节点上查看
cat /var/log/pods//kube-ovn-cni//messages
示例流程
假设 kube-ovn 返回:
{
"ips": [{
"address": "192.168.1.10/24",
"gateway": "192.168.1.1"
}],
"routes": [{
"dst": "0.0.0.0/0",
"gw": "192.168.1.1"
}]
}
最终容器内会有:
- IP: 192.168.1.10/24 on eth1
- 路由: default via 192.168.1.1 dev eth1
如果 kube-ovn 只返回 IP 不返回路由:
{
"ips": [{
"address": "192.168.1.10/24"
}],
"routes": []
}
最终容器内会有:
- IP: 192.168.1.10/24 on eth1
- 路由: 无额外路由(只有直连路由 192.168.1.0/24 dev eth1)
总结:在你的场景中,macvlan 工作在 Layer 3 模式,IP 肯定会配置,路由完全由 kube-ovn IPAM 决定。