美文网首页
k8s cni bridge

k8s cni bridge

作者: shoyu666 | 来源:发表于2022-03-17 22:01 被阅读0次

bridge 是 cni一个比较重要的实现,基本原理就是 linux的bridge, bridge 源码比较短,本文对 bridge 源码做分析。

目标效果

配置的网络图如下


image.png

代码入口

//plugins/main/bridge/bridge.go
func main() {
    skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
}

main函数是标准的CNI插件开局,这里只分析cmdAdd

func cmdAdd(args *skel.CmdArgs) error {
        //加载配置
    n, cniVersion, err := loadNetConf(args.StdinData, args.Args)
    isLayer3 := n.IPAM.Type != ""
        //添加bridge
        br, brInterface, err := setupBridge(n)
       //添加veth对
    hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.mac)
       //调用IPAM  插件
       if isLayer3 {
        // run the IPAM plugin and get back the config to apply
        r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
     }
       //收集网关信息
    gwsV4, gwsV6, err := calcGateways(result, n)
//为容器配置ip和mac地址
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
                return err
            }
 //最后输出日志
return types.PrintResult(result, cniVersion)
  }
加载配置
type NetConf struct {
    types.NetConf
    BrName       string `json:"bridge"`
    IsGW         bool   `json:"isGateway"`
    IsDefaultGW  bool   `json:"isDefaultGateway"`
    ForceAddress bool   `json:"forceAddress"`
    IPMasq       bool   `json:"ipMasq"`
    MTU          int    `json:"mtu"`
    HairpinMode  bool   `json:"hairpinMode"`
    PromiscMode  bool   `json:"promiscMode"`
    Vlan         int    `json:"vlan"`
    MacSpoofChk  bool   `json:"macspoofchk,omitempty"`
    EnableDad    bool   `json:"enabledad,omitempty"`

    Args struct {
        Cni BridgeArgs `json:"cni,omitempty"`
    } `json:"args,omitempty"`
    RuntimeConfig struct {
        Mac string `json:"mac,omitempty"`
    } `json:"runtimeConfig,omitempty"`

    mac string
}

配置的struct 是 NetConf

{
    "name": "mynet",
    "type": "bridge",
    "mtu": 1472,
    "ipMasq": false,
    "isGateway": true,
    "ipam": {
        "type": "host-local",
        "subnet": "203.0.113.0/24"
    }
}

传入的配置示例,当 ipam 指定时 isLayer3 时true,会调用IPAM 插件

添加bridge

func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
    vlanFiltering := false
    if n.Vlan != 0 {
        vlanFiltering = true
    }
    // create bridge if necessary
    br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode, vlanFiltering)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
    }

    return br, &current.Interface{
        Name: br.Attrs().Name,
        Mac:  br.Attrs().HardwareAddr.String(),
    }, nil
}

调用的是 ensureBridge

func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
    br := &netlink.Bridge{
        LinkAttrs: netlink.LinkAttrs{
            Name: brName,
            MTU:  mtu,
            // Let kernel use default txqueuelen; leaving it unset
            // means 0, and a zero-length TX queue messes up FIFO
            // traffic shapers which use TX queue length as the
            // default packet limit
            TxQLen: -1,
        },
    }
    if vlanFiltering {
        br.VlanFiltering = &vlanFiltering
    }

    err := netlink.LinkAdd(br)
    if err != nil && err != syscall.EEXIST {
        return nil, fmt.Errorf("could not add %q: %v", brName, err)
    }

    if promiscMode {
        if err := netlink.SetPromiscOn(br); err != nil {
            return nil, fmt.Errorf("could not set promiscuous mode on %q: %v", brName, err)
        }
    }

    // Re-fetch link to read all attributes and if it already existed,
    // ensure it's really a bridge with similar configuration
    br, err = bridgeByName(brName)
    if err != nil {
        return nil, err
    }

    // we want to own the routes for this interface
    _, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", brName), "0")

    if err := netlink.LinkSetUp(br); err != nil {
        return nil, err
    }

    return br, nil
}

ensureBridge 通过系统调用 LinkAdd 添加bridge,添加后 bridgeByName 重新查询出brige,确保存在。
accept_ra 0 关闭路由建议,最后 LinkSetUp 启用 bridge设备,返回mac地址。

添加Veth对(linux虚拟网络设备)

