Hướng dẫn cấu hình PCI Passthrough trên KVM/Proxmox: Chuyển GPU và card mạng thực cho máy ảo

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

Vấn đề thực tế gặp phải: Khi máy ảo cần nhiều hơn những gì hypervisor cung cấp

Tôi đã vận hành một homelab dùng Proxmox VE để quản lý hơn 12 máy ảo (VM) và container trong một thời gian dài. Đây là môi trường lý tưởng để tôi thử nghiệm mọi thứ trước khi triển khai vào môi trường sản xuất.

Trong số đó, có một vài máy ảo đòi hỏi hiệu năng thực sự, chứ không chỉ là ‘tạm được’. Ví dụ, tôi có một máy ảo Windows dùng để chơi game nhẹ hoặc làm việc đồ họa cơ bản. Một máy ảo khác chạy pfSense/OPNsense, và tôi muốn nó xử lý mạng với độ trễ cực thấp.

Ban đầu, tôi cứ nghĩ việc cấp cho máy ảo một GPU ảo hay card mạng ảo (virtio) là đủ. Nhưng rồi tôi nhanh chóng nhận ra giới hạn của cách làm này. Với máy ảo Windows, các card đồ họa ảo hóa (như SPICE/QXL) thường không đáp ứng được yêu cầu cho gaming hay các ứng dụng cần hiệu năng DirectX/OpenGL cao. Game thường giật lag, thậm chí không thể khởi chạy do thiếu driver tương thích. Các tác vụ render video cũng diễn ra rất chậm.

Đối với máy ảo router, dù virtio-net đã cung cấp hiệu suất khá tốt, tôi vẫn mong muốn nó đạt được hiệu suất tương đương phần cứng vật lý. Điều này đặc biệt quan trọng khi xử lý lưu lượng mạng lớn hoặc cần các tính năng đặc thù của card mạng (NIC).

Tóm lại, những gì hypervisor cung cấp qua cơ chế ảo hóa thường đủ cho các tác vụ server thông thường. Tuy nhiên, khi đối mặt với hiệu năng đồ họa chuyên sâu, gaming, hoặc các yêu cầu về độ trễ thấp và băng thông cao cho mạng, cơ chế này lại trở thành một rào cản đáng kể.

Phân tích nguyên nhân: Tại sao ảo hóa lại giới hạn hiệu năng thực của phần cứng?

Nguyên nhân chính của giới hạn hiệu năng nằm ở cách hypervisor tương tác với phần cứng. Khi một máy ảo khởi chạy, hypervisor (như KVM/Proxmox) tạo ra một lớp trừu tượng giữa máy ảo và phần cứng vật lý. Thay vì truy cập trực tiếp GPU hay card mạng, máy ảo chỉ có thể truy cập vào một thiết bị ảo do hypervisor mô phỏng. Ví dụ, với GPU, đó có thể là một thiết bị VGA ảo (QXL, SPICE) hoặc một card đồ họa paravirtualized. Đối với card mạng, đó là virtio-net.

Quá trình mô phỏng này tạo ra một số vấn đề:

  • Overhead: Mỗi khi máy ảo cần giao tiếp với phần cứng, yêu cầu đó phải đi qua hypervisor. Hypervisor có nhiệm vụ phiên dịch các lệnh từ máy ảo sang phần cứng vật lý và ngược lại, điều này gây ra độ trễ và tiêu tốn tài nguyên CPU của máy chủ vật lý.
  • Thiếu tính năng: Các thiết bị ảo hóa thường chỉ hỗ trợ các tính năng cơ bản. Ví dụ, một GPU ảo không có đầy đủ các tập lệnh đồ họa, bộ nhớ chuyên dụng hay khả năng xử lý song song như GPU vật lý. Điều này khiến cho các ứng dụng nặng về đồ họa, game không thể hoạt động hiệu quả, hoặc thậm chí không thể chạy.
  • Tương thích driver: Máy ảo phải sử dụng driver cho phần cứng ảo hóa. Driver này thường không được tối ưu bằng driver gốc của nhà sản xuất cho phần cứng vật lý. Hơn nữa, đôi khi chúng không tương thích với các phần mềm yêu cầu driver cụ thể, chẳng hạn như một số game có hệ thống anti-cheat phát hiện môi trường ảo hóa.
  • Độ trễ cao: Đây là yếu tố đặc biệt quan trọng với card mạng. Việc truyền dữ liệu qua lớp ảo hóa có thể làm tăng độ trễ, ảnh hưởng nghiêm trọng đến các ứng dụng nhạy cảm về thời gian như VoIP, game online, hoặc các hệ thống mạng yêu cầu hiệu năng cao.

