device-plugin 扩展——intel-sriov-device-plugin解读

二进制诗人
• 阅读 5997

学习目的

了解sriov网卡的正确读取姿势,学习相关的golang开源库

配置文件

看这部分代码前我们先看看官方提供的一个配置文件范例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: sriovdp-config
  namespace: kube-system
data:
  config.json: |
    {
        "resourceList": [{
                "resourceName": "intel_sriov_netdevice",
                "selectors": {
                    "vendors": ["8086"], 
                    "devices": ["154c", "10ed"],
                    "drivers": ["i40evf", "ixgbevf"]
                }
            },
            {
                "resourceName": "intel_sriov_dpdk",
                "selectors": {
                    "vendors": ["8086"],
                    "devices": ["154c", "10ed"],
                    "drivers": ["vfio-pci"],
                    "pfNames": ["enp0s0f0","enp2s2f1"]
                }
            },
            {
                "resourceName": "mlnx_sriov_rdma",
                "isRdma": true,
                "selectors": {
                    "vendors": ["15b3"],
                    "devices": ["1018"],
                    "drivers": ["mlx5_ib"]
                }
            },
            {
                "resourceName": "mlnx_sriov_rdma",
                "isRdma": true,
                "selectors": {
                    "pfNames": ["enp0s0f0#1,3,5-9,23","enp2s2f1"],
                    "vendors": ["15b3"],
                    "devices": ["1018"],
                    "drivers": ["mlx5_ib"]
                }
            }
        ]
    }

这里selectors是用来过滤机器上的pci网络设备的,只要当前check的网卡的属性包含于配置文件里对应属性的数组,就会被选入。

vendors表示厂商号,devices表示设备号,drivers表示驱动,pfNames表示vf所在pf的网卡名。

比如某个网卡的bdf是0000:00:11.4,我们可以通过lspci -n |grep 00:11.4得到他的信息:

lspci  -n |grep '00:11.4'     
00:11.4 0106: 8086:8d62 (rev 05)

8086就是厂商号,8d62就是设备号。

值得一提的是,这里selectors中的pfNames是一个规则表达式。可以简单地用来作为pf网卡名的过滤器,也可以通过这个字段指定pf上哪些vf要注册为k8s资源。

pfNames是一个字符串数组,他的每一个字符串元素都按照如下的规则:

"<PFName>#<SingleVF>,<FirstVF>-<LastVF>,<SingleVF>,<SingleVF>,<FirstVF>-<LastVF>"
`<PFName>`   - is the PF interface name
`<SingleVF>` - is a single VF index (0-based) that is included into the pool
`<FirstVF>`  - is the first VF index (0-based) that is included into the range
`<LastVF>`   - is the last VF index (0-based) that is included into the range

比如我们机器上sriov网卡有两块pf,分别名为eth0,eth1,每个pf有32个vf,其中前两个vf要预留作为其他网络的设备,那么可以写成:

"pfNames": ["eth0#2-31","eth1#2-31"]

入口和参数

intel/sriov-network-device-plugin的程序入口在cmd/sriovdp/main.go

提供了两个基本的参数:

  1. config-file。指定配置参数
  2. resource-prefix。指定要注册到apiserver中的硬件资源的前缀,比如intel.comnetease.com

main函数中调用了四个主要函数:

第一步:newResourceManager

resourceManager是deviceplugin的核心逻辑组件,初始化一个空的resourceManager,并依次调用readConfig,validConfigs, 注入配置。配置由ResourceConfig数组构成,包括:

type ResourceConfig struct {
    ResourceName string `json:"resourceName"` // the resource name will be added with resource prefix in K8s api
    IsRdma       bool   // the resource support rdma
    Selectors    struct {
        Vendors   []string `json:"vendors,omitempty"`
        Devices   []string `json:"devices,omitempty"`
        Drivers   []string `json:"drivers,omitempty"`
        PfNames   []string `json:"pfNames,omitempty"`
        LinkTypes []string `json:"linkTypes,omitempty"`
    } `json:"selectors,omitempty"` // Whether devices have SRIOV virtual function capabilities or not
}

