前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于K8s的SR-IOV网络实践

基于K8s的SR-IOV网络实践

作者头像
zouyee
发布2022-05-25 10:07:33
3.5K1
发布2022-05-25 10:07:33
举报
文章被收录于专栏:Kubernetes GO

在传统的虚拟化中,虚拟机的网卡通常是通过桥接(Bridge或OVS)的方式,因为这种方式最方便,也最简单,但是这样做最大的问题在于性能。本文讲的SR-IOV在2010年左右由Intel提出,但是随着容器技术的推广,intel官方也给出了SR-IOV技术在容器中使用的开源组件,例如:sriov-cni和sriov-device-plugin等,所以SR-IOV也开始在容器领域得到的大量使用。

文|周宇

编辑|zouyee

技术深度|适中

SR-IOV简介

谈及智能制造,首先就需要从企业需求角度出发。制造业企业最关心的是质量能不能更好?成本能不能再低一些?怎么让交付更快?说到底是制造业本身对规模效应的追求,特别是在当今市场需求愈发多样化、个性化,企业需要具备更强的柔性制造能力和产品设计创新能力。智能制造正是要回应企业对规模效应和柔性化制造这两方面的诉求。

SR-IOV由来

在传统的虚拟化中,虚拟机的网卡通常是通过桥接(Bridge或OVS)的方式,因为这种方式最方便,也最简单,但是这样做最大的问题在于性能。本文讲的SR-IOV在2010年左右由Intel提出,SR-IOV全称Single-Root I/O Virtualization,是一种基于硬件的虚拟化解决方案,它允许多个云主机高效共享PCIe设备,且同时获得与物理设备性能媲美的I/O性能,能有效提高性能和可伸缩性。可适用于网络NFV、云游戏、视频流(UDP)等对网络性能、传输速度要求极高的应用场景,比如在游戏场景中,当宿主机的CPU压力比较大时,虚拟机内部网卡的发包率(PPS)性能会下降,极端情况会出现丢包。这点对于游戏业务来说,最直观的感受是游戏卡顿、玩家掉线,这在游戏业务中是绝对不允许的。虽然sriov最初提出时是面向虚拟机应用,但是随着容器技术的推广,intel官方也给出了SR-IOV技术在容器中使用的开源组件,例如:sriov-cni和sriov-device-plugin等,所以SR-IOV也开始在容器领域得到的大量使用。

SR-IOV技数主要是虚拟出来通道给用户使用的,通道分为两种:

  • PF(Physical Function,物理功能):管理 PCIe 设备在物理层面的通道功能,可以看作是一个完整的 PCIe 设备,包含了 SR-IOV 的功能结构,具有管理、配置 VF 的功能。
  • VF(Virtual Function,虚拟功能):是 PCIe 设备在虚拟层面的通道功能,即仅仅包含了 I/O 功能,VF 之间共享物理资源。VF 是一种裁剪版的 PCIe 设备,仅允许配置其自身的资源,虚拟机无法通过 VF 对 SR-IOV 网卡进行管理。所有的 VF 都是通过 PF 衍生而来,有些型号的 SR-IOV 网卡最多可以生成 256 个 VF。在性能方面,硬件化虚拟网卡VF,一般能达到原生网卡的一半性能。

关于PF和VF的关系描述:每个VF就像是物理网卡硬件资源的一个切片,而PF则是对所有物理网卡硬件资源的统筹者,包括管理众多 VF 可以协同工作。