func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, mac string) (*current.Interface, *current.Interface, error) {
    contIface := &current.Interface{}
    hostIface := &current.Interface{}

    err := netns.Do(func(hostNS ns.NetNS) error {
        // create the veth pair in the container and move host end into host netns
        hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, mac, hostNS)
        if err != nil {
            return err
        }
        contIface.Name = containerVeth.Name
        contIface.Mac = containerVeth.HardwareAddr.String()
        contIface.Sandbox = netns.Path()
        hostIface.Name = hostVeth.Name
        return nil
    })
    if err != nil {
        return nil, nil, err
    }

    // need to lookup hostVeth again as its index has changed during ns move
    hostVeth, err := netlink.LinkByName(hostIface.Name)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to lookup %q: %v", hostIface.Name, err)
    }
    hostIface.Mac = hostVeth.Attrs().HardwareAddr.String()

    // connect host veth end to the bridge
    if err := netlink.LinkSetMaster(hostVeth, br); err != nil {
        return nil, nil, fmt.Errorf("failed to connect %q to bridge %v: %v", hostVeth.Attrs().Name, br.Attrs().Name, err)
    }

    // set hairpin mode
    if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
        return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
    }
      ....
}

1:hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, mac, hostNS)
创建Veth对,一端用于host(hostVeth),一端用于容器(containerVeth)

2:netlink.LinkSetMaster(hostVeth, br)
将hostVeth挂在前面创建的bridge上

3:netlink.LinkSetHairpin(hostVeth, hairpinMode)
bridge的某个端口打开hairpin mode后允许从这个端口收到的包仍然从这个端口发出

调用IPAM 插件

r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)

以ipam 的实现 host-local为例,host-local在 subnet 地址范围内分配ip,分配的ip记录到本地文件。
返回的结果包含 ip4,ip6

//返回结果示例
{
    "ips": [
        {
            "version": "4",
            "address": "203.0.113.2/24",
            "gateway": "203.0.113.1"
        },
        {
            "version": "6",
            "address": "2001:db8:1::2/64",
            "gateway": "2001:db8:1::1"
        }
    ],
}

收集网关信息

gwsV4, gwsV6, err := calcGateways(result, n)
gwsV4:203.0.113.1
gwsV6:2001:db8:1::1

为容器设置ip

func ConfigureIface(ifName string, res *current.Result) error {
        for _, ipc := range res.IPs {
            addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
        if err = netlink.AddrAdd(link, addr); err != nil {
            return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
        }

    if err := netlink.LinkSetUp(link); err != nil {
        return fmt.Errorf("failed to set %q UP: %v", ifName, err)
    }
}

通过 netlink.AddrAdd 系统调用设置ip和LinkSetUp 启用ip

为bridge设置ip和mac地址

//err = ensureAddr(br, gws.family, &gw, n.ForceAddress)

func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool) error {
addr := &netlink.Addr{IPNet: ipn, Label: ""}
    if err := netlink.AddrAdd(br, addr); err != nil && err != syscall.EEXIST {
        return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
    }

    // Set the bridge's MAC to itself. Otherwise, the bridge will take the
    // lowest-numbered mac on the bridge, and will change as ifs churn
    if err := netlink.LinkSetHardwareAddr(br, br.Attrs().HardwareAddr); err != nil {
        return fmt.Errorf("could not set bridge's mac: %v", err)
    }

通过AddrAdd 设置ip,通过 LinkSetHardwareAddr 设置mac地址为自身的mac地址(作者时说不这样做的话,mac地址会变化,变化原因未知)
ip地址就是前面gateway的地址。

输出日志

return types.PrintResult(result, cniVersion)

最后会将配置的结果输出日志

相关文章

  • k8s cni bridge

    bridge 是 cni一个比较重要的实现,基本原理就是 linux的bridge, bridge 源码[http...

  • k8s cni

    背景 提到k8s网络就不得不提 CNI(容器网络接口),本文主要是说下CNI是什么,以及在哪里被调用,不对CNI插...

  • K8S 网络插件对比

    集群网络架构是 K8s 中比较复杂的,最让用户头痛的方面之一。K8s 拥有众多的 CNI 插件,该如何做好CNI的...

  • k8s之ovs-cni

    ovs-cni是由kubevirt提供的一种k8s cni, 用于将pod接口长在ovs网桥上面,其原理为:创建一...

  • k8s flannel host-gw

    接 k8s flannel cni plugin[https://www.jianshu.com/p/d1917a...

  • 查看CNI中的veth pair

    flannel是k8s的pod网络之一,cni0是配置flannel时会出现的典型网桥Cni0:网桥设备,每创建一...

  • k8s 网络三

    k8s网络包括网络模型、CNI、Service、Ingress、DNS。k8s的容器网络关注两点:IP地址分配,路...

  • 压测iptables规则条数的限制

    前言 今天在使用k8s的时候,由于运用到了calico这个cni插件和k8s自带的NetworkPolicy的功能...

  • k8s Flannel网络方案

    k8s中规定了CNI接口,但是没有标准化网络方案,网络方案也是网络基础,在这个基础上k8s提供了service负载...

  • k8s cni

    https://github.com/containernetworking/cni/blob/master/pk...

网友评论

      本文标题:k8s cni bridge

      本文链接:https://www.haomeiwen.com/subject/isovdrtx.html