Tối ưu mạng KVM với virtio-net Multiqueue và vhost-net: Kéo full 10GbE không nghẽn CPU

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

Vấn đề thực tế: Phần cứng 10GbE nhưng máy ảo chỉ kéo nổi hơn 2Gbps

Vừa rồi mình nâng cấp switch Mikrotik và cặp card mạng Intel X520-DA2 10GbE cho cụm homelab Proxmox. Host vật lý chạy 12 VM phục vụ lab staging. Cắm cáp quang DAC xong, mình test ngay giữa Proxmox host và NAS TrueNAS: iperf3 báo về 9.41 Gbps mượt mà, đúng kỳ vọng.

Vấn đề chỉ lộ ra khi mình dựng một VM Ubuntu 22.04 chạy NGINX reverse proxy gánh tải cho cụm backend. Khi chạy bài test iperf3 từ máy trạm vào thẳng VM này qua Linux Bridge (vmbr0), băng thông khựng lại ở mức 1.8 – 2.2 Gbps. Mạng chậm đã đành, nhìn sang htop trên host thì tá hỏa: vCPU 0 của máy ảo chạm đỉnh 100% tải do tiến trình ksoftirqd/0 ăn trọn, trong khi 3 core còn lại gần như đứng chơi.

Vì sao VirtIO mặc định lại nghẽn cổ chai?

Soi kỹ bằng mpstat -P ALL 1 và kiểm tra I/O stack của QEMU, mình thấy card mạng ảo mặc định đang vướng hai nút thắt kinh điển:

1. Bẫy Single-Queue của virtio-net

Theo mặc định, card virtio-net chỉ có 1 cặp hàng đợi TX/RX duy nhất. Điều này dẫn tới hai hệ quả:

  • Toàn bộ ngắt mạng (IRQ) và gói tin vào/ra VM đều đổ dồn cho một core CPU duy nhất gánh.
  • Bạn cấp 4, 8 hay 16 vCPU cho máy ảo thì cũng vô ích. Một khi core xử lý ngắt kia chạm trần 100%, thông lượng toàn hệ thống lập tức bị bóp nghẹt. Ở lưu lượng tầm 300.000 đến 400.000 packets/sec (pps), single-queue sẽ kiệt sức ngay lập tức.

2. Chi phí chuyển ngữ cảnh (Context Switching) của QEMU Userspace

Mặc định, backend của virtio-net chạy trong userspace thông qua tiến trình QEMU. Đường đi của gói tin vì thế bị đội thêm nhiều chặng:

Host Kernel (NIC vật lý / Bridge) → QEMU Userspace Process → KVM Guest Kernel.

Gói tin phải nhảy liên tục giữa Kernel Space và User Space. Việc này làm phát sinh chi phí context switch cực lớn, kéo latency lên cao và tiêu hao chu kỳ CPU vô ích.

So sánh 3 hướng xử lý phổ biến

Khi gặp bài toán này, anh em quản trị hạ tầng thường cân nhắc ba phương án:

Phương án 1: PCI Passthrough hoặc SR-IOV

Gán thẳng card mạng vật lý hoặc Virtual Function (VF) vào máy ảo.

  • Điểm cộng: Chạm ngưỡng bare-metal (9.8 Gbps), độ trễ cực thấp.
  • Điểm trừ: Đánh đổi toàn bộ linh hoạt của ảo hóa. Bạn mất tính năng snapshot, không thể live migration sang node khác nếu thiếu cấu hình phần cứng tương đương, và card mạng phải có hỗ trợ SR-IOV.

Phương án 2: Bật module vhost-net trong Host Kernel

Chuyển việc xử lý gói tin từ tiến trình QEMU userspace xuống trực tiếp driver vhost_net trong nhân Linux của host.

  • Điểm cộng: Bỏ qua bước trung gian userspace, giảm ngay 25-30% CPU overhead trên host.
  • Điểm trừ: Nếu vẫn giữ 1 queue thì vCPU 0 vẫn sẽ nghẽn khi gặp bão traffic hoặc gói tin kích thước nhỏ (small packets).

Phương án 3: Kết hợp virtio-net Multiqueue và vhost-net