Tóm lại, hypervisor mang lại tính linh hoạt và khả năng quản lý tài nguyên, nhưng đổi lại là sự đánh đổi về hiệu năng và khả năng truy cập trực tiếp phần cứng.

Các cách giải quyết: Từ chấp nhận đến tối ưu hóa

Khi đối mặt với những giới hạn này, tôi đã tìm kiếm các giải pháp để cải thiện:

1. Chấp nhận và tối ưu phần cứng ảo hóa

Đây là giải pháp mặc định và đơn giản nhất. Người dùng chỉ cần sử dụng các thiết bị ảo hóa mà KVM/Proxmox cung cấp (như virtio-net, virtio-blk, SPICE/QXL cho đồ họa). Để tối ưu, có thể:

  • Cấp thêm RAM và CPU cho máy ảo.
  • Sử dụng driver VirtIO mới nhất bên trong máy ảo.
  • Chọn chế độ hiển thị hiệu quả nhất (ví dụ: SPICE thường tốt hơn VNC).

Ưu điểm: Dễ cài đặt, không cần phần cứng đặc biệt, linh hoạt (có thể live migrate VM).
Nhược điểm: Vẫn không thể vượt qua giới hạn về hiệu năng đồ họa và độ trễ mạng như đã phân tích. Không phù hợp cho chơi game đồ họa nặng, ứng dụng đồ họa chuyên nghiệp, hoặc các ứng dụng mạng yêu cầu cao.

2. Sử dụng SR-IOV (Single Root I/O Virtualization)

SR-IOV là một công nghệ cho phép một thiết bị PCI vật lý (ví dụ: card mạng) tạo ra nhiều chức năng ảo (Virtual Functions – VFs) độc lập cho các máy ảo. Mỗi VF có thể được gán trực tiếp cho một máy ảo, giúp máy ảo giao tiếp trực tiếp với phần cứng và bỏ qua hypervisor cho các tác vụ I/O.

Ưu điểm: Hiệu năng gần như tương đương native cho các thiết bị mạng, đồng thời giảm tải CPU cho máy chủ vật lý.
Nhược điểm: Yêu cầu phần cứng hỗ trợ (card mạng, bo mạch chủ, CPU). Cấu hình phức tạp hơn. Công nghệ này không áp dụng được cho GPU một cách trực tiếp và phổ biến như với card mạng. Theo kinh nghiệm của tôi, SR-IOV khá kén chọn và kém linh hoạt hơn so với PCI Passthrough tổng thể.

3. PCI Passthrough (VFIO) – Giải pháp tối ưu

Đây chính là giải pháp tôi đã áp dụng và hoàn toàn hài lòng. PCI Passthrough (còn gọi là VFIO – Virtual Function I/O) cho phép bạn gán trực tiếp một thiết bị PCI vật lý (như GPU, card mạng, bộ điều khiển USB, SSD NVMe) cho một máy ảo. Khi đó, máy ảo sẽ xem thiết bị đó như thể nó được cắm trực tiếp vào chính nó, bỏ qua hoàn toàn hypervisor để giao tiếp trực tiếp.