第二步:discoverHostDevices

intel引用了github.com/jaypipes/ghw,将机器上的PCI设备全部list一遍,并判断和逐步过滤,最后归纳出所有vf设备,记录的数据类型是types.PciNetDevice

pci的设备必须是网络设备 -> 机器上必须没有该网卡对应的default路由 -> 是sriov的vf设备  -> 加入到resourceManager.netDeviceList

第三步:initServers

在上面我们提到的config-file中,我们可以定义多组配置,每组是一个resourceName,它会对应一个device-plugin的服务。对于每一组配置,做的事情如下:

  1. 生成资源池resourcePool。他们将之前在配置文件中写的多个Selectors应用到resourceManager.netDeviceList中(每个selector的filter逻辑见pkg/resources/deviceSelectors.go),得到我们想要的类型的vf设备,并还能根据配置文件过滤出rdma设备。我们可以看到在resourcePool的结构体中包含了一个devicePool,这里是真正存储设备的地方。
  2. 初始化一个resourceServer,按照我们的配置,生成一个resourceServer,它包括真正的资源池resourcePool,grpc Server,resourcePrefix等:

    return &resourceServer{
         resourcePool:       rp,
         pluginWatch:        pluginWatch,
         endPoint:           sockName,
         sockPath:           sockPath,
         resourceNamePrefix: prefix,
         grpcServer:         grpc.NewServer(),
         termSignal:         make(chan bool, 1),
         updateSignal:       make(chan bool),
         stopWatcher:        make(chan bool),
         checkIntervals:     20, // updates every 20 seconds
     }

第四步:startAllServers

执行resourceServer的Start方法,运行device-plugin。device-plugin会先向kubelet发送一个Register。kubelet收到后会启动一个grpc客户端,与resourceServer中的grpcServer建立连接。

kubelet会远程调用ListAndWatch,并在需要申请资源时调用Allocate

配置文件自定义

intel官方提供的配置文件并不足以满足所有的sriov设备,比如当我们使用Mellanox 5系列的硬件时,用这套configmap是没法识别出vf的。按照上问的说明,我们可以在机器上使用lspci确定该sriov网卡的vf的vendorID、deviceID,driver, 通过添加或修改configmap内容,实现对该硬件的采集。如:

# lspci  -n |grep 04:05.1
04:05.1 0200: 15b3:1016
# ls -l /sys/class/pci_bus/0000:04/device/0000:04:05.1  |grep driver
lrwxrwxrwx 1 root root       0 Nov 20 11:09 driver -> ../../../../bus/pci/drivers/mlx5_core
-rw-r--r-- 1 root root    4096 Nov 20 11:09 driver_override

## change configmap yaml file:
...
            {
                "resourceName": "mlnx_sriov_rdma",
                "isRdma": true,
                "selectors": {
                    "vendors": ["15b3"],
                    "devices": ["1016"],
                    "drivers": ["mlx5_core"]
                }
            }
            ...

device-plugin的工作内容

ListAndWatch

resourcePool中的所有设备组成数组返回给kubelet。然后开始监听退出信号和更新信号。一旦发生设备更新,会刷新将设备数组返回给kubelet

kubelet会在内存中记录设备数组。

Allocate

kubelet会记录那些容器用了哪些设备,并找到一个可以用的设备,向device-plugin申请该设备。resourceServer收到请求后,将使用该设备需要设置的docker option封装返回。options包括:

  1. Devices
  2. Envs
  3. Mounts

从上面的分析我们知道,resourcePool中记录的资源,归根结底·是types.PciNetDevice资源。所以我们还是要从github.com\intel\sriov-network-device-plugin\pkg\resources\pciNetDevice.go去找“到底返回给kubelet的数据是怎样的”。

  1. 我们看到func (nd *pciNetDevice) GetDeviceSpecs()func (nd *pciNetDevice) GetMounts()返回的都是资源的结构体字段,在func NewPciNetDevice 中我们又看到这些字段的赋值是按照不同驱动区分的:

    func NewPciNetDevice(pciDevice *ghw.PCIDevice, rFactory types.ResourceFactory) (types.PciNetDevice, error) {
     pciAddr := pciDevice.Address
     driverName, err := utils.GetDriverName(pciAddr)
     ...
     // 3. Get Device file info (e.g., uio, vfio specific)
     // Get DeviceInfoProvider using device driver
     infoProvider := rFactory.GetInfoProvider(driverName)
     dSpecs := infoProvider.GetDeviceSpecs(pciAddr)
     mnt := infoProvider.GetMounts(pciAddr)
     env := infoProvider.GetEnvVal(pciAddr)
     ...
    }

