Kubernetes插件:Intel sriov-cni插件簡介/修改

github bloghttps://xftony.github.io

sriov-cni簡介

sriov-cni是hustcat/sriov-cni開發的一種容器網絡插件(Container Network Interface),它使得容器可以直接使用物理機中擴展出來的VF(virtual functions)。Intel在此基礎上,爲其添加了dpdk功能。本文介紹的sriov-cni的版本爲Intel版,修改也是基於Intel版本進行的修改。

sriov-cni代碼簡介

sriov-cni主要通過調用netlink包將vf修改到容器的namespace下,使得VF可以被容器直接調用。其中VF是指支持SRIOV的物理網卡所虛擬出的一個“網卡”或者說虛出來的一個實例,它會以一個獨立網卡的形式呈現出來,每一個VF有它自己獨享的PCI配置區域,並且可能與其他VF共享着同一個物理資源(公用同一個物理網口)
根據cni的標準定義兩個函數cmdAdd和cmdDel,分別用於VF的添加和刪除skel.PluginMain(cmdAdd, cmdDel)
cmdAdd和cmdDel基本是一對反過程,所以這裏就只簡單介紹一下cmdAdd函數。

 func cmdAdd(args *skel.CmdArgs) error {
    n, err := loadConf(args.StdinData) // 將輸入參數轉化爲NetConf變量n
    if err != nil {
        return fmt.Errorf("failed to load netconf: %v", err)
    }
    // 獲取container的net命名空間,args.Netns是container的在host中的網絡命名空間路徑
    netns, err := ns.GetNS(args.Netns)
    if err != nil {
        return fmt.Errorf("failed to open netns %q: %v", netns, err)
    }
    defer netns.Close()

    if n.IF0NAME != "" {
        args.IfName = n.IF0NAME
    }
    // 核心函數,配置VF,將VF移動到container的命名空間
    if err = setupVF(n, n.IF0, args.IfName, args.ContainerID, netns); err != nil {
        return fmt.Errorf("failed to set up pod interface %q from the device %q: %v", args.IfName, n.IF0, err)
    }

    // 在DPDK 和L2模式下, 無需調用IPAM進行IP分配
    var result *types.Result
    if n.DPDKMode != false || n.L2Mode != false {
        return result.Print()
    }

    // 調用IPAM插件,並返回配置
    result, err = ipam.ExecAdd(n.IPAM.Type, args.StdinData)
    if err != nil {
        return fmt.Errorf("failed to set up IPAM plugin type %q from the device %q: %v", n.IPAM.Type, n.IF0, err)
    }
    if result.IP4 == nil {
        return errors.New("IPAM plugin returned missing IPv4 config")
    }

    err = netns.Do(func(_ ns.NetNS) error {
        return ipam.ConfigureIface(args.IfName, result)
    })
    if err != nil {
        return err
    }

    result.DNS = n.DNS
    //返回分配結果
    return result.Print()
}

下面簡單介紹一下sriov-cni的核心函數setupVF(),其基本實現步驟是,通過if0找到可用的VF,並設置爲up,然後將VF移動到container的網絡命名空間,重命名爲ifName,即container內部看到的網卡名稱