Ưu điểm:

  • Hiệu năng native: Máy ảo có thể sử dụng toàn bộ sức mạnh và tính năng của thiết bị vật lý, bao gồm cả driver gốc của nhà sản xuất.
  • Độ trễ thấp: Giảm đáng kể độ trễ cho các tác vụ I/O.
  • Tương thích tốt: Giải quyết vấn đề tương thích driver và các tính năng đặc thù mà ảo hóa không cung cấp được.

Nhược điểm:

  • Thiết bị được passthrough sẽ không còn khả dụng cho máy chủ vật lý (host) hoặc các máy ảo khác.
  • Yêu cầu phần cứng host hỗ trợ IOMMU (Intel VT-d hoặc AMD-Vi).
  • Cấu hình phức tạp hơn một chút.
  • Không thể live migrate VM có thiết bị passthrough.

Với những yêu cầu về hiệu năng đồ họa và mạng cho homelab của tôi, PCI Passthrough là lựa chọn không thể thay thế.

Cách tốt nhất: Hướng dẫn cấu hình PCI Passthrough trên KVM/Proxmox

Sau nhiều lần thử nghiệm và điều chỉnh, dưới đây là các bước tôi đã thực hiện để cấu hình PCI Passthrough thành công trên Proxmox (dựa trên KVM).

Bước 1: Kiểm tra khả năng tương thích phần cứng (IOMMU)

Đầu tiên, bạn cần đảm bảo CPU và bo mạch chủ của máy chủ vật lý hỗ trợ IOMMU (Intel VT-d hoặc AMD-Vi). Đây là công nghệ cho phép hypervisor gán trực tiếp thiết bị PCI cho máy ảo.

Kiểm tra CPU:

  • Intel: Tra cứu thông số kỹ thuật của CPU để kiểm tra hỗ trợ “Intel VT-d”.
  • AMD: Tương tự, kiểm tra xem CPU có hỗ trợ “AMD-Vi” (còn gọi là AMD IOMMU) không.

Kiểm tra bo mạch chủ: Đảm bảo BIOS/UEFI của bạn có tùy chọn để bật VT-d/AMD-Vi.

Bước 2: Kích hoạt IOMMU trong BIOS/UEFI

Khởi động lại máy chủ Proxmox của bạn, vào BIOS/UEFI và tìm các tùy chọn sau để bật:

  • Intel: Virtualization Technology for Directed I/O (VT-d), hoặc Intel VT-d.
  • AMD: AMD IOMMU, hoặc SVM Mode (đôi khi IOMMU nằm trong phần này).

Lưu lại cài đặt và khởi động lại.

Bước 3: Kích hoạt IOMMU trong Kernel của Proxmox (Host OS)

Sau khi kích hoạt trong BIOS, bạn cần thông báo cho kernel Linux biết để sử dụng IOMMU. Tôi thường thực hiện điều này bằng cách chỉnh sửa file GRUB.

Mở file cấu hình GRUB:

nano /etc/default/grub

Tìm dòng bắt đầu bằng GRUB_CMDLINE_LINUX_DEFAULT. Thêm các tham số sau vào cuối dòng (bên trong dấu ngoặc kép):

  • Đối với Intel: intel_iommu=on iommu=pt
  • Đối với AMD: amd_iommu=on iommu=pt

Ví dụ, với Intel, dòng của tôi sẽ trông giống thế này:

GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"

Lưu file (Ctrl+O, Enter, Ctrl+X) và cập nhật GRUB:

update-grub

Cuối cùng, khởi động lại máy chủ Proxmox:

reboot

Sau khi khởi động lại, kiểm tra xem IOMMU đã được kích hoạt chưa:

dmesg | grep -e DMAR -e IOMMU
find /sys/kernel/iommu_groups/ -type l

Nếu bạn thấy các thông báo liên quan đến DMAR/IOMMU và có output từ lệnh find, nghĩa là IOMMU đã hoạt động.

Bước 4: Tìm ID thiết bị PCI và nhóm IOMMU

