azeqjz OpenStack: 红帽OSP10 NFV配置指南: 4. 配置OVS+DPDK
原文:
NETWORK FUNCTIONS VIRTUALIZATION CONFIGURATION GUIDE > Chapter 4. Configure DPDK Accelerated Open vSwitch (OVS) for Networking
4 配置OVS+DPDK
这一章节包括OVS+DPDK在Red Hat OpenStack Platform环境下的部署与调测。参考以下文档OVS-DPDK部署规划以了解配置OVS-DPDK的配置参数。NETWORK FUNCTIONS VIRTUALIZATION PLANNING GUIDE > Chapter 6. Planning Your OVS-DPDK Deployment
- 4.1 单OVS-DPDK数据面 单OVS网桥 单端口
- 4.2 双OVS-DPDK数据面 双OVS网桥 双端口
- 4.3 单OVS-DPDK数据面 单OVS网桥 双端口
- 4.5 配置OVS-DPDK可组合角色
- 4.6 设置OVS-DPDK接口的MTU值
注意:本指南提供了CPU使用、内存分配与网卡配置等示例,在不同的拓扑与use case下,这可能都不一样。硬件与配置选项,可以参考NFV产品指南与NFV规划指南。
OVS+DPDK的优点
DPDK是Intel实现的一个用户态高速数据包处理框架,相比于Linux内核实现的数据包处理方式,有以下优势。
- 用户态驱动程序,避免不必要的内存拷贝和系统调用。
- 使用轮询方式从网卡获取数据包,避免中断方式的上下文切换开销。
- 独占CPU处理数据包,虽然在网络流量低的时候浪费CPU资源,但是网络流量高的时候处理数据包性能很好,可以避免CPU切换导致的cache miss和上下文切换。最新DPDK可以实现流量小的时候使用中断方式,流量大的时候使用轮询方式。
- 内存访问优化,充分利用NUMA架构,大页内存,无锁队列实现数据包的并发高效处理。
- 软件的优化,比如cache line对齐,CPU预取数据,充分利用IntelCPU的网络相关新指令来提升性能。
- 充分利用网卡的Offload功能实现硬件加速。
虽然DPDK对于数据包处理性能很好,但是它只是将数据包高效的送给用户态,而没有网络栈去处理数据包,社区版DPDK也无法与Linux网络栈很好结合,所以基于Linux网络栈实现的网络应用程序无法直接使用DPDK,如果要使用DPDK,应用程序需要重写。当然如果是全新的网络程序,基于DPDK开发是个不错的选择。
原文:x86服务器中网络性能分析与调优
在接下来的流程中,我们需要:
- 更新
network-environment.yaml
以包含内核参数和DPDK参数。 - 更新
compute.yaml
以包含DPDK接口参数中的Bridge。 - 更新
controller.yaml
以包含DPDK接口参数中的同一个Bridge的细节。 - 执行
overcloud_deploy.sh
脚本部署含有DPDK的overcloud。
在开始之前,需要确保有:
- 基于RHEL7.3的Red Hat OpenStack Platform 10
- OVS-DPDK 2.6
- 已经通过测试的网卡. 获取通过测试的NFV网卡列表,参考 NETWORK FUNCTIONS VIRTUALIZATION PLANNING GUIDE > 3.2. Tested NICs。
注意:部署OVS-DPDK时,Red Hat OpenStack Platform 10(OVS2.6)以OVS服务器模式运行。在OVS服务器模式中,如果OVS重启,要求重启所有连接到该OVS的虚拟机实例。
4.1. 配置单网口OVS-DPDK与VLAN Tunneling
*单OVS-DPDK数据面 单OVS网桥 单端口 *
这一节包括配置与部署单数据平面端口的OVS-DPDK,同时配置OpenStack环境的控制面Linux网桥绑定。
4.1.1. 修改first-boot.yaml
修改first-boot.yaml
,定义OVS与DPDK参数,配置CPU亲和性为tuned
。
- 增加额外资源
resources:
userdata:
type: OS::Heat::MultipartMime
properties:
parts:
- config: {get_resource: set_ovs_config}
- config: {get_resource: set_dpdk_params}
- config: {get_resource: install_tuned}
- config: {get_resource: compute_kernel_args}
- OVS配置。
set_ovs_config:
type: OS::Heat::SoftwareConfig
properties:
config:
str_replace:
template: |
#!/bin/bash
FORMAT=$COMPUTE_HOSTNAME_FORMAT
if [[ -z $FORMAT ]] ; then
FORMAT="compute" ;
else
# Assumption: only %index% and %stackname% are the variables in Host name format
FORMAT=$(echo $FORMAT | sed 's/\%index\%//g' | sed 's/\%stackname\%//g') ;
fi
if [[ $(hostname) == *$FORMAT* ]] ; then
if [ -f /usr/lib/systemd/system/openvswitch-nonetwork.service ]; then
ovs_service_path="/usr/lib/systemd/system/openvswitch-nonetwork.service"
elif [ -f /usr/lib/systemd/system/ovs-vswitchd.service ]; then
ovs_service_path="/usr/lib/systemd/system/ovs-vswitchd.service"
fi
grep -q "RuntimeDirectoryMode=.*" $ovs_service_path
if [ "$?" -eq 0 ]; then
sed -i 's/RuntimeDirectoryMode=.*/RuntimeDirectoryMode=0775/' $ovs_service_path
else
echo "RuntimeDirectoryMode=0775" >> $ovs_service_path
fi
grep -Fxq "Group=qemu" $ovs_service_path
if [ ! "$?" -eq 0 ]; then
echo "Group=qemu" >> $ovs_service_path
fi
grep -Fxq "UMask=0002" $ovs_service_path
if [ ! "$?" -eq 0 ]; then
echo "UMask=0002" >> $ovs_service_path
fi
ovs_ctl_path='/usr/share/openvswitch/scripts/ovs-ctl'
grep -q "umask 0002 \&\& start_daemon \"\$OVS_VSWITCHD_PRIORITY\"" $ovs_ctl_path
if [ ! "$?" -eq 0 ]; then
sed -i 's/start_daemon \"\$OVS_VSWITCHD_PRIORITY.*/umask 0002 \&\& start_daemon \"$OVS_VSWITCHD_PRIORITY\" \"$OVS_VSWITCHD_WRAPPER\" \"$@\"/' $ovs_ctl_path
fi
fi
params:
$COMPUTE_HOSTNAME_FORMAT: {get_param: ComputeHostnameFormat}
- 设置DPDK参数
set_dpdk_params:
type: OS::Heat::SoftwareConfig
properties:
config:
str_replace:
template: |
#!/bin/bash
set -x
get_mask()
{
local list=$1
local mask=0
declare -a bm
max_idx=0
for core in $(echo $list | sed 's/,/ /g')
do
index=$(($core/32))
bm[$index]=0
if [ $max_idx -lt $index ]; then
max_idx=$(($index))
fi
done
for ((i=$max_idx;i>=0;i--));
do
bm[$i]=0
done
for core in $(echo $list | sed 's/,/ /g')
do
index=$(($core/32))
temp=$((1<<$(($core % 32))))
bm[$index]=$((${bm[$index]} | $temp))
done
printf -v mask "%x" "${bm[$max_idx]}"
for ((i=$max_idx-1;i>=0;i--));
do
printf -v hex "%08x" "${bm[$i]}"
mask+=$hex
done
printf "%s" "$mask"
}
FORMAT=$COMPUTE_HOSTNAME_FORMAT
if [[ -z $FORMAT ]] ; then
FORMAT="compute" ;
else
# Assumption: only %index% and %stackname% are the variables in Host name format
FORMAT=$(echo $FORMAT | sed 's/\%index\%//g' | sed 's/\%stackname\%//g') ;
fi
if [[ $(hostname) == *$FORMAT* ]] ; then
pmd_cpu_mask=$( get_mask $PMD_CORES )
host_cpu_mask=$( get_mask $LCORE_LIST )
socket_mem=$(echo $SOCKET_MEMORY | sed s/\'//g )
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem=$socket_mem
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=$pmd_cpu_mask
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask=$host_cpu_mask
fi
params:
$COMPUTE_HOSTNAME_FORMAT: {get_param: ComputeHostnameFormat}
$LCORE_LIST: {get_param: HostCpusList}
$PMD_CORES: {get_param: NeutronDpdkCoreList}
$SOCKET_MEMORY: {get_param: NeutronDpdkSocketMemory}
- 设置
tuned
以提供CPU亲和性
install_tuned:
type: OS::Heat::SoftwareConfig
properties:
config:
str_replace:
template: |
#!/bin/bash
FORMAT=$COMPUTE_HOSTNAME_FORMAT
if [[ -z $FORMAT ]] ; then
FORMAT="compute" ;
else
# Assumption: only %index% and %stackname% are the variables in Host name format
FORMAT=$(echo $FORMAT | sed 's/\%index\%//g' | sed 's/\%stackname\%//g') ;
fi
if [[ $(hostname) == *$FORMAT* ]] ; then
tuned_conf_path="/etc/tuned/cpu-partitioning-variables.conf"
if [ -n "$TUNED_CORES" ]; then
grep -q "^isolated_cores" $tuned_conf_path
if [ "$?" -eq 0 ]; then
sed -i 's/^isolated_cores=.*/isolated_cores=$TUNED_CORES/' $tuned_conf_path
else
echo "isolated_cores=$TUNED_CORES" >> $tuned_conf_path
fi
tuned-adm profile cpu-partitioning
fi
fi
params:
$COMPUTE_HOSTNAME_FORMAT: {get_param: ComputeHostnameFormat}
$TUNED_CORES: {get_param: HostIsolatedCoreList}
- 设置内核参数
compute_kernel_args:
type: OS::Heat::SoftwareConfig
properties:
config:
str_replace:
template: |
#!/bin/bash
FORMAT=$COMPUTE_HOSTNAME_FORMAT
if [[ -z $FORMAT ]] ; then
FORMAT="compute" ;
else
# Assumption: only %index% and %stackname% are the variables in Host name format
FORMAT=$(echo $FORMAT | sed 's/\%index\%//g' | sed 's/\%stackname\%//g') ;
fi
if [[ $(hostname) == *$FORMAT* ]] ; then
sed 's/^\(GRUB_CMDLINE_LINUX=".*\)"/\1 $KERNEL_ARGS isolcpus=$TUNED_CORES"/g' -i /etc/default/grub ;
grub2-mkconfig -o /etc/grub2.cfg
reboot
fi
params:
$KERNEL_ARGS: {get_param: ComputeKernelArgs}
$COMPUTE_HOSTNAME_FORMAT: {get_param: ComputeHostnameFormat}
$TUNED_CORES: {get_param: HostIsolatedCoreList}
4.1.2. 修改 post-install.yaml
- 设置
tuned
以提供CPU亲和性
ExtraConfig:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template: |
#!/bin/bash
set -x
FORMAT=$COMPUTE_HOSTNAME_FORMAT
if [[ -z $FORMAT ]] ; then
FORMAT="compute" ;
else
# Assumption: only %index% and %stackname% are the variables in Host name format
FORMAT=$(echo $FORMAT | sed 's/\%index\%//g' | sed 's/\%stackname\%//g') ;
fi
if [[ $(hostname) == *$FORMAT* ]] ; then
tuned_service=/usr/lib/systemd/system/tuned.service
grep -q "network.target" $tuned_service
if [ "$?" -eq 0 ]; then
sed -i '/After=.*/s/network.target//g' $tuned_service
fi
grep -q "Before=.*network.target" $tuned_service
if [ ! "$?" -eq 0 ]; then
grep -q "Before=.*" $tuned_service
if [ "$?" -eq 0 ]; then
sed -i 's/^\(Before=.*\)/\1 network.target openvswitch.service/g' $tuned_service
else
sed -i '/After/i Before=network.target openvswitch.service' $tuned_service
fi
fi
systemctl daemon-reload
fi
params:
$COMPUTE_HOSTNAME_FORMAT: {get_param: ComputeHostnameFormat}
4.1.3. 修改 network-environment.yaml
- 在
resource_registry
增加OVS-DPDK自定义资源
resource_registry:
# Specify the relative/absolute path to the config files you want to use for override the default.
OS::TripleO::Compute::Net::SoftwareConfig: nic-configs/compute-ovs-dpdk.yaml
OS::TripleO::Controller::Net::SoftwareConfig: nic-configs/controller.yaml
OS::TripleO::NodeUserData: first-boot.yaml
OS::TripleO::NodeExtraConfigPost: post-install.yaml
- 在
parameter_defaults
,关闭tunnel类型(设置值为""
),设置网络类型为vlan
。
NeutronTunnelTypes: ""
NeutronNetworkType: 'vlan'
- 更新
parameter_defaults
,关联物理网络到虚拟网桥。
NeutronBridgeMappings: 'dpdk:br-link'
- 更新
parameter_defaults
,设置OpenStack网络ML2与OVS VLAN映射范围。
NeutronNetworkVLANRanges: 'dpdk:22:22'
这个示例在物理网络(dpdk_data)上设置VLAN范围。
- 在
parameter_defaults
下设置OVS-DPDK配置参数。
注意:NeutronDPDKCoreList 和NeutronDPDKMemoryChannels是必要的配置,如果部署DPDK时此参数值不正确,部署会失败,或者导致不稳定。
ⅰ 提供可以被用来作DPDK轮循模式驱动(DPDK poll mode drivers,PMDs)的CPU核列表,格式为[allowed_pattern: "'[0-9,-]+'"]
。
NeutronDpdkCoreList: "'4,6,20,22'"
可通过以下选项优化OVS-DPDK性能:
- 配置CPU与DPDK接口的NUMA节点关联。
使用cat /sys/class/net/
列出与接口关联的NUMA节点,使用/device/numa_node lscpu
列出与NUMA节点关联的CPU。 - 超线程情况下把CPU sibling放到同个组里( 什么是CPU sibling? )。
使用cat /sys/devices/system/cpu/
查询CPU sibling。/topology/thread_siblings_list - 为主机进程预留CPU 0。
- 隔离分配给PMD的CPU,保证主机进程不使用这些CPU。
- 使用
NovaVcpuPinset
把分配给PMD的CPU从计算调度中排除。
》 Type 1: DPDK PMD使用,NeutronDpdkCoreList;Type 2:宿主机进程使用,HostCpusList;Type 3:虚拟机使用,NovaVcpuPinset。
》 NovaVcpuPinSet + NeutronDpdkCoreList = HostIsolatedCoreList
ⅱ提供内存通道的数量,格式[allowed_pattern: "[0-9]+"]
NeutronDpdkMemoryChannels: "4"
ⅲ 设置从CPU socket的大页池中预分配的内存。
NeutronDpdkSocketMemory: "2048,2048"
这是用逗号分隔的字符串,按照CPU socket升序排列。如果只有一个NUMA节点,则设置为 1024,0 。
ⅳ 设置DPDK驱动类型与数据通道类型。
NeutronDpdkDriverType: "vfio-pci"
NeutronDatapathType: "netdev"
- 在
parameter_defaults
下设置OVS的vhost-user socket目录。
NeutronVhostuserSocketDir: "/var/run/openvswitch"
- 在
parameter_defaults
下预留给主机进程的RAM。
NovaReservedHostMemory: 2048
- 在
parameter_defaults
下,设置预留给虚拟机进程的物理CPU核范围,以逗号分隔。
NovaVcpuPinSet: "8,10,12,14,18,24,26,28,30"
- 在
parameter_defaults
下,列出使用的过滤器。
Nova scheduler使用这些列出来的过滤器。优先列出最有拘束力的过滤器,以使节点的过滤进程更加高效运行。
NovaSchedulerDefaultFilters: "RamFilter,ComputeFilter,AvailabilityZoneFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,PciPassthroughFilter,NUMATopologyFilter"
- 在
parameter_defaults
下,增加ComputeKernelArgs
参数,以在初次启动时增加这些参数到默认的grub
文件中。
ComputeKernelArgs: "default_hugepagesz=1GB hugepagesz=1G hugepages=32 iommu=pt intel_iommu=on"
注意:这些大页内存会被虚拟机消耗使用,也会被OVS-DPDK使用,如在此步骤中的NeutronDpdkSocketMemory参数所示。可以被虚拟机使用的大页内存页数是引导参数减去NeutronDpdkSocketMemory。
需要在使用DPDK的虚拟机实例flavor中添加hw:mem_page_size=1GB
。如果没有做这一步,虚拟机实例会无法获取DHCP分配(大页内存?)。
- 在
parameter_defaults
下,设置需要调整的物理CPU核范围。
参数在附录调整文档cpu-partitioning
中。
HostIsolatedCoreList: "2,4,6,8,10,12,14,18,20,22,24,26,28,30"
- 在
parameter_defaults
下,设置逻辑OVS-DPDK核列表。这些CPU核必须要手工从NeutronDpdkCoreList
与NovaVcpuPinSet
列表中排除出去。
HostCpusList: "'3,5,7,19,21,23'"
4.1.4. 修改 controller.yaml
- 创建一个独立的provisioning接口。
-
type: interface
name: nic1
use_dhcp: false
addresses:
-
ip_netmask:
list_join:
- '/'
- - {get_param: ControlPlaneIp}
- {get_param: ControlPlaneSubnetCidr}
routes:
-
ip_netmask: 169.254.169.254/32
next_hop: {get_param: EC2MetadataIp}
-
default: true
next_hop: {get_param: ExternalInterfaceDefaultRoute}
- 为隔离的网络创建控制面Linux绑定。
-
type: linux_bond
name: bond_api
bonding_options: "mode=active-backup"
use_dhcp: false
dns_servers: {get_param: DnsServers}
members:
-
type: interface
name: nic2
primary: true
-
type: interface
name: nic3
- 为Linux绑定分配VLAN。
-
type: vlan
vlan_id: {get_param: InternalApiNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: InternalApiIpSubnet}
-
type: vlan
vlan_id: {get_param: TenantNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: TenantIpSubnet}
-
type: vlan
vlan_id: {get_param: StorageNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: StorageIpSubnet}
-
type: vlan
vlan_id: {get_param: StorageMgmtNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: StorageMgmtIpSubnet}
-
type: vlan
vlan_id: {get_param: ExternalNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: ExternalIpSubnet}
- 为计算节点创建VOS网桥。
-
type: ovs_bridge
name: br-link
use_dhcp: false
members:
-
type: interface
name: nic4
4.1.5. 修改compute.yaml
复制默认的compute.yaml
为compute-ovs-dpdk.yaml
,并且修改一下内容:
- 创建分离的provisioning接口。
-
type: interface
name: nic1
use_dhcp: false
addresses:
-
ip_netmask:
list_join:
- '/'
- - {get_param: ControlPlaneIp}
- {get_param: ControlPlaneSubnetCidr}
routes:
-
ip_netmask: 169.254.169.254/32
next_hop: {get_param: EC2MetadataIp}
-
default: true
next_hop: {get_param: ControlPlaneDefaultRoute}
- 为隔离的网络创建控制面Linux绑定。
-
type: linux_bond
name: bond_api
bonding_options: "mode=active-backup"
use_dhcp: false
dns_servers: {get_param: DnsServers}
members:
-
type: interface
name: nic2
primary: true
-
type: interface
name: nic3
- 分配VLAN给这个Linux绑定。
-
type: vlan
vlan_id: {get_param: InternalApiNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: InternalApiIpSubnet}
-
type: vlan
vlan_id: {get_param: TenantNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: TenantIpSubnet}
-
type: vlan
vlan_id: {get_param: StorageNetworkVlanID}
device: bond_api
addresses:
-
ip_netmask: {get_param: StorageIpSubnet}
- 设置有DPDK接口的网桥,以连接到控制节点。
-
type: ovs_user_bridge
name: br-link
use_dhcp: false
members:
-
type: ovs_dpdk_port
name: dpdk0
members:
-
type: interface
name: nic4
注意:
如果有多个DPDK设备,为每个需要添加的DPDK设备复制一遍type
字段即可。
注意:
使用OVS-DPDK时,在同一个计算节点上的所有网桥类型应该为ovs_user_bridge
。当不是这个类型时,虽然Director可能会接受这个配置,但是Red Hat OpenStack Platform不支持同个节点上同时有ovs_bridge
和ovs_user_bridge
。
4.1.6. 执行 overcloud_deploy.sh 脚本
以下例子定义bash脚本中的OVS-DPDK环境openstack overcloud deploy
命令:
#!/bin/bash
openstack overcloud deploy --templates \
-e /usr/share/openstack-tripleo-heat-templates/environments/network-isolation.yaml \
-e /usr/share/openstack-tripleo-heat-templates/environments/neutron-ovs-dpdk.yaml \
-e /home/stack/ospd-10-vlan-dpdk-single-port-ctlplane-bonding/network-environment.yaml
/usr/share/openstack-tripleo-heat-templates/environments/neutron-ovs-dpdk.yaml
是默认neutron-ovs-dpdk.yaml
文件的位置,这使能计算节点的OVS-DPDK参数。/home/stack/
是/network-environment.yaml network-environment.yaml
文件的路径。使用这个文件来覆盖neutron-ovs-dpdk.yaml
文件的默认值。
注意:
overcloud部署后,需要重启计算节点以执行tuned文件。
注意:
此OVS-DPDK配置不支持安全组与热迁移。