Tại Sao SR-IOV Thay Đổi Cách Tôi Nhìn Nhận Mạng Ảo Hóa
Khi vận hành hypervisor bare-metal với hơn 20 KVM guest, tôi liên tục gặp phải cùng một vấn đề: virtual bridge (virbr0) và virtio networking đang ngốn CPU cycles và gây ra độ trễ hiện rõ trong các bài kiểm tra iperf3. Một NIC 10GbE chỉ đạt khoảng 4–5 Gbps đến VM qua software stack. Thật bực bội khi mục đích chính của việc dùng phần cứng vật lý chính là hiệu năng.
SR-IOV — Single Root I/O Virtualization — là giải pháp phần cứng cho vấn đề đó. Chính NIC expose ra nhiều Virtual Functions (VF) nhẹ, ánh xạ gần như trực tiếp vào hệ điều hành guest, loại bỏ kernel host khỏi đường truyền dữ liệu. Tôi đã chạy cấu hình này trong môi trường production trên nhiều cluster, và nó hoạt động tốt. Throughput liên tục đạt 9.4–9.8 Gbps trên card 10GbE — không cần tuning tricks, không cần DPDK, chỉ cần VF được truyền thẳng vào guest.
Hướng dẫn này bao gồm toàn bộ quy trình: xác minh hỗ trợ phần cứng, kích hoạt IOMMU, tạo VF và gán chúng cho KVM guest. Đã kiểm tra trên Ubuntu 22.04 và RHEL 9, với card Intel X550 và Mellanox ConnectX-4.
Yêu Cầu và Kiểm Tra Phần Cứng
SR-IOV yêu cầu hỗ trợ ở ba lớp: bản thân NIC, CPU/chipset (IOMMU) và BIOS. Kiểm tra theo thứ tự này trước khi chỉnh sửa bất kỳ file cấu hình nào.
Kiểm Tra Hỗ Trợ SR-IOV của NIC
# Tìm địa chỉ PCI của NIC
lspci | grep -i ethernet
# Kiểm tra khả năng SR-IOV (tìm "Single Root I/O Virtualization")
lspci -v -s 0000:03:00.0 | grep -i sriov
# Hoặc kiểm tra trực tiếp qua sysfs
cat /sys/bus/pci/devices/0000:03:00.0/sriov_totalvfs
Một con số từ sriov_totalvfs — như 63 cho Intel X550 hoặc 8 cho một số card Mellanox — nghĩa là NIC hỗ trợ SR-IOV. Không có file? Card đó không hỗ trợ.
Kiểm Tra Hỗ Trợ IOMMU
# CPU Intel
grep -o 'vmx' /proc/cpuinfo | head -1
# CPU AMD
grep -o 'svm' /proc/cpuinfo | head -1
# Kiểm tra xem IOMMU đã được kích hoạt chưa
dmesg | grep -e DMAR -e IOMMU | head -20
Những dòng như DMAR: IOMMU enabled hoặc AMD-Vi: Interrupt remapping enabled nghĩa là bạn đã sẵn sàng. Không có gì? IOMMU có thể đang tắt trong BIOS hoặc chưa được truyền vào kernel — chúng ta sẽ xử lý ở bước tiếp theo.
Cài Đặt: Kích Hoạt IOMMU và Nạp Driver
Kích Hoạt IOMMU trong Tham Số Khởi Động Kernel
Chỉnh sửa cấu hình GRUB để truyền các tham số IOMMU khi khởi động. Tham số cụ thể phụ thuộc vào nhà sản xuất CPU:
# Chỉnh sửa cấu hình GRUB
sudo nano /etc/default/grub
# Cho Intel — thêm vào GRUB_CMDLINE_LINUX_DEFAULT:
# intel_iommu=on iommu=pt
# Cho AMD — thêm:
# amd_iommu=on iommu=pt
# Ví dụ dòng sau khi chỉnh sửa:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on iommu=pt"
Tham số iommu=pt kích hoạt chế độ passthrough — bỏ qua chi phí dịch IOMMU cho các thiết bị giữ nguyên trên host. Nếu không có nó, mọi giao dịch DMA đều phải đi qua ánh xạ địa chỉ, kể cả NIC và storage controller của chính host.
# Áp dụng và khởi động lại
sudo update-grub # Debian/Ubuntu
# sudo grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/CentOS
sudo reboot
Xác Minh IOMMU Sau Khi Khởi Động Lại
dmesg | grep -e DMAR -e IOMMU
# Kết quả mong đợi: "DMAR: IOMMU enabled" hoặc "AMD-Vi: AMD IOMMUv2 loaded and initialized"
Cài Đặt Các Gói Cần Thiết
# Ubuntu/Debian
sudo apt install -y qemu-kvm libvirt-daemon-system virt-manager
sudo apt install -y linux-modules-extra-$(uname -r) # module driver bổ sung
# RHEL/CentOS
sudo dnf install -y qemu-kvm libvirt virt-install
sudo dnf install -y kernel-modules-extra
Cấu Hình: Tạo Virtual Functions và Gán cho KVM
Tạo Virtual Functions trên NIC Vật Lý
Tìm tên interface của physical function (PF) trước, sau đó tạo các VF:
# Lấy tên interface từ địa chỉ PCI
ls /sys/bus/pci/devices/0000:03:00.0/net/
# Kết quả: ens3f0
# Tạo 4 Virtual Functions (điều chỉnh số lượng theo nhu cầu)
echo 4 | sudo tee /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs
# Xác minh các VF đã được tạo
lspci | grep "Virtual Function"
ip link show ens3f0
Sau lệnh echo, ip link show sẽ liệt kê vf 0 đến vf 3 dưới physical interface, mỗi cái có địa chỉ MAC riêng.
Duy Trì Số Lượng VF Qua Các Lần Khởi Động Lại
Lệnh ghi vào sysfs đó không tồn tại sau khi khởi động lại. Dùng systemd service để tự động khôi phục:
# Tạo systemd service để lưu cấu hình
sudo tee /etc/systemd/system/sriov-setup.service <<'EOF'
[Unit]
Description=Kích hoạt SR-IOV Virtual Functions
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now sriov-setup.service
Tìm Địa Chỉ PCI của VF cho Passthrough
# Liệt kê địa chỉ PCI của các VF
lspci | grep "Virtual Function"
# Ví dụ kết quả:
# 03:10.0 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 03:10.2 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# Kiểm tra IOMMU group của một VF
for d in /sys/kernel/iommu_groups/*/devices/*; do
echo "Nhóm $(basename $(dirname $(dirname $d))): $(basename $d) $(lspci -nns $(basename $d) 2>/dev/null | cut -d' ' -f3-)"
done | grep "03:10"
Mỗi VF nên nằm trong IOMMU group riêng của nó. Các group dùng chung gây ra xung đột passthrough — đây là vấn đề định tuyến phần cứng thay đổi theo từng mainboard và không thể khắc phục chỉ bằng phần mềm.
Gán VF cho KVM Guest qua virt-manager / virsh
Hai lựa chọn: chỉnh sửa XML hoặc dùng lệnh virsh. XML rõ ràng hơn và dễ kiểm tra hơn:
# Lấy đoạn XML của thiết bị VF để thêm vào
virsh nodedev-dumpxml pci_0000_03_10_0
# Chỉnh sửa định nghĩa VM và thêm vào trong <devices>:
sudo virsh edit vm-name
Thêm khối này vào bên trong <devices>:
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x03' slot='0x10' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</hostdev>
Thuộc tính managed='yes' cho libvirt biết tự động detach VF khỏi driver host và gắn lại khi VM khởi động/tắt — không cần driver binding thủ công.
Tùy Chọn: Đặt Địa Chỉ MAC Cụ Thể cho VF
# Đặt MAC cho VF 0 của interface vật lý ens3f0
sudo ip link set ens3f0 vf 0 mac 52:54:00:ab:cd:01
# Kiểm tra
ip link show ens3f0
Hữu ích khi DHCP server của bạn gắn lease với MAC — nếu không, VF sẽ nhận MAC ngẫu nhiên sau mỗi lần khởi động.
Xác Minh và Giám Sát
Xác Nhận VF Hiển Thị Bên Trong Guest
Khởi động VM và xác minh từ bên trong guest:
# Bên trong KVM guest
lspci | grep Ethernet
# Phải hiển thị thiết bị Intel hoặc Mellanox, KHÔNG phải virtio-net
ip link show
ethtool -i eth0 | grep driver
# driver: iavf (driver Intel Adaptive Virtual Function)
# hoặc: mlx5_core (Mellanox)
Kiểm Tra Throughput với iperf3
# Trên receiver (host hoặc VM khác)
iperf3 -s
# Trên guest dùng SR-IOV VF
iperf3 -c <receiver_ip> -t 30 -P 4
# So sánh: cùng bài test trên interface virtio-net
# Kết quả SR-IOV: ~9.4 Gbps trên 10GbE, CPU sử dụng ~8%
# Kết quả virtio-net: ~4.5 Gbps trên 10GbE, CPU sử dụng ~35%
Giám Sát Thống Kê VF từ Host
# Xem thống kê theo từng VF từ host
watch -n 1 'cat /sys/class/net/ens3f0/device/sriov/0/stats 2>/dev/null || \
ip -s link show ens3f0'
# Kiểm tra trạng thái link và trust mode của VF
ip link show ens3f0
# Kích hoạt trust mode cho VF (cần thiết cho promiscuous trong một số use case)
sudo ip link set ens3f0 vf 0 trust on
# Kiểm tra dmesg để tìm lỗi VF
dmesg | grep -i 'vf\|iavf\|mlx5' | tail -20
Xử Lý Sự Cố Thường Gặp
- VF không xuất hiện sau khi echo vào sriov_numvfs: Kiểm tra
dmesg | grep sriov— thường là cần bật cài đặt BIOS (SR-IOV Enable, ARI Forwarding) trước. - libvirt không khởi động được VM với VF: Chạy
virsh nodedev-detach pci_0000_03_10_0thủ công trước, sau đó thử khởi động VM. Đôi khi auto-detach bị xung đột với driver binding. - IOMMU group chứa nhiều thiết bị: Đây là vấn đề ACS (Access Control Services). Một số kernel có ACS override patch; hoặc có thể truyền toàn bộ IOMMU group vào guest.
- Guest mất VF sau khi host khởi động lại: Xác nhận systemd service ở bước trước đã được enable và kiểm tra
systemctl status sriov-setup.servicesau khi khởi động lại.
SR-IOV cần nhiều bước thiết lập hơn virtio — không thể phủ nhận điều đó. Nhưng một khi các VF đã được cấu hình và systemd service đã sẵn sàng, bạn không cần nghĩ đến nó nữa. Guest sẽ thấy một NIC thật.
CPU overhead trên host giảm từ ~35% xuống dưới 10%. Độ trễ giảm xuống dưới mili giây. Với các workload xử lý lưu lượng lớn — database replication, NFS storage backend, pipeline log tần số cao — khoảng cách overhead đó tích lũy rất nhanh. Một giờ mày mò trong BIOS và thiết lập udev chỉ là chi phí bỏ ra một lần; phần thưởng throughput sẽ chạy mỗi ngày sau đó.