对于SR-IOV支持的网卡(NIC),每个VF的MAC和IP地址可独立配置,VF之间的数据包切换在设备硬件中进行,pod中使用vf设备作为网卡设备使用。在 Kubernetes pod 中使用 SR-IOV 网络设备的优势包括:

  • 与网卡设备的直接通信有助于向“裸机”性能靠近,也就是说容器可以直接访问物理网卡设备
  • 支持用户空间中的多个快速网络数据包处理同步工作负载(例如,基于DPDK)。由于优越的性能优势,DPDK变得流行起来,越来越多的应用开始基于DPDK开发。传统的容器网络使用VETH作为网络设备,目前无法直接使用DPDK的PMD驱动,所以无法直接在容器内部使用基于DPDK的应用。而在多网卡的方案中,容器使用网络设备,网络设备为常见的E1000 或者 virtio_net设备,这两种设备都有PMD驱动,容器使用这个设备就能够直接运行基于DPDK的应用,能够在一定程度上提升容器内应用的网络性能。
适用场景

a. 游戏场景

网络游戏场景中对实时性要求比较高,使用纯虚拟化出来的网卡或者容器方案的话,首先包中转的设备较多且都是纯软件虚拟化出来的网络设备,无法保证低丢包率和低时延。而SR-IOV硬件化虚拟网卡VF,一般能达到原生网卡的一半性能,所以游戏场景会使用到SR-IOV。

b. 流量隔离场景

比如在金融行业中,需要对数据库每天进行备份,而容器内部只有一张网卡,因此,可能出现在备份过程中,出现一个很大的网络流量,直接把网卡带宽占满,造成数据库业务的中断。同时,在k8s集群中,有很多功能,需要通过容器原生的Overlay网络完成,如监控自动纳管、服务自动发现等功能。所以,在这类场景中,会使用容器多网卡方案,将流量隔离,监控流量和备份用一张网卡,业务流量用一张网卡。当然对性能要求高的,就得使用SR-IOV的那张网卡。

SR-IOV局限性

使用SR-IOV直通方案的优点是最优的网络性能,但是由于SR_IOV技术的硬件虚拟化特性,一张万兆网卡上的虚拟网卡(VF)数量是有限的,一般为63个,一些高端网卡是126个,也就是说一台机器上使用SR-IOV网卡的pod数量有限,所以得省着点用!解决方案:

  • 一般业务,不使用SR-IOV网卡;
  • 对于网络性能有一定要求,但要求不是很高的,例如低配版数据库实例,可以使用macvlan或者ipvlan的网卡(之前的文章有例子);
  • 对网络性能要求较高的业务实(例如高配版数据库实例)例才可使用SR-IOV网卡。
SR-IOV设备数据包分发机制

其实,从逻辑上可以认为启用了 SR-IOV 技术后的物理网卡内置了一个特别的 Switch,将所有的 PF 和 VF 端口连接起来,通过 VF 和 PF 的 MAC 地址以及 VLAN ID 来进行数据包分发。

  • 在 Ingress 上(从外部进入网卡):如果数据包的目的MAC地址和VLANID都匹配某一个VF,那么数据包会分发到该VF,否则数据包会进入PF;如果数据包的目的MAC地址是广播地址,那么数据包会在同一个 VLAN 内广播,所有 VLAN ID 一致的 VF 都会收到该数据包。
  • 在 Egress 上(从 PF 或者 VF发出):如果数据包的MAC地址不匹配同一VLAN内的任何端口(VF或PF),那么数据包会向网卡外部转发,否则会直接在内部转发给对应的端口;如果数据包的 MAC 地址为广播地址,那么数据包会在同一个 VLAN 内以及向网卡外部广播。

注意:所有未设置 VLAN ID 的 VF 和 PF,可以认为是在同一个 LAN 中,不带 VLAN 的数据包在该 LAN 中按照上述规则进行处理。此外,设置了 VLAN 的 VF,发出数据包时,会自动给数据包加上 VLAN,在接收到数据包时,可以设置是否由硬件剥离 VLAN 头部。

SR-IOV设备与容器网络

英特尔推出了 SR-IOV CNI 插件,支持 Kubernetes pod 在两种模式任意之一的条件下直接连接 SR-IOV 虚拟功能 (VF)。第一个模式在容器主机核心中使用标准 SR-IOV VF 驱动程序。第二个模式支持在用户空间执行 VF 驱动程序和网络协议的 DPDK VNF。本文介绍的是第一个模式,直接连接SR-IOV虚拟功能(vf设备),如下图所示:

