看到一些方案使用 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

按照帮助信息 使用命令sriov inti 进行初始化。

init

初始化完成后 使用sriov status 查询状态

status

说明已经成功,这时候在WebGUI中已能看到,根据status中设备ID自由分配即可

WebGUI

正式配置

手动测试已经通过后,只需要把服务加入开机自启动即可

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地址那么就再执行一次即可。

最后修改:2024 年 12 月 28 日
如果觉得我的文章对你有用,请随意赞赏