使用Nvidia加速网络计算进行CNF加速评估的技术预览

创建于2022年8月14日 范围 本文档提供了在K8s集群环境中通过NAT和路由网络应用加速来评估Nvidia加速网络计算技术的背景和指南。

文档目录

创建于2022年8月14日

范围

本文档提供了在K8s集群环境中通过NAT和路由网络应用加速来评估Nvidia加速网络计算技术的背景和指南。

缩写和缩略语

术语 定义 术语 定义
PF 物理功能 DPDK 数据平面开发套件
VF 虚拟功能 CNI 容器网络接口
SRIOV 单根输入输出虚拟化 RDMA 远程直接内存访问
VPP 向量包处理 NIC 网络接口
PMD 轮询模式驱动 DAC 直连铜缆
DUT 被测设备 TG 流量生成器
MPPS 百万包每秒 CNF 云原生网络功能
CRD 自定义资源定义 SDK 软件开发工具包
K8s Kubernetes NAT 网络地址转换
TP 技术预览 SW 软件
VNF 虚拟网络功能 NF 网络功能

参考文献

引言

云计算是现代数据中心(包括电信数据中心)的首选架构。在此架构下,网络功能(NF)作为虚拟化或容器化网络功能(CNF或VNF)运行。

NF必须跟上5G和AI等技术带来的吞吐量、消息速率以及低且可预测的延迟的日益增长的需求。作为云应用,NF高度依赖于服务器的CPU,而CPU是通用型的,并未针对网络应用进行优化。因此,NF可能需要大量CPU核心才能达到预期性能,导致实现效率极低,甚至可能完全无法满足预期性能。

加速网络计算是NVIDIA的一项技术,允许使用位于数据处理单元(DPU)或智能网卡上的专用应用加速器来加速NF。NF的应用逻辑(或控制平面)继续在主机CPU上运行,而NF的数据平面通过一组定义良好的API进行加速,从而实现了前所未有的效率和性能。加速网络计算支持云和云原生环境,并可与K8s等现代编排框架在多租户环境中使用。

本文档演示了如何使用加速网络计算来加速一个示例CNF。该示例CNF实现了一个路由器应用,该应用在k8s云环境中由ConnectX-6 Dx智能网卡加速,并使用通用的标准DPDK API。

我们将把加速CNF的性能和效率与基于流行向量包处理(VPP)框架的同一CNF的软件实现进行比较。

加速CNF

加速网络计算是指使用物理网络加速器(如Nvidia智能网卡)来执行原本由x86 CPU执行的NF数据平面功能。这些数据平面功能包括包分类、包导向、QoS、NAT等。

将CNF功能加速到网卡上,使CNF无需处理数据平面,从而减少CPU资源需求,并为CNF处理其他用例腾出空间。加速功能在智能网卡加速器上的执行效率高于x86 CPU。因此,操作可以以更高的吞吐量、更低的延迟和更少的CPU核心数执行,使运营商能够在同一服务器上加载更多CNF应用,或者使用更少/更便宜的服务器。

下图比较了两种方法。左侧系统在主机CPU上运行应用(仅软件),而右侧系统通过编程ConnectX-6 Dx应用加速器来加速数据平面。

image2021-2-7_10-50-34.png

解决方案架构

关键组件和技术

  • NVIDIA ConnectX SmartNICs 10/25/40/50/100/200和400G以太网网卡 业界领先的NVIDIA® ConnectX®系列智能网卡提供先进的硬件卸载和加速。 NVIDIA以太网适配器为超大规模、公有云和私有云、存储、机器学习、AI、大数据和电信平台提供最高的ROI和最低的总拥有成本。

  • NVIDIA LinkX Cables NVIDIA® LinkX®产品系列的线缆和收发器提供了业界最完整的10、25、40、50、100、200和400GbE以太网以及100、200和400Gb/s InfiniBand产品线,适用于云、HPC、超大规模、企业、电信、存储和人工智能数据中心应用。

  • NVIDIA Spectrum 以太网交换机 灵活的外形规格,支持16到128个物理端口,支持1GbE到400GbE速度。 基于突破性的硅技术,针对性能和可扩展性进行了优化,NVIDIA Spectrum交换机非常适合构建高性能、高性价比的

高效云数据中心网络、以太网存储架构和深度学习互连。

NVIDIA 结合了基于业界领先专用集成电路(ASIC)技术的 NVIDIA Spectrum™ 交换机的优势,以及多种现代网络操作系统选择,包括 NVIDIA Cumulus® LinuxSONiCNVIDIA Onyx®

  • Kubernetes Kubernetes 是一个开源容器编排平台,用于容器化应用的部署自动化、扩缩容和管理。

  • Kubespray Kubespray 由 Ansible 剧本、清单、配置工具和通用操作系统/Kubernetes 集群配置管理任务的领域知识组成,提供:

    • 高可用集群
    • 可组合属性
    • 支持大多数主流 Linux 发行版
  • NVIDIA Network Operator NVIDIA Network Operator 简化了 Kubernetes 集群中 NVIDIA 网络资源的配置和管理。该操作符自动安装所需的主机网络软件,汇集所有必要组件以提供高速网络连接。这些组件包括 NVIDIA 网络驱动、Kubernetes 设备插件、CNI 插件、IP 地址管理(IPAM)插件等。NVIDIA Network Operator 与 NVIDIA GPU Operator 协同工作,为可扩展的 GPU 计算集群提供高吞吐量、低延迟的网络。

  • NVIDIA PMD 轮询模式驱动(PMD)是一个开源上游驱动,嵌入在 dpdk.org 发行版中,通过提供接收和发送的内核旁路,并避免中断处理的性能开销,实现快速数据包处理和低延迟。

  • TRex – 真实流量生成器 TRex 是一个开源、低成本、有状态和无状态的流量生成器,由 DPDK 驱动。它基于预处理和 L7 流量模板的智能重放生成 L4-7 流量。TRex 放大客户端和服务器端流量,可在单个 UCS 上扩展到 200Gb/秒。

服务器角色方案

服务器角色和网络描述:

  • 一个计算层:

    1. 部署节点
    2. K8s Master 节点
    3. K8s Worker 节点,安装有 Nvidia ConnectX-6 DX 网卡,托管 Testpmd/VPP POD
    4. TRex 服务器节点,安装有 Nvidia ConnectX-6 DX 网卡
  • 两个独立的网络层:

    1. 管理 K8s 主网络,1Gb 链路速度
    2. 高速 100Gb 以太网 Nvidia 交换机,用于 DPDK K8s 辅助网络 image2021-1-28_11-6-20.png

软件栈组件

image2021-10-19_10-42-50.png

物料清单

image2021-2-22_12-25-31.png

部署与配置

布线

Trex 和 Worker 节点连接到 100GE 数据平面 K8s 辅助网络。 此外,Worker、Master 和 Deployment 节点连接到 1GE 管理 K8s 主网络。

image2021-10-18_12-31-55.png

主机前提条件

  1. 为 k8s master、worker 和 deployment 节点安装 CentOS 8.4。

  2. 在 worker 节点上安装 OFED >= 5.4。 从 Nvidia 网络 OFED/EN 官方下载网站 下载最新的 Nvidia OFED/EN。 通过以下命令安装所需的库和内核模块:

    Worker 节点控制台

    ./mlnxofedinstall --dpdk
    
  3. K8s worker 节点 BIOS 设置:

    • 将电源节能设置为性能模式
    • 设置 VT-D 标志 - 开启
    • 设置 Turbo-boost - 开启
    • SRIOV - 启用
  4. 查找 NVIDIA 网卡使用的 NUMA 节点(本例中网卡名称为 ens2f0):

    Worker 节点控制台

    cat /sys/class/net/ens2f0/device/numa_node
    

    提取与 NVIDIA 网卡相同 NUMA 节点的核心列表:

    Worker 节点控制台

    lscpu | grep NUMA
    
  5. 编辑 worker 节点 grub 文件以进行 DPDK 性能优化和巨页配置: core-list 包含仅用于 Pod 的专用核心列表。本例将使用核心 4-23。不在列表中的剩余核心可保留给 K8s 系统使用。 使用 isolcpus 内核启动命令行将 CPU 与内核调度器隔离。这将确保用户空间进程不会被内核调度。

    intel_iommu=on iommu=pt default_hugepagesz=1G hugepagesz=1G hugepages=32 isolcpus=<core-list>
    

RDMA 子系统配置

每个 Worker 节点上都需要进行 RDMA 子系统配置。

  1. 设置 PF 接口在启动时链路为“UP”(本例中网卡 ens2f0, ens2f1):

    Worker 节点控制台

    sed -i 's/\(ONBOOT=\)no/\1yes/g' /etc/sysconfig/network-scripts/ifcfg-ens2f0
    sed -i 's/\(ONBOOT=\)no/\1yes/g' /etc/sysconfig/network-scripts/ifcfg-ens2f1
    
  2. 将 RDMA 子系统网络命名空间模式设置为独占模式。 RDMA 子系统网络命名空间模式(ib_core 模块中的 netns 参数)在独占模式下允许 Worker 节点服务器上 RDMA 工作负载的网络命名空间隔离。请创建 /etc/modprobe.d/ib_core.conf 配置文件以更改 ib_core 模块参数:

    Worker 节点控制台

    echo "options ib_core netns_mode=1" > /etc/modprobe.d/ib_core.conf
    

设置RDMA netns模式

在worker节点上,将RDMA netns模式设置为exclusive。

Worker节点控制台

echo "options ib_core netns_mode=0" > /etc/modprobe.d/ib_core.conf
dracut -f

重启worker服务器:

Worker节点控制台

reboot

服务器重启后,检查netns模式:

Worker节点控制台

rdma system

输出应为:"netns exclusive"

在部署服务器上安装Kubespray

部署节点控制台

yum install python3-pip jq git -y
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
pip3 install -r requirements.txt

使用Kubespray部署K8s集群

  1. 设置master和worker节点的本地IP,并为每个节点生成配置文件:

    部署节点控制台

    cp -rfp inventory/sample inventory/mycluster
    declare -a IPS=(192.168.200.3 192.168.200.2)
    CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
    

    验证文件inventory/mycluster/hosts.yaml中的集群信息,必要时进行编辑。 本例中node1为master节点,node2为worker节点。部署文件示例:

    all:
      hosts:
        node1:
          ansible_host: 192.168.200.3
          ip: 192.168.200.3
          access_ip: 192.168.200.3
        node2:
          ansible_host: 192.168.200.2
          ip: 192.168.200.2
          access_ip: 192.168.200.2
      children:
        kube-master:
          hosts:
            node1:
        kube-node:
          hosts:
            node2:
        etcd:
          hosts:
            node1:
        k8s-cluster:
          children:
            kube-master:
            kube-node:
        calico-rr:
          hosts: {}
    
  2. 修改文件inventory/mycluster/group_vars/k8s-cluster/k8s-cluster.yml中的部署参数,设置k8s版本:

    kube_version: v1.21.5
    
  3. 为Kubespray服务器设置无密码SSH认证的RSA密钥:

    部署节点控制台

    ssh-keygen
    

    将RSA密钥复制到master和worker节点:

    部署节点控制台

    ssh-copy-id root@<Node-IP>
    
  4. 安装K8s集群:

    部署节点控制台

    ansible-playbook -i inventory/mycluster/hosts.yaml  -e 'ansible_python_interpreter=/usr/bin/python3' --become --become-user=root cluster.yml
    

    成功安装的输出示例:

    PLAY RECAP *************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    node1                      : ok=551  changed=24   unreachable=0    failed=0    skipped=1142 rescued=0    ignored=0
    node2                      : ok=350  changed=11   unreachable=0    failed=0    skipped=587  rescued=0    ignored=0
    
    Tuesday 24 November 2020  09:31:22 +0200 (0:00:00.029)       0:04:50.249 ******
    ===============================================================================
    kubernetes-apps/ansible : Kubernetes Apps | Lay Down CoreDNS Template ------------------------------------------------------------------------------------------------------------------------------ 14.73s
    container-engine/docker : ensure docker packages are installed ------------------------------------------------------------------------------------------------------------------------------------- 10.24s
    kubernetes/master : Master | wait for kube-scheduler ------------------------------------------------------------------------------------------------------------------------------------------------ 8.52s
    kubernetes-apps/network_plugin/multus : Multus | Start resources ------------------------------------------------------------------------------------------------------------------------------------ 7.91s
    kubernetes-apps/ansible : Kubernetes Apps | Start Resources ----------------------------------------------------------------------------------------------------------------------------------------- 7.76s
    network_plugin/multus : Multus | Copy manifest files ------------------------------------------------------------------------------------------------------------------------------------------------ 4.43s
    network_plugin/calico : Calico | Create calico manifests -------------------------------------------------------------------------------------------------------------------------------------------- 4.37s
    Gather necessary facts ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 4.34s
    kubernetes/preinstall : Get current version of calico cluster version ------------------------------------------------------------------------------------------------------------------------------- 4.17s
    download : check_pull_required |  Generate a list of information about the images on a node --------------------------------------------------------------------------------------------------------- 4.11s
    network_plugin/calico : Get current version of calico cluster version ------------------------------------------------------------------------------------------------------------------------------- 3.88s
    kubernetes/master : kubeadm | Check if apiserver.crt contains all needed SANs ----------------------------------------------------------------------------------------------------------------------- 3.52s
    kubernetes/master : slurp kubeadm certs ------------------------------------------------------------------------------------------------------------------------------------------------------------- 3.27s
    policy_controller/calico : Create calico-kube-controllers manifests --------------------------------------------------------------------------------------------------------------------------------- 2.98s
    network_plugin/cni : CNI | Copy cni plugins --------------------------------------------------------------------------------------------------------------------------------------------------------- 2.92s
    network_plugin/calico : Start Calico resources ------------------------------------------------------------------------------------------------------------------------------------------------------ 2.82s
    kubernetes/master : Backup old certs and keys ------------------------------------------------------------------------------------------------------------------------------------------------------- 2.75s
    kubernetes-apps/cluster_roles : PriorityClass | Create k8s-cluster-critical ------------------------------------------------------------------------------------------------------------------------- 2.60s
    network_plugin/calico : Calico | wait for etcd ------------------------------------------------------------------------------------------------------------------------------------------------------ 2.44s
    policy_controller/calico : Start of Calico kube controllers ----------------------------------------------------------------------------------------------------------------------------------------- 2.23s
    
  5. 为node2设置"worker"标签:

    Master节点控制台

    kubectl label nodes node2 node-role.kubernetes.io/worker=
    

为K8s集群安装NVIDIA Network Operator

NVIDIA Network Operator利用Kubernetes CRD和Operator SDK管理网络相关组件,以启用K8s集群中工作负载的快速网络和RDMA。快速网络是K8s集群的辅助网络,适用于需要高带宽或低延迟的应用。

要使其工作,需要配置和部署多个组件。所有Operator配置和安装步骤应在K8s Master节点上使用root用户账户执行。

准备工作

  1. 按照Helm安装文档在master节点服务器上安装Helm。

  2. 安装额外的RDMA CNI插件。 RDMA CNI插件允许容器化环境中RDMA工作负载的网络命名空间隔离。 使用以下YAML文件部署CNI:

    Master节点控制台

    # 此处应提供具体的YAML文件内容或命令,原文未完整给出
    
kubectl apply -f https://raw.githubusercontent.com/Mellanox/rdma-cni/master/deployment/rdma-cni-daemonset.yaml

要确保插件正确安装,请运行以下命令:

主节点控制台

kubectl -n kube-system get pods -o wide | egrep  "rdma"
kube-rdma-cni-ds-c9cml                        1/1     Running   0          22s     1.1.1.2        node1   <none>           <none>
kube-rdma-cni-ds-cw22n                        1/1     Running   0          22s     1.1.1.3        node2   <none>           <none>
kube-rdma-cni-ds-dh28z                        1/1     Running   0          22s     1.1.1.4        node3   <none>           <none>

部署

  1. 添加NVIDIA Network Operator Helm仓库:

    主节点控制台

    helm repo add mellanox https://mellanox.github.io/network-operator
    helm repo update
    
  2. 创建 values.yaml 文件(示例):

    nfd:
      enabled: true
    
    sriovNetworkOperator:
      enabled: true
    
    # NicClusterPolicy CR values:
    deployCR: true
    ofedDriver:
      deploy: false
    
    nvPeerDriver:
      deploy: false
    
    rdmaSharedDevicePlugin:
      deploy: false
    
    sriovDevicePlugin:
      deploy: false
    
    secondaryNetwork:
      deploy: true
      cniPlugins:
        deploy: true
        image: containernetworking-plugins
        repository: mellanox
        version: v0.8.7
        imagePullSecrets: []
      multus:
        deploy: true
        image: multus
        repository: nfvpe
        version: v3.6
        imagePullSecrets: []
        config: ''
      ipamPlugin:
        deploy: true
        image: whereabouts
        repository: mellanox
        version: v0.3
        imagePullSecrets: []
    
  3. 部署Operator:

    主节点控制台

    helm install -f values.yaml -n network-operator --create-namespace --wait mellanox/network-operator --generate-name
    
    NAME: network-operator
    LAST DEPLOYED: Sun Jul 11 23:06:54 2021
    NAMESPACE: network-operator
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    Get Network Operator deployed resources by running the following commands:
    
    $ kubectl -n network-operator get pods
    $ kubectl -n mlnx-network-operator-resources get pods
    

    要确保Operator正确部署,请运行以下命令:

    主节点控制台

    kubectl -n network-operator get pods -o wide
    NAME                                                              READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
    network-operator-1635068237-57c94ff559-fm7kb                      1/1     Running   0          22s   10.233.90.10   node1   <none>           <none>
    network-operator-1635068237-node-feature-discovery-master-ms57h   1/1     Running   0          22s   10.233.90.9    node1   <none>           <none>
    network-operator-1635068237-node-feature-discovery-worker-fqw9g   1/1     Running   0          22s   10.233.96.3    node2   <none>           <none>
    network-operator-1635068237-node-feature-discovery-worker-kh29n   1/1     Running   0          22s   10.233.90.8    node1   <none>           <none>
    network-operator-1635068237-sriov-network-operator-5c9486726pj9   1/1     Running   0          22s   10.233.90.7    node1   <none>           <none>
    sriov-network-config-daemon-gj7zp                                 1/1     Running   0          16s   10.7.215.90    node2   <none>           <none>
    

高速网络配置

  1. 安装Operator后,请检查SriovNetworkNodeState CR以查看工作节点上所有支持SRIOV的设备。 在我们的部署中,选择的网络接口名称为 ens2f0, ens2f1。要查看接口状态,请使用以下命令:

    主节点控制台

    kubectl -n network-operator get sriovnetworknodestates.sriovnetwork.openshift.io node2 -o yaml
    ...
    - deviceID: 101d
        driver: mlx5_core
        linkSpeed: 100000 Mb/s
        linkType: ETH
        mac: 1c:34:da:54:cd:34
        mtu: 1500
        name: ens2f0
        pciAddress: 0000:61:00.0
        totalvfs: 2
        vendor: 15b3
      - deviceID: 101d
        driver: mlx5_core
        linkSpeed: 100000 Mb/s
        linkType: ETH
        mac: 1c:34:da:54:cd:35
        mtu: 1500
        name: ens2f1
        pciAddress: 0000:61:00.1
        totalvfs: 2
        vendor: 15b3
    ...
    
  2. 为第一个PF接口创建SriovNetworkNodePolicy CR文件 policy-1.yaml,为第二个PF接口创建 policy-2.yaml,通过在'nicSelector'中指定接口名称和供应商(此示例包括ens2f0和ens2f1接口):

    apiVersion: sriovnetwork.openshift.io/v1
    kind: SriovNetworkNodePolicy
    metadata:
      name: mlnxnics-1
      namespace: network-operator
    spec:
      nodeSelector:
        feature.node.kubernetes.io/network-sriov.capable: "true"
      resourceName: mlnx2f0
      mtu: 1500
      numVfs: 2
      nicSelector:
        vendor: "15b3"
        pfNames: [ "ens2f0" ]
      deviceType: netdevice
      isRdma: true
    
    apiVersion: sriovnetwork.openshift.io/v1
    kind: SriovNetworkNodePolicy
    metadata:
      name: mlnxnics-2
      namespace: network-operator
    spec:
      nodeSelector:
        feature.node.kubernetes.io/network-sriov.capable: "true"
      resourceName: mlnx2f1
      mtu: 1500
      numVfs: 2
      nicSelector:
        vendor: "15b3"
        pfNames: [ "ens2f1" ]
      deviceType: netdevice
      isRdma: true
    
  3. 部署policy-1.yaml和policy-2.yaml:

    主节点控制台

    kubectl apply -f policy-1.yaml
    kubectl apply -f policy-2.yaml
    
  4. 创建SriovNetwork CR文件 network-1.yamlnetwork-2.yaml,它们引用SriovNetworkNodePolicy中为每个PF接口定义的'resourceName'(在此示例中,1.1.1.0/24和2.2.2.0/24定义为高速网络的CIDR范围):

    apiVersion: sriovnetwork.openshift.io/v1
    kind: SriovNetwork
    metadata:
      name: "netmlnx2f0"
      namespace: network-operator
    spec:
      ipam: |
        {
          "datastore": "kubernetes",
          "kubernetes": {
             "kubeconfig": "/etc/cni/net.d/whereabouts.d/whereabouts.kubeconfig"
          },
          "log_file": "/tmp/whereabouts.log",
          "log_level": "debug",
          "type": "whereabouts",
          "range": "1.1.1.0/24"
        }
      vlan: 0
      networkNamespace: "default"
      spoofChk: "off"
      resourceName: "mlnx2f0"
      linkState: "enable"
      metaPlugins: |
        {
          "type": "rdma"
        }
    
    apiVersion: sriovnetwork.openshift.io/v1
    kind: SriovNetwork
    metadata:
      name: "netmlnx2f1"
      namespace: network-operator
    spec:
      ipam: |
        {
          "datastore": "kubernetes",
          "kubernetes": {
             "kubeconfig": "/etc/cni/net.d/whereabouts.d/whereabouts.kubeconfig"
          },
          "log_file": "/tmp/whereabouts.log",
          "log_level": "debug",
          "type": "whereabouts",
          "range": "2.2.2.0/24"
        }
      vlan: 0
      networkNamespace: "default"
      spoofChk: "off"
      resourceName: "mlnx2f1"
      linkState: "enable"
      metaPlugins: |
        {
          "type": "rdma"
        }
    
  5. 部署network-1.yaml和network-2.yaml:

    主节点控制台

    kubectl apply -f network-1.yaml
    kubectl apply -f network-2.yaml
    
  6. 验证部署

    检查已部署的网络:

    主节点控制台

    kubectl get network-attachment-definitions.k8s.cni.cncf.io
    NAME         AGE
    netmlnx2f0   3m
    netmlnx2f1   3m
    

    检查工作节点资源:

    kubectl describe nodes node2
    ...
    Addresses:
      InternalIP:  192.168.200.2
      Hostname:    node2
    Capacity:
      cpu:                 24
      ephemeral-storage:   45093936Ki
      hugepages-1Gi:       32Gi
    

hugepages-2Mi: 0 memory: 96076Mi nvidia.com/mlnx2f0: 2 nvidia.com/mlnx2f1: 2 pods: 110 Allocatable: cpu: 23900m ephemeral-storage: 41558571349 hugepages-1Gi: 32Gi hugepages-2Mi: 0 memory: 62952Mi nvidia.com/mlnx2f0: 2 nvidia.com/mlnx2f1: 2 pods: 110 ...

Topology manager settings

性能敏感的应用程序需要拓扑对齐的资源分配,以避免引入额外的延迟。 拓扑管理器确保从统一内存访问(NUMA)节点分配设备。 更多信息请参考控制节点上的CPU管理策略控制节点上的拓扑管理策略

  1. 定义保留给K8s操作系统服务的核心:

    Worker节点控制台

    numactl --hardware
    available: 1 nodes (0)
    node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
    node 0 size: 92611 MB
    node 0 free: 53873 MB
    node distances:
    node   0
      0:  10
    

    在我们的示例中,Numa节点0包含核心0-23。 要为核心0-3保留给K8s,请将以下行添加到kubelet-config.yaml文件中:

    /etc/kubernetes/kubelet-config.yaml

    reservedSystemCPUs:0-3
    

    现在核心4-23可供Pod使用。确保grub文件设置了相同的隔离核心列表(isolcpus=4-23),如前提条件部分所述。

  2. 通过将以下行添加到Worker节点的kubelet配置文件/etc/kubernetes/kubelet-config.yaml中启用拓扑管理器:

    /etc/kubernetes/kubelet-config.yaml

    cpuManagerPolicy: static
    reservedSystemCPUs: 0-3
    topologyManagerPolicy: single-numa-node
    
  3. 由于cpuManagerPolicy的更改,您应该删除/var/lib/kubelet/cpu_manager_state并重启Worker节点上的kubelet服务,如下所示:

    rm -f /var/lib/kubelet/cpu_manager_state
    systemctl daemon-reload && systemctl restart kubelet
    

Configuring VFs to be trusted

  1. 使用以下命令提取PF网络接口的PCI插槽号(在我们的示例中,PCI插槽号为61:00.0):

    Worker节点控制台

    lspci | grep "Mellanox" | grep -v "Virtual" | cut -d " " -f 1
    
  2. 使用mlxreg配置所有VF被固件信任:

    Worker节点控制台

    mlxreg -d 61:00.0 --reg_id 0xc007 --reg_len 0x40 --indexes "0x0.0:32=0x80000000" --yes --set "0x4.0:32=0x1"
    mlxreg -d 61:00.1 --reg_id 0xc007 --reg_len 0x40 --indexes "0x0.0:32=0x80000000" --yes --set "0x4.0:32=0x1"
    
  3. 从驱动侧设置VF为信任(在我们的示例中,VF属于接口ens2f0,ensf1):

    Worker节点控制台

    PF=($(ibdev2netdev | grep mlx5_ | awk '{print $5}'));
    for i in ${PF[@]};
       do
          cat /sys/class/net/${i}/settings/vf_roce &> /dev/null;
          RESULT=$?;
          if [ $RESULT -eq 0 ];
          then
          VFS=($(cat /sys/class/net/${i}/settings/vf_roce | awk '{print $2}' | tr -d :));
          for j in ${VFS[@]}; do ip link set $i vf $j trust on; echo "Set inteface $i VF $j to trusted"; done
          fi;
       done
    
  4. 绑定和解绑VF的以太网驱动:

    Worker节点控制台

    VFS_PCI=($(lspci | grep "Mellanox" | grep "Virtual" | cut -d " " -f 1));
         for i in ${VFS_PCI[@]};
         do
             echo "unbinding VF $i";
             echo "0000:${i}" >> /sys/bus/pci/drivers/mlx5_core/unbind;
             echo "binding VF $i";
             echo "0000:${i}" >> /sys/bus/pci/drivers/mlx5_core/bind;
         done
    

TRex server installation with L2 interfaces

要构建Trex服务器,请按照本指南中的步骤操作:使用Nvidia ConnectX适配器在几个步骤中安装TRex

  • 确保配置基于MAC的配置。
  • 设置第一个TRex端口的目标MAC地址为testpmd或VPP端口0的MAC地址。
  • 设置第二个TRex端口的目标MAC地址为testpmd或VPP端口1的MAC地址。

安装过程示例如下:

./dpdk_setup_ports.py -i
By default, IP based configuration file will be created. Do you want to use MAC based config? (y/N)y
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| ID | NUMA |   PCI   |        MAC        |                  Name                   |  Driver   | Linux IF | Active |
+====+======+=========+===================+=========================================+===========+==========+========+
| 0  | 0    | 02:00.0 | 38:63:bb:33:29:9c | NetXtreme BCM5719 Gigabit Ethernet PCIe | tg3       | eno1     |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| 1  | 0    | 02:00.1 | 38:63:bb:33:29:9d | NetXtreme BCM5719 Gigabit Ethernet PCIe | tg3       | eno2     |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| 2  | 0    | 02:00.2 | 38:63:bb:33:29:9e | NetXtreme BCM5719 Gigabit Ethernet PCIe | tg3       | eno3     |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| 3  | 0    | 02:00.3 | 38:63:bb:33:29:9f | NetXtreme BCM5719 Gigabit Ethernet PCIe | tg3       | eno4     |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| 4  | 0    | 08:00.0 | ec:0d:9a:8a:27:ba | MT28800 Family [ConnectX-5 Ex]          | mlx5_core | ens1f0   |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
| 5  | 0    | 08:00.1 | ec:0d:9a:8a:27:bb | MT28800 Family [ConnectX-5 Ex]          | mlx5_core | ens1f1   |        |
+----+------+---------+-------------------+-----------------------------------------+-----------+----------+--------+
Please choose even number of interfaces from the list above, either by ID , PCI or Linux IF
Stateful will use order of interfaces: Client1 Server1 Client2 Server2 etc. for flows.
Stateless can be in any order.
Enter list of interfaces separated by space (for example: 1 3) : 4 5

For interface 4, assuming loopback to it's dual interface 5.
Destination MAC is ec:0d:9a:8a:27:bb. Change it to MAC of DUT? (y/N).y
Please enter new destination MAC of interface 4: 22:2F:60:A5:F8:01
For interface 5, assuming loopback to it's dual interface 4.
Destination MAC is ec:0d:9a:8a:27:ba. Change it to MAC of DUT? (y/N).y
Please enter new destination MAC of interface 5: 22:2F:60:A5:F8:02
Print preview of generated config? (Y/n)y
### Config file generated by dpdk_setup_ports.py ###

- version: 2
  interfaces: ['08:00.0', '08:00.1']
  port_info:
      - dest_mac: 22:2f:60:a5:f8:01
        src_mac:  ec:0d:9a:8a:27:ba
      - dest_mac: 22:2f:60:a5:f8:02
        src_mac:  ec:0d:9a:8a:27:bb

  platform:
      master_thread_id: 0
      latency_thread_id: 6
      dual_if:
        - socket: 0
          threads: [1,2,3,4,5,12,13,14,15,16,17]

Save the config to file? (Y/n)y
Default filename is /etc/trex_cfg.yaml

Performance Evaluation

