kubeovn nat-gw 功能分析

52 阅读7分钟

参考: kube-ovn 功能

创建 VPC 网关

配置外部网络

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 来连接租户网络。
  • lanIpsubnet 内某个未被使用的 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 是如何实现的

2cc93fc538ab310a8048541083ef5750.png

e50a88032752f538ae468db96ad5a915.png

代码非常简单,如果为 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 地址。

主要组件

  1. 配置结构 (NetConf): - Master: 父接口名称(如果未指定,自动选择默认路由接口) - Mode: macvlan 模式(bridge/private/vepa/passthru) - MTU: 最大传输单元 - Mac: MAC 地址(可选) - LinkContNs: 父接口是否在容器内 - BcQueueLen: 广播队列长度

  2. 核心流程 (cmdAdd):

  3. 第295行 判断是否为 Layer 3 模式: isLayer3 := n.IPAM.Type != ""

  4. 第303-306行 创建 macvlan 接口(无论是否有 IPAM)

  5. 第323-363行 如果配置了 IPAM(Layer 3 模式): - 调用 IPAM 插件获取 IP 配置 - 检查 IPAM 是否返回了 IP - 将 IP 和路由信息保存到 result - 调用 ipam.ConfigureIface 配置接口

第364-380行 如果没有 IPAM(Layer 2 模式): - 只将接口状态设置为 UP - 不配置任何 IP 和路由

回答你的问题

  1. 如果 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 函数。

  1. 如果 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 插件只做这些事:

  1. 创建 macvlan 网络接口(有独立的 MAC 地址)
  2. 设置接口状态为 UP
  3. 不配置 IP 地址
  4. 不配置路由

这时容器的网络接口就像一个纯二层设备,可以:

  • 发送和接收以太网帧
  • 参与二层广播
  • 使用其他方式获取 IP(比如 DHCP)
  • 或者根本不需要 IP,只做二层桥接

使用场景:

容器可以通过 DHCP 获取 IP

或者用于纯二层网络,如桥接模式

或者后续手动配置 IP

Layer 3 模式(有 IPAM)

当配置了 IPAM 时,插件会:

  1. 创建 macvlan 网络接口
  2. 调用 IPAM 插件获取 IP 地址和路由
  3. 配置 IP 地址到接口(通过 ipam.ConfigureIface)
  4. 配置路由表
  5. 设置接口为 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"
  }

工作流程

  1. macvlan 检测到 IPAM: isLayer3 := n.IPAM.Type != "" // "kube-ovn" != "" => true
  2. 调用 kube-ovn IPAM(macvlan.go:325): r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) // 实际执行: /opt/cni/bin/kube-ovn < stdin配置
  3. kube-ovn 返回 IP 配置: - 从 kube-ovn-daemon(通过 socket)获取 IP 地址 - 根据 provider: "ovn-vpc-external-network.kube-system" 确定使用哪个网络 - 返回分配的 IP、Gateway、Routes 等信息
  4. 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 返回了什么路由,可以:

  1. 查看容器内的路由: kubectl exec -it -- ip route
  2. 查看 kube-ovn Subnet 配置: kubectl get subnet ovn-vpc-external-network -n kube-system -o yaml
  3. 查看是否配置了 gateway、gatewayType 等字段
  4. 查看 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 决定。