1. SR-IOV 介绍
SR-IOV 技术是一种基于硬件的虚拟化解决方案,可提高网络性能和可伸缩性。SR-IOV 标准允许在虚拟机之间高效共享 PCIe(Peripheral Component Interconnect Express,快速外设组件互连)设备,并且它是在硬件中实现的,可以获得能够与本机性能媲美的 I/O 性能。SR-IOV 规范定义了新的标准,根据该标准,创建的新设备可允许将虚拟机直接连接到 I/O 设备。
单个 I/O 资源可由许多虚拟机共享。共享的设备将提供专用的资源,并且还使用共享的通用资源。这样,每个虚拟机都可访问唯一的资源。因此,启用了 SR-IOV 并且具有适当的硬件和 OS 支持的 PCIe 设备(例如以太网端口)可以显示为多个单独的物理设备,每个都具有自己的 PCIe 配置空间。
下图展示了针对 PCIe 硬件的 SR-IOV 技术体系示意图。
图 1 SR-IOV 技术体系架构
支持 SR-IOV 的网卡主要功能模块分为:
物理功能 (Physical Function, PF)
支持 SR-IOV 的管理。可以创建 VF,对于网卡,理论上可以创建 256 个 VF。一般情况下, 千兆网卡能支持达 7 个 VF, 万兆网卡能支持达 63 个 VF。
虚拟功能 (Virtual Function, VF)
VF 可以理解为一个虚拟网卡,拥有独立内存空间、中断和 Direct Memory Access (DMA)流。VF 是 PF 虚拟出的一个实例,以一个独立的网卡形式展现。VF 是一种轻量级 PCIe 功能,可以与 PF 以及其创建的所有 VF 共享一个或多个物理资源。
交换功能(Layer 2 Classifier/Sorter switch)
这个交换机其实是物理网卡内置的。流量进入物理网卡后,经过这个交换机然后分发到不同的 VF 上。
使用 SR-IOV 技术的主要优点是:
提高虚拟机包转发效率
减少报文在主机内部的传输延时以及延时抖动
由于绕过了主机的协议栈以及 VMM(Virtual Machine Manager),减少了主机在网络报文收发中的资源消耗
同时,由于 SR-IOV 技术使虚拟机通过 PCI-passthrough 的方式直接使用 VF,绕过了主机的内核协议栈以及 VMM,故存在以下局限:
Host 无法监控 VF 的状态
安全组无法应用
虚拟机热迁移无法实现,迁移的支持能力不够灵活
对 HA 的支持不太高
sriov 不支持 vxlan
2. SR-IOV 性能
本测试对比测试万兆网络中 SRIOV 虚拟机和 OVS 虚拟机在网络负载较高情况下的虚拟网络表现,总结分析,同时为以后 SRIOV 虚拟机和其它类型的虚拟机做对比给出参考。
2.1 测试性能指标
主要对以下几个网络性能指标进行测试:
负载(OfferedLoad):网络流量负荷百分比,当前流量占端口速率的比例(百分比)
包速(Frame Per-second):数据包每秒的收/发个数
平均时延(Average Latency):数据包传输的平均延迟(毫秒,ms)
2.2 测试拓扑图
SR-IOV 测试环境网络拓扑:
Openvswitch 测试环境网络拓扑:
2.3 测试结果
可以看出 SR-IOV 各方面性能都要优于 Openvswitch。
3. SR-IOV 高可用方案
因为采用 SR-IOV,虚拟机流量不经过宿主机操作系统,所以不能在宿主机操作系统层面做高可用,必须在虚拟机内部做高可用。SR-IOV 高可用方案如下图:
(1) vm 挂载不同 pf 的 vf,在 vm 内部配置网卡 bond,可以根据需求选择 balance-rr、active-backup 或者 balance-xor 策略。
(2) vm 挂载的 vf 的 mac 地址必须设置成一样。这样在虚拟机配置网卡 bond 的情况下,仍然可以使用 vf 的 mac spoofing check 功能。
(3) pf 配置 lacp bond,交换机侧配置动态链路聚合,实现聚合带宽的动态调整。
4. OpenStack 创建 SR-IOV 虚拟机过程
Openstack 默认不支持 SR-IOV 的高可用,neutron 中一个逻辑的 port 对应一个 vf 口。所以我们需要对 openstack 进行改造,总体思路是,通过在调用 Neutron Create Port 的 api 时候,对 Port 打上 bond 标签,Nova 在创建虚机的时候通过读取到对应 port 的 bond 标签,为该 port 分配两个 VF 口,并且在分配 VF 口的 pci 资源的时候,默认的把两个 vf 分配在不同的 pf 上。对应在 port 的 binding:profile 上需要展示使用的两个 vf 口的 pci 信息。
4.1 调用 neutron api 创建 sriov 的 port
调用 neutron 接口创建 sriov port,在 profile 中对 Port 打上 bond 标签,这里无需赘述。命令如下:
4.2 调用 nova api 创建虚拟机
入口为 nova/api/openstack/compute/servers 的 create 方法,检查一系列参数和 policy,然后调用 compute_api 的 create 方法:
compute_api 是 nova/compute/api.py 模块,在该文件中找到 create 方法,该方法接着调用_create_instance 方法,在_create_instance 方法中调用_validate_and_build_base_options 方法对所有的输入参数和策略做检查,并且封装 pci 请求。封装 pci 请求的方法是 nova/network/neutronv2/api.py 的 create_pci_requests_for_sriov_ports 方法,该方法会先调用 neutron api 获取 port 的 vnic_type、phynet_name、bond 属性,根据 bond 属性确定 vf 的数量(bond 为 true,vf 的数量是 2,否则 vf 数量是 1)。
返回到 nova/compute/api.py 的_create_instance 方法,该方法接着调用 compute_task_api 的 schedule_and_build_instances 方法:
compute_task_api 是 nova/conductor/api.py 模块。这里没有执行什么操作,直接调用了 conductor_compute_rpcapi 的 schedule_and_build_instances 方法:
该方法远程过程调用 api,即 nova/conductor/rpcapi.py 模块,该方法会对版本进行检查,然后调用 RPC:
cast 表示异步调用,schedule_and_build_instances 是远程调用的方法,kw 是传递的参数。现在 nova-api 任务完成,此时会响应用户请求,虚拟机状态为 building。
4.3 nova conductor
nova-api 向 nova-conductor 发起 RPC 调用,进程跳到 nova-conductor 服务,入口为 nova/conductor/manager.py 的 schedule_and_build_instances 方法,该方法首先调用了_schedule_instances 方法,在_schedule_instances 方法中调用了 scheduler_client 的 select_destinations 方法:
这个方法最终调用到 nova/scheduler/client/query.py 下的 select_destinations 方法,执行 RPC 的调用。
RPC 封装同样是在 scheduler 的 rpcapi 中实现。该方法 RPC 调用代码如下:
Call 表示同步调用,此时 nova-conductor 并不会退出,而是堵塞等待直到 nova-scheduler 返回。因此当前状态为 nova-conductor 为 blocked 状态,等待 nova-scheduler 返回,nova-scheduler 接管任务。
4.4 nova scheduler
nova scheduler 中入口为 nova/scheduler/manager.py 模块的 select_destinations 方法,该方法会调用 driver 的 select_destinations 方法,这里的 driver 是调度算法实现,通过 filters 过滤掉不满足条件的计算节点,剩下的节点通过 weigh 方法计算权值,最后选择权值高的作为候选计算节点返回。最后 nova-scheduler 返回调度结果的 hosts 集合,任务结束,返回到 nova-conductor 服务。
nova/scheduler/manager.py
nova/scheduler/driver.py
nova/scheduler/filter_scheduler.py
4.5 nova condutor
回到 nova-conductor 的 schedule_and_build_instances 方法,nova-conductor 等待 nova-scheduler 返回后,拿到调度的计算节点列表。因为可能同时启动多个虚拟机,因此循环调用了 compute_rpcapi 的 build_and_run_instance 方法。
Compute_rpcpai 位于 nova/compute/rpcapi 模块,该方法向 nova-compute 发起 RPC 请求:
发起的是异步 RPC,因此 nova-conductor 任务结束,进入 nova-compute 服务。
4.6 nova compute
入口是 nova/compute/manager.py 的 build_and_run_instance 方法,该方法会调用_do_build_and_run_instance 方法,该方法会更新虚拟机状态,然后调用_build_and_run_instance 方法,该方法会预先声明占用计算节点的资源。
其中在 instance_claim 方法中会处理 pci 的请求,instance_claim→claim_instance(nova/pci/manager.py)→_claim_instance →consume_requests(nova/pci/stats.py),在 nova/pci/stats.py 中会根据不同的网卡把 pci 资源分为不同的 pool。
在 consume_requests 方法中会具体分配 pci 资源也就是 vf,
先根据剩余可用的 vf 的数量将 pool 进行排序,如果 port 的 bond 属性为 false,则 count 数量为 1,则从可用 vf 数量最多的 pool 中分配一个 vf,这样使不同网卡的负载尽量均衡。如果 port 的 bond 属性为 true,则 count 数量为 2,则从两个 pool 中各分配一个 vf,达到高可用的目的。
回到 nova/compute/manager.py 的 build_and_run_instance 方法,这个方法接下来会调用 spawn_n 方法,开始真正创建虚拟机。这里不同的虚拟机技术对应不同的 driver,其中 libvrit 的 driver 对应 nova/virt/libvirt/driver.py。从 spawn 方法开始,会先获取 instance 的磁盘、镜像、网络等信息,然后生成 instance 的 xml 文件。
在_get_guest_xml 方法中会调用_get_guest_config 方法,其中 sriov 网卡的部分如下:
这里如果 port 的 bond 为 true,则会将两块网卡的信息写入 xml 文件中。
回到 spawn 方法,接下来会调用_create_domain_and_network 方法,该方法会调用 plug_vifs 方法创建 qbr 和 qvo,接着创建虚拟机,虚拟机状态为 pause。然后等待 neutron-server 的消息,如果等到消息后将虚拟机状态改为 running,否则超时没有等到,则将虚拟机销毁。
4.7 neutron sriov agent
在 neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py 中 daemon_loop 方法会定时检测 vf 的状态变化,如果有 vf 发生变化,进入 process_network_devices 方法,在该方法中,会根据 vf 的状态是添加更改删除做不同的处理。
其中在 treat_devices_added_updated 方法中,如果 port 的 bond 为 true,需要更新两个 vf 的状态:
然后会调用 rpc,通知 neutron-server 更新状态。
这里是同步调用,neutron-server 接收到消息后,会向 nova 发送更新 port 的消息,接着 nova compute 会 resume 状态是 pause 的虚拟机。
至此,带有 sriov 高可用网卡的虚拟机创建完成。
作者
李伟杰,苏宁云网络架构师,长期从事云计算的研发工作,在云计算虚拟网络和 SDN 网络方面有专业的研究,现在负责苏宁云虚拟网络产品设计及研发。
陈玮,苏宁云高级研发工程师,有多年云计算网络研发经验,现在负责苏宁云网络产品的研发。
评论