Để passthrough một thiết bị, bạn cần biết ID của nó và đảm bảo thiết bị đó nằm trong một nhóm IOMMU riêng biệt. Đây là một trong những bước quan trọng nhất.

Liệt kê tất cả các thiết bị PCI:

lspci -nnk

Tìm thiết bị bạn muốn passthrough (ví dụ: GPU của bạn, hoặc card mạng thứ hai). Ghi lại ID của thiết bị, thường có dạng xxxx:xxxx (Vendor ID: Device ID). Ví dụ, một card đồ họa NVIDIA có thể là 10de:1c03.

Quan trọng: GPU thường đi kèm với thiết bị audio (HDMI Audio), bạn cần passthrough cả hai thiết bị trong cùng một nhóm IOMMU để nó hoạt động đúng cách.

Để kiểm tra nhóm IOMMU của thiết bị:

for d in $(find /sys/kernel/iommu_groups/*/devices -type l | sort -V); do 
    n=${d#*/iommu_groups/*}; n=${n%%/*}; 
    printf 'IOMMU Group %s: %s
' "$n" "$(lspci -nns "$(basename "$d")")"; 
done

Bạn cần đảm bảo rằng tất cả các thiết bị bạn muốn passthrough (ví dụ: GPU và thiết bị audio đi kèm của nó) nằm trong **CÙNG MỘT** nhóm IOMMU. Nếu chúng nằm trong các nhóm khác nhau hoặc có các thiết bị không liên quan trong cùng nhóm, bạn có thể gặp vấn đề. Tình trạng này thường xảy ra trên một số bo mạch chủ và có thể yêu cầu áp dụng các patch kernel (ví dụ: pcie_acs_override). Tuy nhiên, tôi khuyên bạn nên tránh sử dụng các patch này nếu có thể, vì chúng tiềm ẩn rủi ro bảo mật.

Bước 5: Tách thiết bị khỏi Host (Blacklist Driver và Bind to VFIO-PCI)

Để máy ảo có thể sử dụng thiết bị, máy chủ Proxmox không được sử dụng driver cho thiết bị đó. Chúng ta sẽ blacklist các driver liên quan và gán thiết bị cho module vfio-pci.

Tạo hoặc chỉnh sửa file blacklist:

nano /etc/modprobe.d/blacklist.conf

Thêm các dòng sau, thay nvidia, nouveau, radeon, amdgpu bằng driver tương ứng của GPU bạn (hoặc driver của card mạng nếu passthrough NIC):

blacklist nouveau
blacklist nvidia
blacklist radeon
blacklist amdgpu
blacklist snd_hda_intel # Nếu passthrough GPU có HDMI Audio

Tiếp theo, tạo file cấu hình để gán thiết bị cho vfio-pci:

nano /etc/modprobe.d/vfio.conf

Thêm dòng sau, thay xxxx:xxxxyyyy:yyyy bằng Vendor ID: Device ID của GPU và thiết bị audio đi kèm (hoặc card mạng). Ví dụ, nếu GPU là 10de:1c03 và audio là 10de:10f1:

options vfio-pci ids=10de:1c03,10de:10f1 disable_vga=1

disable_vga=1 là cần thiết nếu bạn passthrough GPU chính. Nếu bạn có nhiều GPU và chỉ passthrough GPU phụ, có thể không cần.

Thêm module vfio-pci vào danh sách module được tải lúc khởi động:

echo "vfio_pci" >> /etc/modules
echo "vfio_iommu_type1" >> /etc/modules
echo "vfio" >> /etc/modules
echo "kvm_intel" >> /etc/modules # hoặc kvm_amd

Cập nhật initramfs và khởi động lại:

update-initramfs -u -k all
reboot

Sau khi khởi động lại, kiểm tra xem module vfio-pci đã được gán cho thiết bị của bạn chưa:

lspci -nnk | grep -i vfio

Bạn sẽ thấy Kernel driver in use: vfio-pci cho các thiết bị bạn đã cấu hình.

Bước 6: Thêm thiết bị vào Máy ảo trên Proxmox

Giờ là lúc thêm thiết bị vào máy ảo. Tôi thường dùng giao diện web của Proxmox vì nó tiện lợi hơn đáng kể so với việc chỉnh sửa file XML của KVM trực tiếp.

  1. Truy cập giao diện web Proxmox.
  2. Chọn máy ảo bạn muốn passthrough thiết bị.
  3. Vào mục Hardware.
  4. Nhấn Add > PCI Device.
  5. Trong cửa sổ mới, chọn thiết bị PCI bạn muốn từ danh sách (Proxmox sẽ tự động hiển thị các thiết bị đã được gán cho vfio-pci).
  6. Nếu passthrough GPU, hãy chọn Primary GPU nếu đây là GPU chính cho máy ảo và All Functions. Tích chọn ROM-Bar nếu cần (Lưu ý: Một số GPU cũ có thể cần tùy chọn này, nhưng GPU mới thường không).
  7. Nhấn Add.

Sau khi thêm, khởi động máy ảo. Tôi từng gặp trường hợp máy ảo Windows hiển thị màn hình đen khi passthrough GPU lần đầu. Thông thường, việc thiết lập GPU chính (Primary GPU) và đôi khi tắt card đồ họa ảo mặc định của Proxmox (như display: std) trong cấu hình máy ảo sẽ giải quyết vấn đề này.

Bước 7: Cài đặt Driver trong Máy ảo

Bên trong máy ảo, bạn sẽ thấy thiết bị PCI mới. Với Windows, nó sẽ xuất hiện trong Device Manager. Bạn chỉ cần tải và cài đặt driver chính thức từ nhà sản xuất (NVIDIA, AMD, Intel, Realtek…) như khi cài đặt trên một máy vật lý thông thường. Sau khi cài đặt driver, khởi động lại máy ảo và bạn sẽ có thể tận hưởng hiệu năng tối ưu của thiết bị.

Kinh nghiệm cá nhân và những lưu ý

Quá trình này không phải lúc nào cũng suôn sẻ ngay lập tức. Tôi từng khá vất vả với chiếc RX 580 khi nó mãi không nhận driver trong máy ảo Windows. Hóa ra, nguyên nhân là do BIOS của bo mạch chủ cũ không hỗ trợ IOMMU tốt, hoặc thiết bị audio của GPU bị tách ra khỏi nhóm IOMMU chính. Đôi khi, tôi phải thử nghiệm với các phiên bản BIOS khác nhau hoặc thêm các tham số kernel như pcie_acs_override=downstream,multifunction (nhưng hãy cẩn thận với tùy chọn này).

Một điểm quan trọng khác là nếu bạn passthrough GPU, máy chủ Proxmox sẽ không thể sử dụng GPU đó để xuất hình ảnh ra màn hình console nữa. Bạn sẽ phải quản lý máy chủ qua SSH hoặc giao diện web. Điều này hoàn toàn chấp nhận được đối với một máy chủ không có màn hình.

Sau 6 tháng vận hành ổn định một máy ảo Windows với RTX 2060 được passthrough và một máy ảo OPNsense với card mạng Intel dual-port được passthrough, tôi có thể khẳng định hiệu năng là vượt trội. Máy ảo Windows của tôi chạy game mượt mà, render video nhanh chóng, không khác gì một máy vật lý. Máy ảo OPNsense thì xử lý lưu lượng mạng rất hiệu quả, với độ trễ tối thiểu.

PCI Passthrough là một tính năng mạnh mẽ, biến máy chủ ảo hóa của bạn thành một nền tảng đa năng, có thể đáp ứng cả những nhu cầu khắt khe nhất về hiệu năng phần cứng. Dù ban đầu quá trình cấu hình có thể hơi phức tạp, nhưng thành quả nhận được hoàn toàn xứng đáng.

Share: