看到一些方案使用 systemd 文件中逐行写入命令。由于SRIOV会有一些定制化功能,所以博主认为使用脚本集中管理复杂命令,这样不仅可以简化 service 文件,还可以方便将来对逻辑的调整。
创建初始化脚本
vim /usr/local/bin/sriov
记得按照 实际网卡和需求修改好用户配置部分,按i
插入内容
#!/bin/bash
# ______ ______ __ ______ __ __
# /\ ___\ /\ == \ /\ \ /\ __ \ /\ \ / /
# \ \___ \ \ \ __< \ \ \ \ \ \/\ \ \ \ \'/
# \/\_____\ \ \_\ \_\ \ \_\ \ \_____\ \ \__|
# \/_____/ \/_/ /_/ \/_/ \/_____/ \/_/
#
# 作者: Zimri
# 发布时间: 2024-12-28
# 版本: v1.0.0
# 文件位置 /usr/local/bin/sriov
# 使用方法:
# sriov init # 初始化 SR-IOV
# sriov status # 查看 SR-IOV 状态
# sriov shutdown # 关闭所有 SR-IOV 功能
###########################################
## 用户设置 ##
# 物理网口支持的VF数
SRIOV_NUM=8
# 物理网口名称
INTERFACE_NAMES=("ens20f0np0" "ens20f1np1")
# VF网卡前缀
PREFIXES=("ens20f0" "ens20f1")
# VF网卡MAC前缀 总长 e8:eb:d3:00:aa:00 最后00自动生成 并固定
PREFIX_MACS=("e8:eb:d3:00:aa" "e8:eb:d3:01:bb")
###########################################
## 辅助函数 ##
# 错误处理函数
function error_exit {
echo "[ERROR] $1" >&2
exit 1
}
###########################################
## 核心逻辑 ##
# 初始化 SR-IOV
function init_sriov {
echo "[INFO] 初始化 SR-IOV..."
for idx in "${!INTERFACE_NAMES[@]}"; do
interface=${INTERFACE_NAMES[$idx]}
prefix=${PREFIXES[$idx]}
mac_prefix=${PREFIX_MACS[$idx]}
# 检查接口是否存在
[[ -d "/sys/class/net/$interface/device" ]] || error_exit "物理网卡 $interface 不存在。"
# 设置 VF 数量
echo $SRIOV_NUM > /sys/class/net/$interface/device/sriov_numvfs || error_exit "设置 $interface 的 VF 数量失败。"
# 为每个 VF 设置 MAC 地址
for i in $(seq 0 $((SRIOV_NUM - 1))); do
mac_address=$(printf "%s:%02x" "$mac_prefix" "$i")
ip link set dev $interface vf $i mac $mac_address || error_exit "设置 $interface 的 VF $i MAC 地址失败。"
ip link set dev $interface vf $i spoofchk off || error_exit "关闭 $interface 的 VF $i MAC 欺骗检查失败。"
done
# 启用物理接口
ip link set $interface up || error_exit "启用 $interface 失败。"
# 调试日志
echo "[DEBUG] $interface 配置完成后状态:"
ip link show $interface
done
# 启用所有 VF 接口
for prefix in "${PREFIXES[@]}"; do
for i in $(seq 0 $((SRIOV_NUM - 1))); do
ip link set ${prefix}v${i} up || error_exit "启用 ${prefix}v${i} 失败。"
done
done
echo "[INFO] 初始化完成。"
}
# 查看 SR-IOV 状态
function status_sriov {
echo "[INFO] 查看 SR-IOV 状态..."
for idx in "${!INTERFACE_NAMES[@]}"; do
interface=${INTERFACE_NAMES[$idx]}
prefix=${PREFIXES[$idx]}
if [[ -d "/sys/class/net/$interface/device" ]]; then
vfs=$(cat /sys/class/net/$interface/device/sriov_numvfs)
echo "[INFO] $interface 当前启用的 VF 数量:$vfs"
# 获取主网卡的 VF 信息
vf_info=$(ip link show "$interface")
# 遍历每个 VF
for i in $(seq 0 $((vfs - 1))); do
vf_path="/sys/class/net/${interface}/device/virtfn${i}"
if [[ -d "$vf_path" ]]; then
vf_pci_id=$(basename "$(readlink -f "$vf_path")")
# 从主网卡的 VF 信息中提取 MAC 地址
vf_mac=$(echo "$vf_info" | grep -E "vf $i " | grep -oP 'link/ether \K[^\s]+')
# 获取网卡名称
vf_name=""
if [[ -d "${vf_path}/net" ]]; then
vf_name=$(ls "${vf_path}/net" 2>/dev/null)
fi
# 获取状态
vf_state="未知"
if [[ -n "$vf_name" ]]; then
vf_state=$(ip link show "$vf_name" 2>/dev/null | grep -Eo 'state (UP|DOWN)' | awk '{print $2}')
fi
echo " VF $i: 网卡名称: ${vf_name:-未知}, MAC地址: ${vf_mac:-未知}, 设备ID: $vf_pci_id, 状态: ${vf_state:-未知}"
else
echo " VF $i: 未检测到网卡信息(可能未启用)"
fi
done
else
echo "[WARN] 网卡接口 $interface 不存在。"
fi
done
}
# 关闭 SR-IOV
function shutdown_sriov {
echo "[INFO] 关闭 SR-IOV..."
for idx in "${!INTERFACE_NAMES[@]}"; do
interface=${INTERFACE_NAMES[$idx]}
prefix=${PREFIXES[$idx]}
# 检查接口是否存在
[[ -d "/sys/class/net/$interface/device" ]] || {
echo "[WARN] 网卡接口 $interface 不存在,跳过。"
continue
}
# 关闭所有 VF 接口
for i in $(seq 0 $((SRIOV_NUM - 1))); do
vf_interface="${prefix}v${i}"
ip link set $vf_interface down 2>/dev/null || echo "[WARN] 无法关闭 $vf_interface,可能未初始化。"
done
# 设置 VF 数量为 0
echo 0 > /sys/class/net/$interface/device/sriov_numvfs || error_exit "关闭 $interface 的 VFs 失败。"
echo "[INFO] 已关闭 $interface 的所有 VFs。"
done
}
function help {
cat << EOF
SRIOV 工具 v1.0 (2024-12-28 14:07:44)
使用方法:
sriov init # 初始化 SR-IOV
sriov status # 查看 SR-IOV 状态 VF网卡信息
sriov shutdown # 关闭所有 SR-IOV 功能
兼容版本:
基于 Virtual Environment 8.3.2开发
EOF
}
###########################################
## 主逻辑入口 ##
case "$1" in
init)
init_sriov
;;
status)
status_sriov
;;
shutdown)
shutdown_sriov
;;
*)
help
exit 1
;;
esac
创建服务
创建一个名为 sriov的服务
vim /etc/systemd/system/sriov.service
写入内容
[Unit]
Description=SR-IOV Management Service
Documentation=man:sriov(8)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sriov init
ExecStop=/usr/local/bin/sriov shutdown
ExecReload=/bin/bash -c '/usr/local/bin/sriov shutdown && /usr/local/bin/sriov init'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
测试服务
授予执行权限
chmod +x /usr/local/bin/sriov
重载服务
systemctl daemon-reload
手动执行初始化
sriov
按照帮助信息 使用命令sriov inti
进行初始化。
初始化完成后 使用sriov status
查询状态
说明已经成功,这时候在WebGUI中已能看到,根据status中设备ID自由分配即可
正式配置
手动测试已经通过后,只需要把服务加入开机自启动即可
systemctl enable sriov
如需关闭开机自启动 只需
systemctl disable sriov
VF与VMBR互通
使用SR-IOV VF网卡后 发现和虚拟机交换机或VF之间不互通,原因是VF 网卡的 MAC 地址过滤机制可能阻止未注册的 MAC 地址通信。 已有大佬写了脚本,我这里修善一下以让它能够支持多个网卡或多个虚拟交换机。
在适当的位置放入写入这个文件, 示例中在 root目录下
vim /root/vf_add_maddr.sh
写入如下内容:
#!/usr/bin/bash
#
# vf_add_maddr.sh Version 1.3 Multi-NIC, Multi-Bridge Edition without logging
# 脚本基于kriss35,由Rama、Zimri更新。
# Usage: execute directly without arguments or as a systemd service.
CTCONFDIR=/etc/pve/nodes/pve/lxc
VMCONFDIR=/etc/pve/nodes/pve/qemu-server
# Define arrays for IFBRIDGE (SR-IOV enabled NICs) and LBRIDGE (virtual bridges)
IFBRIDGES=("ens20f0np0" "ens20f1np1") # 这里填写 SR-IOV 网卡名称
LBRIDGES=("vmbr0" "vmbr1" "vmbr2" "vmbr3") # 这里填写需要和 SR-IOV 网卡互通的虚拟交换机
# 逻辑开始
if [ ! -d "$CTCONFDIR" ] || [ ! -d "$VMCONFDIR" ]; then
echo "ERROR: 配置目录未装载!"
exit 1
fi
MAC_LIST_VMS=$(cat ${VMCONFDIR}/*.conf | grep bridge | grep -Eo '([[:xdigit:]]{1,2}[:-]){5}[[:xdigit:]]{1,2}' | tr '[:upper:]' '[:lower:]')
MAC_LIST_VMS+=" $(cat ${CTCONFDIR}/*.conf | grep hwaddr | grep -Eo '([[:xdigit:]]{1,2}[:-]){5}[[:xdigit:]]{1,2}' | tr '[:upper:]' '[:lower:]')"
for (( i=0; i<${#IFBRIDGES[@]}; i++ )); do
IFBRIDGE=${IFBRIDGES[$i]}
LBRIDGE=${LBRIDGES[$i]}
TMP_FILE="/tmp/vf_add_maddr_${IFBRIDGE}_${LBRIDGE}.tmp"
MAC_ADD2LIST="$(cat /sys/class/net/$LBRIDGE/address)"
MAC_LIST="$MAC_LIST_VMS $MAC_ADD2LIST"
/usr/sbin/bridge fdb show | grep "${IFBRIDGE} self permanent" > "$TMP_FILE"
for mactoregister in $MAC_LIST; do
if (grep -Fq "$mactoregister" "$TMP_FILE"); then
:
else
/usr/sbin/bridge fdb add "$mactoregister" dev "${IFBRIDGE}"
fi
done
done
exit 0
其中 IFBRIDGES 与 LBRIDGES 支持多组配置。按需设置 。
最后修复
chmod +x /root/vf_add_maddr.sh && /bin/bash /root/vf_add_maddr.sh
该脚本只需在执行一次即可,如未来修改了mac地址那么就再执行一次即可。