上图中包含了一个node节点上使用的组件:kubelet、sriov-device-plugin、sriov-cni和multus-cni。节点上的vf设备需要提前生成,然后由sriov-device-plugin将vf设备发布到k8s集群中。在pod创建的时候,由kubelet调用multus-cni,multus-cni分别调用默认cni和sriov-cni插件为pod构建网络环境。sriov-cni就是将主机上的vf设备添加进容器的网络命名空间中并配置ip地址。

SR-IOV网络实践

1. SR-IOV设备使用

查找宿主机上是否有支持SR-IOV的智能网卡

代码语言:javascript
复制
# 插件所有的pci设备
[root@xxx ~]# lspci -nn | grep Eth
1a:00.2 Ethernet controller [0200]: Intel Corporation Ethernet Connection X722 for 1GbE [8086:37d1] (rev 09)
1a:00.3 Ethernet controller [0200]: Intel Corporation Ethernet Connection X722 for 1GbE [8086:37d1] (rev 09)
3b:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
3b:00.1 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
3d:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
3d:00.1 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
86:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
86:00.1 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
af:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
af:00.1 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
# 查看设备1a:00.2具备的能力
[root@WXBY01-IRONIC-ZTE02 ~]# lspci -s 1a:00.2  -vvv | grep Capabilities
  Capabilities: [40] Power Management version 3
  Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
  Capabilities: [70] MSI-X: Enable+ Count=129 Masked-
  Capabilities: [a0] Express (v2) Endpoint, MSI 00
  Capabilities: [e0] Vital Product Data
  Capabilities: [100 v2] Advanced Error Reporting
  Capabilities: [150 v1] Alternative Routing-ID Interpretation (ARI)
  Capabilities: [160 v1] Single Root I/O Virtualization (SR-IOV)     !!!!!有这行说明网卡支持SRIOV
  Capabilities: [1a0 v1] Transaction Processing Hints
  Capabilities: [1b0 v1] Access Control Services

2. sriov-device-plugin使用

sriov-device-plugin原理

SR-IOV设备的pf资源和vf资源需要发布到k8s集群中以供pod使用,所以这边需要用到device-plugin,device-plugin的pod是用daemonset部署的,运行在每个node节点上,节点上的kubelet服务会通过grpc方式调用device-plugin里的ListAndWatch接口获取节点上的所有SR-IOV设备device信息,device-plugin也会通过register方法向kubelet注册自己的服务,当kubelet需要为pod分配SR-IOV设备时,会调用device-plugin的Allocate方法,传入deviceId,获取设备的详细信息。

sriov-device-plugin的部署

a. 新建configmap

后面部署的daemonset需要使用configmap,主要是用于筛选节点上的SR-IOV的vf设备。resourceList里面装的每一个对象都是用于搜索节点上的vf设备的筛选条件,比如selectors.vendors是厂商号,selectors.devices是设备号,selectors.drivers是驱动信息,device-plugin的pod就会在本地查询满足这几个信息的设备,kubelet会调ListAndWatch接口获取这些设备信息

代码语言:javascript
复制
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", "1889"],
                    "drivers": ["i40evf", "iavf", "ixgbevf"]
                }
            },
            {
                "resourceName": "intel_sriov_dpdk",
                "selectors": {
                    "vendors": ["8086"],
                    "devices": ["154c", "10ed", "1889"],
                    "drivers": ["vfio-pci"],
                    "pfNames": ["enp0s0f0","enp2s2f1"]
                }
            },
            {
                "resourceName": "mlnx_sriov_rdma",
                "selectors": {
                    "vendors": ["15b3"],
                    "devices": ["1018"],
                    "drivers": ["mlx5_ib"],
                    "isRdma": true
                }
            }
        ]
    }