Đây là giải pháp cân bằng nhất giữa hiệu năng và độ ổn định. Chúng ta cấp nhiều cặp TX/RX queue tương ứng với số vCPU, đồng thời giao toàn bộ backend cho vhost-net ở kernel xử lý.

  • Điểm cộng: Phân bổ ngắt đều ra các vCPU, khai thác cạn đường truyền 10GbE nhưng vẫn giữ nguyên tính năng live migration và snapshot.

Hướng dẫn cấu hình thực chiến từng bước

Dưới đây là các bước thao tác trên KVM/libvirt (hoặc Proxmox VE) và cấu hình bên trong Guest OS.

Bước 1: Nạp module vhost_net trên Host

Mở terminal trên máy chủ KVM host và kiểm tra module:

# Kiểm tra xem module đã chạy chưa
lsmod | grep vhost_net

# Nạp module nếu chưa có
sudo modprobe vhost_net

# Thiết lập tự nạp khi reboot
echo "vhost_net" | sudo tee -a /etc/modules

Bước 2: Cấp Multiqueue và vhost backend cho máy ảo

Tắt máy ảo trước khi can thiệp. Mở file XML cấu hình máy ảo qua lệnh virsh edit:

virsh edit ubuntu-proxy

Tìm khối thẻ <interface> đang nối với bridge, bổ sung thuộc tính driver name='vhost' queues='N'. Kinh nghiệm thực tế: đặt số queue bằng số vCPU của VM (tối đa nên là 8 queues để tránh tranh chấp lock giữa các CPU core):

<devices>
  <interface type='bridge'>
    <mac address='52:54:00:1a:2b:3c'/>
    <source bridge='vmbr0'/>
    <model type='virtio'/>
    <!-- Khai báo vhost driver và 4 queues cho VM có 4 vCPU -->
    <driver name='vhost' queues='4'/>
    <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
  </interface>
</devices>

Mẹo với Proxmox VE: Bạn không cần sửa file tay. Tắt VM, vào phần Hardware > Network Device > Edit, chọn card VirtIO, tick vào ô Multiqueue rồi nhập đúng số vCPU (ví dụ: 4) là xong.

Bước 3: Mở khóa số queue bên trong Guest OS

Bật lại máy ảo. Trên Ubuntu, Debian hay RHEL đời mới, hệ điều hành thường tự nhận diện đa queue. Tuy nhiên, bạn vẫn nên kiểm tra lại bằng ethtool:

# Xác định tên card mạng bên trong VM
ip -br link

# Kiểm tra số queue phần cứng hỗ trợ vs đang chạy (ví dụ eth0)
sudo ethtool -l eth0

Nếu thông số Combined trong mục Current hardware settings vẫn hiển thị là 1, hãy gõ lệnh sau để mở đủ 4 channels:

sudo ethtool -L eth0 combined 4

Bước 4: Cân bằng ngắt mạng với irqbalance

Có nhiều queue rồi, nhưng nếu Linux không chia ngắt cho các core thì Multiqueue vẫn vô tác dụng. Hãy cài đặt irqbalance để nó tự rải ngắt đều qua các CPU core:

sudo apt update && sudo apt install -y irqbalance
sudo systemctl enable --now irqbalance

Để kiểm tra phân bổ ngắt trong lúc chạy test mạng, gõ:

watch -n 1 "grep virtio /proc/interrupts"

Lúc này, cột đếm ngắt của từng queue (virtio0-input.0, input.1, input.2, input.3) sẽ nhảy số song song trên cả 4 vCPU thay vì dồn cục vào CPU 0.

Kết quả đo đạc thực tế

Mình kích hoạt 4 luồng kiểm tra song song bằng lệnh iperf3 -c 192.168.10.5 -P 4:

  • Băng thông mạng: Tăng vọt từ 2.1 Gbps lên 9.42 Gbps, chạm sát ngưỡng trần lý thuyết của cổng 10GbE với MTU 1500 tiêu chuẩn.
  • Tải CPU máy ảo: Cả 4 vCPU chia sẻ mức tải đều nhau ở khoảng 25-30% cho tác vụ ngắt mạng. Không còn tình trạng ksoftirqd chiếm dụng 100% gây rớt gói như trước.
  • Tải CPU host: Nhờ vhost-net xử lý trực tiếp dưới kernel, mức chiếm dụng CPU tổng thể của tiến trình QEMU trên host giảm hơn 40%.
Share: