使用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软件开发工具包
K8sKubernetesNAT网络地址转换
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个核心
VPP_PodCR.yaml
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个核心
TestpmdPodCR.yaml
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)
create_flows.sh
#!/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根目录:

UDP_L3_P0.py
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根目录:

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