b. 确定设备的厂商号、设备号和驱动信息

选择一张SR-IOV设备,生成vf设备:

代码语言:javascript
复制
[root@xxx ~]# echo 4 > /sys/bus/pci/devices/0000:1a:00.2/sriov_numvfs

查看厂商号和设备号是8086和37cd

代码语言:javascript
复制
[root@xxx ~]# lspci -n | grep 1b:0a.0
1b:0a.0 0200: 8086:37cd (rev 09)

查看vf设备的驱动信息是i40evf

代码语言:javascript
复制
[root@xxx ~]# ls -l /sys/class/pci_bus/0000\:1b/device/0000:1b:0a.0 | grep driver
lrwxrwxrwx 1 root root      0 4月  24 17:01 driver -> ../../../../../../bus/pci/drivers/i40evf
-rw-r--r-- 1 root root   4096 4月  24 17:56 driver_override
c. 新建daemonset
代码语言:javascript
复制
wget https://raw.githubusercontent.com/k8snetworkplumbingwg/sriov-network-device-plugin/master/deployments/k8s-v1.16/sriovdp-daemonset.yaml
kubectl apply -f sriovdp-daemonset.yaml

过三分钟查看node对象是否存在sriov的资源:

代码语言:javascript
复制
kubectl describe node zhounanjun

Allocatable:
  cpu:                              47
  ephemeral-storage:                320296325k
  hugepages-1Gi:                    0
  hugepages-2Mi:                    0
  intel.com/intel_sriov_netdevice:  4   ## 有这项说明插件生效了
  memory:                           390116500Ki
  pods:

3. sriov-cni使用

SR-IOV设备在k8s中专门用作pod的网卡,所以intel也提供了sriov-cni插件,该插件满足k8s cni规范,可以作为cni插件使用,为pod配置网络环境。

部署sriov-cni:

代码语言:javascript
复制
wget https://raw.githubusercontent.com/k8snetworkplumbingwg/sriov-cni/master/images/k8s-v1.16/sriov-cni-daemonset.yaml
kubectl apply -f sriov-cni-daemonset.yaml

sriov-cni主要做的事情:

  • 首先sriov-cni部署后,会在/opt/cni/bin目录下放一个sriov的可执行文件。
  • 然后,当kubelet会调用multus-cni插件,然后multus-cni插件里会调用delegates数组里的插件,delegates数组中会有SR-IOV信息,然后通过执行/opt/cni/bin/sriov命令为容器构建网络环境,这边构建的网络环境的工作有:
  • 根据kubelet分配的sriov设备id找到设备,并将其添加到容器的网络命名空间中
  • 为该设备添加ip地址

4. network-attachment-definition使用

需要定义network-attachment-definition对象才能使用sriov设备:

代码语言:javascript
复制
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net2
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice
spec:
  config: '{
  "cniVersion": "0.3.1",
  "name": "sriov-network",
  "plugins": [
        {
            "type": "sriov",
            "ipam": {
                "type": "host-local", # 使用host-local作为ipam插件
                "subnet": "192.168.133.0/24", # 设置子网
                "rangeStart": "192.168.133.150",# 设置可分配的ip段
                "rangeEnd": "192.168.133.189",
                "routes": [
                    { "dst": "0.0.0.0/0" }
                ],
                "gateway": "192.168.133.254" 
            }
        },
        {
            "type": "sbr" #设置sbr
        }
   ]
}'

5. 创建测试pod

代码语言:javascript
复制
apiVersion: v1
kind: Pod
metadata:
  name: pod5
  labels:
    environment: production
    app: MyApp
  annotations:
    k8s.v1.cni.cncf.io/networks: '[
            { "name" : "sriov-net2",
              "interface": "sriovnet2" }
    ]'