在本节中,我们将评估执行NAT和IPv4路由的CNF的性能。软件实现将基于VPP,而加速用例将使用Testpmd将相同的数据平面编程到SmartNIC加速器中,与VPP应用程序相同。

Testing bidirectional IPv4 Routing and NAT Scheme

image2021-10-20_14-59-57.png

  • VPP/Testpmd Pod配置了两个接口:1.1.1.1/24 和 2.2.2.1/24
  • TRex服务器配置了两个接口:1.1.1.2/24 和 2.2.2.2/24
  • 当数据包目标为Trex IP 1.1.1.2时,源子网2.2.2.0/24隐藏在网关IP 1.1.1.1之后
  • 源子网1.1.1.0/24

隐藏在网关IP 2.2.2.1后面,当数据包目的地为Trex IP 2.2.2.2时。

  • 来自Trex子网2.2.2.0/24且目的地为Trex IP 1.1.1.2的数据包由Testpmd/VPP Pod路由并NAT回Trex。
  • 来自Trex子网1.1.1.0/24且目的地为Trex IP 2.2.2.2的数据包由Testpmd/VPP Pod路由并NAT回Trex。

VPP启动

使用CentOS启动CNF

在此Pod CR示例中:

  • 已附加network-1和network-2
  • 已挂载8Gb巨页
  • 已从nvidia.com/mlnx2f0、nvidia.com/mlnx2f1资源附加2个SRIOV接口。
  • 已分配8个核心
apiVersion: v1
kind: Pod
metadata:
  name: vpp
  annotations:
    k8s.v1.cni.cncf.io/networks: netmlnx2f0 , netmlnx2f1
spec:
  containers:
  - name: vpp
    image: centos:8.3.2011
    securityContext:
      privileged: true
      allowPrivilegeEscalation: true
      capabilities:
        add:
        - IPC_LOCK
        - SYS_RAWIO
        - NET_RAW
    volumeMounts:
    - mountPath: /dev/hugepages
      name: hugepage
    resources:
      requests:
        memory: 8Gi
        hugepages-1Gi: 2Gi
        cpu: 8
        nvidia.com/mlnx2f0: 1
        nvidia.com/mlnx2f1: 1
      limits:
        memory: 8Gi
        hugepages-1Gi: 2Gi
        cpu: 8
        nvidia.com/mlnx2f0: 1
        nvidia.com/mlnx2f1: 1
    command:
    - sh
    - -c
    - sleep inf
  volumes:
  - name: hugepage
    emptyDir:
      medium: HugePages

启动Pod:

主节点控制台

kubectl create -f VPP_PodCR.yaml

安装VPP应用程序

本指南假设没有预构建的容器镜像可供下载。

要安装VPP,请登录到容器bash shell:

主节点控制台

kubectl exec --stdin --tty vpp -- /bin/bash

继续按照此指南安装VPP:Build and Run Vector Packet Processing (VPP) Applications Over NVIDIA SmartNICs.

VPP性能评估

配置VPP执行NAT

  1. 使用L3接口配置VPP - 确保使用正确的IP配置L3 IPv4接口。在我们的示例中,第一个接口设置为1.1.1.1/24,第二个接口设置为2.2.2.1/24。

    VPP容器控制台

    ## 启动VPP CLI ##
    $HOME-PATH/vpp/build-root/build-vpp-native/vpp/bin/./vppctl
    ## 设置2个L3接口及其IP和子网 ##
    set int ip address HundredGigabitEthernet61/0/2 1.1.1.1/24
    set interface state HundredGigabitEthernet61/0/2 up
    set int ip address HundredGigabitEthernet61/0/3 2.2.2.1/24
    set interface state HundredGigabitEthernet61/0/3 up
    
  2. 设置指向Trex服务器接口的静态ARP条目

    VPP容器控制台

    set ip neighbor HundredGigabitEthernet61/0/2 1.1.1.2 <Trex_first_port_MAC_Address> static
    set ip neighbor HundredGigabitEthernet61/0/3 2.2.2.2 <Trex_second_port_MAC_Address> static
    
  3. 为2个接口设置NAT

    • 将来自端口HundredGigabitEthernet61/0/3的流量NAT到端口HundredGigabitEthernet61/0/2的网关IP 1.1.1.1后面。
    • 将来自端口HundredGigabitEthernet61/0/2的流量NAT到端口HundredGigabitEthernet61/0/3的网关IP 2.2.2.1后面。

    VPP容器控制台

    nat44 add interface address HundredGigabitEthernet61/0/2
    nat44 add interface address HundredGigabitEthernet61/0/3
    set interface nat44 out HundredGigabitEthernet61/0/2 output-feature
    set interface nat44 out HundredGigabitEthernet61/0/3 output-feature
    nat44 forwarding enable
    nat addr-port-assignment-alg default
    

Testpmd SmartNIC加速启动

为Testpmd应用程序创建容器镜像

  1. 克隆Dockerfile镜像构建器:

    git clone https://github.com/Mellanox/mlnx_docker_dpdk
    cd mlnx_docker_dpdk/
    git checkout origin/ninja-build
    
  2. 在本地Docker服务器中创建镜像:

    • OS版本:CentOS 8.4.2105
    • DPDK版本:20.11
    • Nvidia OFED:5.4-1.0.3.0
    docker build --build-arg OS_VER=8.4.2105 --build-arg DPDK_VER=20.11 --build-arg OFED_VER=5.4-1.0.3.0 -t centos8.4/dpdk20.11:ofed5.4 .
    
  3. 创建镜像后,建议将其推送到Docker Hub账户仓库。有关说明,请访问此链接:Pushing docker image to Docker hub。Docker Hub中的仓库将包含所需的镜像,可以在Pod定义yaml文件中指定(在我们的示例文件TestpmdPodCR.yaml中)。

使用Testpmd应用程序启动CNF

在此Pod CR示例中:

  • 已附加network-1和network-2
  • 已挂载8Gb巨页
  • 已从nvidia.com/mlnx2f0、nvidia.com/mlnx2f1资源附加2个SRIOV接口。
  • 已分配8个核心
apiVersion: v1
kind: Pod
metadata:
  name: testpmd
  annotations:
    k8s.v1.cni.cncf.io/networks: netmlnx2f0 , netmlnx2f1
spec:
  containers:
  - name: testpmd
    image: <repository_name>:<Testpmd_image_name>
    securityContext:
      capabilities:
        add:
        - IPC_LOCK
        - SYS_RAWIO
        - NET_RAW
    volumeMounts:
    - mountPath: /dev/hugepages
      name: hugepage
    resources:
      requests:
        memory: 8Gi
        hugepages-1Gi: 2Gi
        cpu: 8
        nvidia.com/mlnx2f0: 1
        nvidia.com/mlnx2f1: 1
      limits:
        memory: 8Gi
        hugepages-1Gi: 2Gi
        cpu: 8
        nvidia.com/mlnx2f0: 1
        nvidia.com/mlnx2f1: 1
    command:
    - sh
    - -c
    - sleep inf
  volumes:
  - name: hugepage
    emptyDir:
      medium: HugePages

启动Pod:

主节点控制台

kubectl create -f TestpmdPodCR.yaml

Testpmd SmartNIC加速性能评估

Testpmd流组成

本节说明如何通过构建流来运行testpmd应用程序,加速路由和NAT操作。

Testpmd使用DPDK标准RTE_FLOW API加速流。以下是模拟NAT和路由的示例和说明:

image2021-3-29_15-53-5.png

此外,IPv4头部校验和以及数据包CRC由NIC自动完成。

单流测试

使用2个hairpin队列和单个核心在2个VF上运行Testpmd应用程序:

TestPMD POD CLI

# 命令示例

(注意:原始内容中TestPMD POD CLI部分未提供具体命令,此处保留占位符。)

cd /usr/src/dpdk-20.11/build/app/
./dpdk-testpmd -c 0x30 -n 4 -m 1024 -w 0000:61:00.2 -w 0000:61:00.4 -- --hairpinq=2 --hairpin-mode=0x12 --burst=64 --txd=1024 --rxd=1024 --mbcache=512 --rxq=1 --txq=1 --nb-cores=1 --flow-isolate-all -a -i

参数定义:

  • 核心数掩码(例如2个核心)-c 0x3
  • NUMA通道数 -n 4
  • 巨页内存量 -m 1024
  • PCI插槽 -w <number>
  • RX/TX队列数 --rxq=2 --txq=2 --nb-cores=1
  • 设置2个hairpin队列 --hairpinq=2
  • Hairpin模式 --hairpin-mode 值:0x10 - 显式tx规则,0x02 - hairpin端口配对
  • --flow-isolate-all 确保所有流量仅通过配置的流规则接收
  • 单包处理PMD核心 --nb-cores=1
  • 设置端口RX/TX描述符大小 --txd=1024 --rxd=1024

从Testpmd CLI插入第一个方向的加速流:

Testpmd CLI

testpmd> flow create 1 ingress pattern eth / ipv4 src is <Src_IP2> / udp src is <S_port> / end actions set_tp_src port <New_S_port> / queue index 1 / end
testpmd> flow create 0 egress pattern eth / ipv4 / udp / end actions set_mac_dst mac_addr <TREX_MAC0> / set_mac_src mac_addr <TESTPMD_MAC0> / set_ipv4_src ipv4_addr <NAT_IP2> / dec_ttl / end

参数值:

  • <Src_IP2> == 2.2.2.2
  • <S_port> == 1002
  • <New_S_port> == 1003
  • <TREX_MAC0> ← 第一个Trex端口0的MAC地址
  • <TESTPMD_MAC0> ← 第一个testpmd端口0的MAC地址
  • <NAT_IP2> == 2.2.2.1

从Testpmd CLI插入第二个方向的加速流:

Testpmd CLI

testpmd> flow create 0 ingress pattern eth / ipv4 src is <Src_IP1> / udp src is <S_port> / end actions set_tp_src port <New_S_port> / queue index 2 / end
testpmd> flow create 1 egress pattern eth / ipv4 / udp / end actions set_mac_dst mac_addr <TREX_MAC1> / set_mac_src mac_addr <TESTPMD_MAC1> / set_ipv4_src ipv4_addr <NAT_IP1> / dec_ttl / end

参数值:

  • <Src_IP1> == 1.1.1.2
  • <S_port> == 1002
  • <New_S_port> == 1003
  • <TREX_MAC1> ← 第二个Trex端口1的MAC地址
  • <TESTPMD_MAC1> ← 第二个testpmd端口1的MAC地址
  • <NAT_IP1> == 1.1.1.1

多流测试

对于大量流,可以将所有所需流列在一个文件中。Testpmd应用程序将通过读取该文件加载所有流。 本节说明如何通过bash脚本将流写入文件来创建"flow"文件。 修改此脚本"create_flows.sh",通过在每个方向上迭代单个用户的源UDP端口来生成多个NAT流会话。

  • NAT_IP1 ← 第一个Testpmd端口的IP
  • NAT_IP2 ← 第二个Testpmd端口的IP
  • MIN_PORT ← 最小源UDP端口范围
  • MAX_PORT ← 最大源UDP端口范围
  • TREX_MAC0 ← 第一个Trex端口0的MAC地址
  • TREX_MAC1 ← 第二个Trex端口1的MAC地址
  • TESTPMD_MAC0 ← 第一个testpmd端口0的MAC地址
  • TESTPMD_MAC1 ← 第二个testpmd端口1的MAC地址
  • Src_IP1 ← 第一个Trex端口的IP
  • Src_IP2 ← 第二个Trex端口的IP
  • PATH ← 流文件目录路径(本例中为/usr/src/dpdk-20.11/build/app/flows.txt)
#!/bin/bash -e

MIN_PORT=1002
MAX_PORT=6002
Src_IP1="1.1.1.2"
Src_IP2="2.2.2.2"
NAT_IP1="1.1.1.1"
NAT_IP2="2.2.2.1"
TESTPMD_MAC0="4A:AC:4F:29:87:80"
TESTPMD_MAC1="86:19:A0:B8:10:BD"
TREX_MAC0="ec:0d:9a:8a:27:ba"
TREX_MAC1="ec:0d:9a:8a:28:3a"
PATH="/usr/src/dpdk-20.11/build/app/flows.txt"

echo "flow create 0 group 0 ingress pattern eth / ipv4 / udp / end actions jump group 1 / end" > $PATH
echo "flow create 1 group 0 ingress pattern eth / ipv4 / udp /  end actions jump group 1 / end" >> $PATH
echo "flow create 0 group 0 egress pattern eth / ipv4 / udp / end actions set_mac_dst mac_addr ${TREX_MAC0} / set_mac_src mac_addr ${TESTPMD_MAC0} / set_ipv4_src ipv4_addr ${NAT_IP2} / dec_ttl / end" >> $PATH
echo "flow create 1 group 0 egress pattern eth / ipv4 / udp / end actions set_mac_dst mac_addr ${TREX_MAC1} / set_mac_src mac_addr ${TESTPMD_MAC1} / set_ipv4_src ipv4_addr ${NAT_IP1} / dec_ttl / end" >> $PATH

for ((i = ${MIN_PORT}; i <= ${MAX_PORT}; i++ ));
do
  echo "flow create 1 group 1 ingress pattern eth / ipv4 src is ${Src_IP2} / udp src is ${i} / end actions set_tp_src port $((i+1)) / queue index 1 / end" >> $PATH
  echo "flow create 0 group 1 ingress pattern eth / ipv4 src is ${Src_IP1} / udp src is ${i} / end actions set_tp_src port $((i+1)) / queue index 2 / end" >> $PATH
done

在Testpmd二进制目录中执行流创建脚本:

TestPMD POD CLI

chmod +x create_flows.sh
./create_flows.sh

运行testpmd并从文件加载所有流:

TestPMD POD CLI

cd /usr/src/dpdk-20.11/build/app/
./dpdk-testpmd -c 0x30 -n 4 -m 1024 -w 0000:61:00.2 -w 0000:61:00.4 -- --hairpinq=2 --hairpin-mode=0x12 --burst=64 --txd=1024 --rxd=1024 --mbcache=512 --rxq=1 --txq=1 --nb-cores=1 --flow-isolate-all -a -i

从文件加载流(此操作可能需要几分钟):

Testpmd CLI

testpmd>load flows.txt

使用Trex测试性能

将Trex_UDP_L3_P0.py python脚本上传到Trex根目录:

from trex_stl_lib.api import *

class STLS1(object):

    def __init__ (self):
        pass;

    def create_stream (self):
        pkt = Ether()/IP(src="https://networking-docs.nvidia.com/sol/1.1.1.2", dst="2.2.2.2")/UDP()/(22*'x')

        vm = STLScVmRaw( [
                STLVmTupleGen ( ip_min="1.1.1.2", ip_max="1.1.1.2",
                                port_min=1002, port_max=6002,
                                name="tuple"),
                STLVmWrFlowVar(fv_name="tuple.ip", pkt_offset="IP.src"),  # write ip to packet IP.src
                STLVmFixIpv4(offset="IP"),  # fix checksum
                STLVmWrFlowVar(fv_name="tuple.port", pkt_offset="UDP.sport")
                            ]
                         )

        return STLStream(
                                packet = STLPktBuilder(pkt = pkt ,vm = vm ) ,
                                mode = STLTXCont(pps = 3000) )

    def get_streams (self, direction = 0, **kwargs):
        # create 1 stream
        return [ self.create_stream() ]

def register():
    return STLS1()

将Trex_UDP_L3_P1.py python脚本上传到Trex根目录:

from trex_stl_lib.api import *

class STLS1(object):

    def __init__ (self):
        pass;

    def create_stream (self):
        pkt = Ether()/IP(src="https://networking-docs.nvidia.com/sol/2.2.2.2", dst="1.1.1.2")/UDP()/(22*'x')

        vm = STLScVmRaw( [
                STLVmTupleGen ( ip_min="2.2.2.2", ip_max="2.2.2.2",
                                port_min=1002, port_max=6002,
                                name="tuple"),
                STLVmWrFlowVar(fv_name="tuple.ip", pkt_offset="IP.src"),  # write ip to packet IP.src
                STLVmFixIpv4(offset="IP"),  # fix checksum
                STLVmWrFlowVar(fv_name="tuple.port", pkt_offset="UDP.sport")
                            ]
                         )

        return STLStream(
                                packet = STLPktBuilder(pkt = pkt ,vm = vm ) ,
                                mode = STLTXCont(pps = 3000) )

    def get_streams (self, direction = 0, **kwargs):
        # create 1 stream
        return [ self.create_stream() ]

def register():
    return STLS1()

请确保遵循以下设置:单个用户,每个方向5k会话(总共10k会话):

  • 用户指源IPv4地址
  • 会话指源UDP端口范围

image2021-3-29_15-56-40.png

单会话测试 如果测试单用户单会话,'port_min'应等于'port_max'(在我们的示例中,两者都应等于1002)。

运行UDP_L3_P0.py和UDP_L3_P1.py脚本向DUT发送数据包: 增加每秒数据包数量,直到发送数据包数等于接收数据包数。

Trex控制台

./trex-console
trex>start -f UDP_L3_P0.py -m 74mpps -p 0
trex>start -f UDP_L3_P1.py -m 74mpps -p 1

参数定义:

  • -f:指定所需的Trex脚本
  • -m:指定每秒数据包数量
  • -p:设置所需端口

验证从端口0发送的数据包是否在端口1上接收:

Trex控制台

trex>tui

单向测试结果示例: image2021-10-25_15-54-12.png

性能总结

双向测试结果显示,在利用SmartNIC加速能力的情况下,使用相同的x86主机单核资源,性能提升超过12倍。 这将突出SmartNIC硬件处理网络数据包相比仅使用x86 CPU软件处理的卓越价值。

单向测试结果

image2021-10-20_15-6-25.png

双向测试结果

image2021-10-20_15-8-32.png

作者

AZ.jpg Amir Zeidner 多年来,Amir主要作为电信领域的解决方案架构师,领导先进解决方案以满足5G、NFV和SDN网络基础设施需求。Amir在数据平面加速技术(如加速交换和网络处理(ASAP²)和DPDK)方面的专业知识,加上对开源云基础设施的深入了解,使他能够在电信领域推广和交付独特的端到端NVIDIA网络解决方案。