github blog:https://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/
下即可