spec:
  containers:
  - name: appcntr1
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command:
    - tail
    - -f
    - /dev/null
    resources:
      requests:
        intel.com/intel_sriov_netdevice: '1'
      limits:
        intel.com/intel_sriov_netdevice: '1'

进入pod查看网络:

代码语言:javascript
复制
[root@xxx]# kubectl get po 
NAME   READY   STATUS             RESTARTS   AGE
pod5   1/1     Running            0          35h
[root@xxx]# kubectl exec -ti pod5 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
310: eth0@if311: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1300 qdisc noqueue 
    link/ether 00:00:00:27:9e:92 brd ff:ff:ff:ff:ff:ff
    inet 10.222.0.32/16 brd 10.222.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fb11:10:16::12/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::200:ff:fe27:9e92/64 scope link 
       valid_lft forever preferred_lft forever
66: sriovnet2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
    link/ether 42:7a:dc:a8:ff:9f brd ff:ff:ff:ff:ff:ff
    inet 192.168.133.150/24 brd 192.168.133.255 scope global sriovnet2
       valid_lft forever preferred_lft forever
    inet6 fe80::407a:dcff:fea8:ff9f/64 scope link

可以看到pod中有三张网卡:lo,eth0和sriovnet2。其中sriobnet2网卡是sriov网络的网卡。

SR-IOV优化实践

优化ipam

上面我们在创建nad对象时,ipam插件使用的是host-local,host-local的话,只能做单节点的ip分配,当时用SR-IOV设备的pod分布在不同的节点上时,节点上的host-local会给予nad中的subnet进行分配,不会和其他节点上的host-local沟通,这会导致不同节点上的pod的SR-IOV网卡的ip地址存在冲突的可能。这个问题的根源是缺少集中式ip地址分配的机制。k8snetworkplumbingwg社区提供了whereabouts插件是专门解决这个问题,whereabouts插件就是一个集中式的ip地址管理插件。

a. whereabouts部署和使用

代码语言:javascript
复制
git clone https://github.com/k8snetworkplumbingwg/whereabouts && cd whereabouts
kubectl apply \
    -f doc/crds/daemonset-install.yaml \
    -f doc/crds/whereabouts.cni.cncf.io_ippools.yaml \
    -f doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml \
    -f doc/crds/ip-reconciler-job.yaml

部署完成后,可以在/opt/cni/bin目录下看到whereabouts的可执行文件,这个可执行文件是在sriov-cni中需要给容器分配ip时被调用。

使用whereabouts作为ip地址分配插件:

代码语言:javascript
复制
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net2
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice
spec:
  config: '{
    "cniVersion":"0.3.1",
    "name":"nsnetwork-sample",
    "plugins":[
        {
            "ipam":{
                "gateway":"192.168.6.254",
                "range":"192.168.6.0/24",
                "type":"whereabouts" # 使用whereabouts
            },
            "type":"sriov"
        },
        {
            "type":"sbr"
        }
    ]
}'

b. whereabouts说明

  • 在使用whereabouts时,会传入ipam的配置,然后获取集群中查找以192.168.6.0/24命名的ippool对象,若是没有,就创建ippools对象
  • whereabouts分配IP时,使用了领导者选举机制来获取分布式锁,然后获取ippool对象,ippool对象中会存在着已使用ip和对应的pod,然后从剩下的可分配ip中找一个作为分配结果,分配完成后更新ippool对象中已使用的ip地址列表。

c. 遇到的问题

若是nad对象中定义的子网信息和宿主机不在一个网段,那么不同主机上的pod是无法通信的,所以若是想pod做跨主机通信,必须将子网信息设置成宿主机网段,需要在使用前规划好网段。

由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流。

周宇:云原生研发工程师,有多年容器及K8s产品架构设计开发经历,具有丰富的harbor、容器网络等方向研发经验。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DCOS 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SR-IOV简介
  • SR-IOV网络实践
  • SR-IOV优化实践
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档