factory.go中,我们看到:

func (rf *resourceFactory) GetInfoProvider(name string) types.DeviceInfoProvider {
    switch name {
    case "vfio-pci":
        return newVfioResourcePool()
    case "uio", "igb_uio":
        return newUioResourcePool()
    default:
        return newNetDevicePool()
    }
}

意思很清楚了,不同的pciDevice有不同的driver,通过读取其driver,我们可以确认这些vf的厂家和型号。从而组织成对应的docker options

这里intel提供的可兼容driver包括:

  1. vfio-pci。见pkg/resources/vfioPool.go
  2. uio或igb_uio。见pkg/resources/uioPool.go
  3. 其他。见pkg/resources/netDevicePool.go(可以由用户自己扩展,只要实现GetDeviceSpecs,GetEnvVal,GetMounts就可以用)

一个Allocate的response案例如下:

AllocateResponse send: &AllocateResponse{ContainerResponses:[]*ContainerAllocateResponse{&ContainerAllocateResponse{Envs:map[string]string{PCIDEVICE_NETWORK_NETEASE_COM_SRIOV_VF: 0000:06:0c.1,},Mounts:[]*Mount{},Devices:[]*DeviceSpec{&DeviceSpec{ContainerPath:/dev/infiniband/ucm97,HostPath:/dev/infiniband/ucm97,Permissions:rwm,},&DeviceSpec{ContainerPath:/dev/infiniband/issm97,HostPath:/dev/infiniband/issm97,Permissions:rwm,},&DeviceSpec{ContainerPath:/dev/infiniband/umad97,HostPath:/dev/infiniband/umad97,Permissions:rwm,},&DeviceSpec{ContainerPath:/dev/infiniband/uverbs97,HostPath:/dev/infiniband/uverbs97,Permissions:rwm,},&DeviceSpec{ContainerPath:/dev/infiniband/rdma_cm,HostPath:/dev/infiniband/rdma_cm,Permissions:rwm,},},Annotations:map[string]string{},},},}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
分布式id如何生成
1.UUID生成通过网卡、时间、随机数来保证生成的唯一的字符串。优点:(1)本地生成,生成简单(2)速度快(3)高可用;缺点:(1)无序,如果存入mysq,影响B的操作性能,因为B树是需要排序的;(2)占用空间较大(36个
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
3年前
Linux查看GPU信息和使用情况
1、Linux查看显卡信息:lspci|grepivga2、使用nvidiaGPU可以:lspci|grepinvidia!(https://oscimg.oschina.net/oscnet/36e7c7382fa9fe49068e7e5f8825bc67a17.png)前边的序号"00:0f.0"是显卡的代
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
ClickHouse性能提升
本文经作者授权,独家转载:作者主页:https://www.jianshu.com/u/8f36a5e63d181\.不要用select\反例:select  from app.user_model正例:select login_id,name,sex from app.user_mo
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Github标星5300+,专门为程序员开发文档开源管理系统,我粉了
!(https://oscimg.oschina.net/oscnet/a11909a041dac65b1a36b2ae8b9bcc5c432.jpg)码农那点事儿关注我们,一起学习进步!(https://oscimg.oschina.net/oscnet/f4cce1b7389cb00baaab228e455da78d0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Nginx反向代理upstream模块介绍
!(https://oscimg.oschina.net/oscnet/1e67c46e359a4d6c8f36b590a372961f.gif)!(https://oscimg.oschina.net/oscnet/819eda5e7de54c23b54b04cfc00d3206.jpg)1.Nginx反
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(