func setupVF(conf *NetConf, ifName string, podifName string, cid string, netns ns.NetNS) error {
    var vfIdx int
    var infos []os.FileInfo
    var pciAddr string
    //通過ifName獲取if0,即m
    m, err := netlink.LinkByName(ifName)
    if err != nil {
        return fmt.Errorf("failed to lookup master %q: %v", conf.IF0, err)
    }

    // 通過讀取/sys/class/net/<if0>/device/sriov_numvfs 文件獲取VF個數
    vfTotal, err := getsriovNumfs(ifName)
    if err != nil {
        return err
    }

    if vfTotal <= 0 {
        return fmt.Errorf("no virtual function in the device %q: %v", ifName)
    }
    //遍歷PF目錄下的VF,找到一個滿足條件的VF
    for vf := 0; vf <= (vfTotal - 1); vf++ {
        vfDir := fmt.Sprintf("/sys/class/net/%s/device/virtfn%d/net", ifName, vf)
        if _, err := os.Lstat(vfDir); err != nil {
            if vf == (vfTotal - 1) {
                return fmt.Errorf("failed to open the virtfn%d dir of the device %q: %v", vf, ifName, err)
            }
            continue
        }

        infos, err = ioutil.ReadDir(vfDir)
        if err != nil {
            return fmt.Errorf("failed to read the virtfn%d dir of the device %q: %v", vf, ifName, err)
        }

        if (len(infos) == 0) && (vf == (vfTotal - 1)) {
            return fmt.Errorf("no Virtual function exist in directory %s, last vf is virtfn%d", vfDir, vf)
        }

        if (len(infos) == 0) && (vf != (vfTotal - 1)) {
            continue
        }

        if len(infos) == maxSharedVf {
            conf.Sharedvf = true
        }

        if len(infos) <= maxSharedVf {
            vfIdx = vf
            //獲取PCI地址, host上“/sys/class/net/<if0>/device/<VF>”
            pciAddr, err = getpciaddress(ifName, vfIdx)
            if err != nil {
                return fmt.Errorf("err in getting pci address - %q", err)
            }
            break
        } else {
            return fmt.Errorf("mutiple network devices in directory %s", vfDir)
        }
    }

    // VF NIC name
    if len(infos) != 1 && len(infos) != maxSharedVf {
        return fmt.Errorf("no virutal network resources avaiable for the %q", conf.IF0)
    }

    if conf.Sharedvf != false && conf.L2Mode != true {
        return fmt.Errorf("l2enable mode must be true to use shared net interface %q", conf.IF0)
    }

    if conf.Vlan != 0 {
        if err = netlink.LinkSetVfVlan(m, vfIdx, conf.Vlan); err != nil {
            return fmt.Errorf("failed to set vf %d vlan: %v", vfIdx, err)
        }

        if conf.Sharedvf {
            if err = setSharedVfVlan(ifName, vfIdx, conf.Vlan); err != nil {
                return fmt.Errorf("failed to set shared vf %d vlan: %v", vfIdx, err)
            }
        }
    }
    //如果是dpdk模式,爲DPDKConf結構體賦值,
    if conf.DPDKMode != false {
        conf.DPDKConf.PCIaddr = pciAddr
        conf.DPDKConf.Ifname = podifName
        conf.DPDKConf.VFID = vfIdx
        //配置文件以 containID-If0name 方式命名, 將其保存在conf.CNIDir目錄下,即配置文件中cniDir,默認“/var/lib/cni/sriov”
        //注意在k8s中這裏的containID是pod中pod-container的id,而不是pod內真正執行服務的container的id。
        if err = savedpdkConf(cid, conf.CNIDir, conf); err != nil {
            return err
        }
        //調用dpdk_tool腳本,更新VF驅動,劃重點
        return enabledpdkmode(&conf.DPDKConf, infos[0].Name(), true)
    }

    // Sort links name if there are 2 or more PF links found for a VF;
    if len(infos) > 1 {
        // sort Links FileInfo by their Link indices
        sort.Sort(LinksByIndex(infos))
    }

    for i := 1; i <= len(infos); i++ {
        vfDev, err := netlink.LinkByName(infos[i-1].Name())
        if err != nil {
            return fmt.Errorf("failed to lookup vf device %q: %v", infos[i-1].Name(), err)
        }
        //調用netlink庫函數實現set link up, 類似`ip link set $link up`
        if err = netlink.LinkSetUp(vfDev); err != nil {
            return fmt.Errorf("failed to setup vf %d device: %v", vfIdx, err)
        }

        // 將VF移動到container的命名空間,類似`ip link set $link netns $ns`
        if err = netlink.LinkSetNsFd(vfDev, int(netns.Fd())); err != nil {
            return fmt.Errorf("failed to move vf %d to netns: %v", vfIdx, err)
        }
    }

    return netns.Do(func(_ ns.NetNS) error {

        ifName := podifName
        for i := 1; i <= len(infos); i++ {
            if len(infos) == maxSharedVf && i == len(infos) {
                ifName = podifName + fmt.Sprintf("d%d", i-1)
            }
            //將該VF重命名爲ifName
            err := renameLink(infos[i-1].Name(), ifName)
            if err != nil {
                return fmt.Errorf("failed to rename %d vf of the device %q to %q: %v", vfIdx, infos[i-1].Name(), ifName, err)
            }

            // for L2 mode enable the pod net interface
            if conf.L2Mode != false {
                err = setUpLink(ifName)
                if err != nil {
                    return fmt.Errorf("failed to set up the pod interface name %q: %v", ifName, err)
                }
            }
        }
        return nil
    })
}

sriov修改版介紹

sriov實現了container中調用host VF的功能,在使用時也發現了一切不足之處(或許是我瞭解不足導致的誤解,如是,請指正),例如:

1、sriov-cni在保存dpdk配置的後,若dpdk驅動更新可能失敗,但是配置不會被刪除;
2、在k8s下使用,其保存dpdk配置的時候,使用的是pod-container-id(k8s會爲每個pod創建一個container以提供相應網絡服務等),對於container無法識別其被分配到的VF,因爲container可以看到所有的包括分配給其他container的VF;(這裏它目的可能只是爲了保存刪除時需要的信息,但是container內存在需要使用其dpdk信息的場景,所以可以修改一下)
3、sriov-cni進保存dpdk模式的配置信息,對於普通的sriov不會進行配置信息的保存;
4、不支持直接使用PF。

對應修改:

1、保存dpdk配置文件和開啓dpdk驅動的代碼順序對調,保證在啓動更新成功的情況下才會保存配置文件;
2、配置文件路徑添加一級容器網絡命名空間ID,從而使得容器內可以識別自身的配置文件;
3、添加netconf的配置保存;
4、類似VF,添加setupPF(),實現與setupVF()基本一致。

針對上述的“問題”,我做了一個修改版的,親測可用。 自提
把這個/bin/sriov文件放到/opt/cni/bin/下即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章