Real-time KVM tuning¶
Chạy VM cho workload cần độ trễ thấp và ổn định (xử lý tín hiệu, vRAN, điều khiển, trading): mục tiêu không phải thông lượng trung bình mà là độ trễ tối đa bị chặn (worst-case latency, jitter). Trang này gom các kỹ thuật dùng chung cho mọi VM kiểu đó, phần riêng cho gNodeB xem vRAN trên VM.
Cách làm là dành riêng một số core, bộ nhớ và thiết bị cho VM, đẩy mọi thứ khác (ngắt, kernel thread, tiến trình quản lý) sang các core còn lại. Đây vẫn là hypervisor tổng quát (KVM). Nếu cần cô lập cứng và dễ chứng nhận hơn thì xem Partitioning hypervisor.
Jitter đến từ đâu¶
| Nguồn | Biểu hiện | Hướng xử lý |
|---|---|---|
| Scheduler của host | vCPU bị preempt bởi thread khác, hoặc chuyển core | Cô lập core, pin vCPU |
| Ngắt và timer tick | Ngắt thiết bị, tick định kỳ rơi vào core của VM | irqaffinity, nohz_full, rcu_nocbs |
| Thread của QEMU (emulator, I/O) | Thread phụ chạy chung core với vCPU | Pin riêng emulator thread và iothread |
| TLB miss, page fault | Nested paging tốn page walk 2D, trang bị swap hoặc cấp phát muộn | Huge page 1G, khóa và cấp sẵn bộ nhớ |
| Truy cập bộ nhớ xa | Vượt NUMA node | Giữ vCPU, RAM và NIC trên cùng một node |
| Quản lý nguồn | C-state sâu, đổi tần số | Tắt C-state, cố định tần số |
| SMT | Hai hyper-thread chung core vật lý tranh tài nguyên | Tắt SMT hoặc không chia sibling cho bên khác |
| VM exit | Mỗi exit cộng thêm độ trễ (CPU Virtualization) | Passthrough, ngắt do phần cứng đưa thẳng vào guest |
| SMI (firmware) | Core bị dừng mà OS không thấy | Cấu hình BIOS, kiểm tra bằng hwlatdetect |
Cấu hình host¶
Cô lập core¶
Giả sử máy có 16 core, core 0-1 dành cho host (housekeeping), core 2-15 dành cho VM. Tham số kernel của host:
| Tham số | Tác dụng |
|---|---|
isolcpus=domain,managed_irq,... |
Bỏ các core khỏi scheduler domain (không cân bằng tải tiến trình vào đó) và tránh gửi managed IRQ của thiết bị tới chúng |
nohz_full=... |
Tắt tick định kỳ trên core khi chỉ có một task chạy. Cần ít nhất một core housekeeping để làm việc timekeeping |
rcu_nocbs=... |
Chuyển xử lý callback RCU sang kthread ở core khác |
irqaffinity=... |
Mặc định đẩy ngắt về các core housekeeping |
Ghi chú:
nohz_fullchỉ tắt tick, không thay thếisolcpushay cpuset, nên dùng cùng nhau.- Tắt
irqbalancehoặc cấu hình nó bỏ qua các core cô lập (IRQBALANCE_BANNED_CPULIST), đồng thời kiểm tra/proc/interruptsvà/proc/irq/*/smp_affinity_list. - Kernel thread (
kworker,ksoftirqd...) có thể vẫn xuất hiện trên core cô lập, dùngps -eLo psr,pid,commhoặc ftrace để tìm và gắn lại affinity. - Trên RHEL và dẫn xuất, profile
tunedđóng gói sẵn phần lớn cấu hình này (cpu-partitioning,realtime-virtual-host,realtime-virtual-guest).
Với yêu cầu khắt khe, bật cả PREEMPT_RT cho kernel của host và của guest (xem Linux Embedded về real-time). PREEMPT_RT làm thread kernel trên host đáng tin hơn, nhưng không thay thế được việc cô lập core.
BIOS và nguồn¶
- Bật VT-x/AMD-V, VT-d/IOMMU, SR-IOV.
- Tắt C-state sâu (hoặc đặt giới hạn bằng
processor.max_cstate=1 intel_idle.max_cstate=0trên kernel), dùng governorperformance, cố định tần số. - Tắt SMT, hoặc nếu giữ lại thì gán cả hai sibling của một core vật lý cho cùng một VM.
- Kiểm tra SMI bằng
hwlatdetect(rt-tests), vì SMI do firmware gây ra không có cách nào chặn từ OS.
Pin vCPU và emulator thread¶
Mỗi vCPU là một thread của QEMU (KVM & QEMU), nên pin vCPU là đặt CPU affinity cho thread đó. QEMU còn các thread khác: main loop (emulator), iothread, thread của vhost. Chúng không được chạy trên core của vCPU, nếu không sẽ tranh chấp ngay trên core đang chạy workload real-time.
Với libvirt:
<vcpu placement='static'>4</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<vcpupin vcpu='2' cpuset='4'/>
<vcpupin vcpu='3' cpuset='5'/>
<emulatorpin cpuset='0-1'/>
<iothreadpin iothread='1' cpuset='0-1'/>
<vcpusched vcpus='0-3' scheduler='fifo' priority='1'/>
</cputune>
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' dies='1' cores='4' threads='1'/>
<feature policy='require' name='invtsc'/>
</cpu>
emulatorpinvàiothreadpinđưa các thread phụ về core housekeeping.vcpuschedđặt chính sáchSCHED_FIFOcho vCPU thread. Chỉ cần khi trên cùng core còn thread khác cần bị đẩy nhường. Nếu core đã cô lập hoàn toàn và chỉ có vCPU chạy thì có thể bỏ. Cẩn thận: vCPUSCHED_FIFOchạy 100% có thể làm đói thread kernel cần thiết trên cùng core.host-passthroughđể guest thấy đúng CPU thật (flag, cache).invtsccho phép guest dùng TSC ổn định làm clocksource.
Nếu dùng QEMU trực tiếp, pin bằng taskset lên từng thread. Tên thread vCPU có dạng CPU 0/KVM:
Có thể kiểm tra kết quả bằng virsh vcpuinfo <vm> hoặc virsh emulatorpin <vm>.
Huge page 1G¶
Với nested paging, TLB miss tốn page walk hai tầng (Memory Virtualization). Huge page 1G giảm số lượng entry cần cache, và bộ nhớ cấp sẵn từ huge page không bị swap hay cấp phát muộn, nên độ trễ ổn định hơn.
Dành sẵn huge page lúc boot (cấp muộn dễ thất bại vì bộ nhớ bị phân mảnh), chia theo từng NUMA node:
# tham số kernel của host
default_hugepagesz=1G hugepagesz=1G hugepages=32
# hoặc cấp muộn theo từng node (dễ thất bại khi RAM đã phân mảnh)
echo 16 > /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages
Dùng cho VM trong libvirt:
<memoryBacking>
<hugepages>
<page size='1048576' unit='KiB' nodeset='0'/>
</hugepages>
<nosharepages/>
<locked/>
</memoryBacking>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
Với QEMU trực tiếp, dùng memory-backend-file trên hugetlbfs, cấp sẵn toàn bộ (prealloc=on):
mount -t hugetlbfs -o pagesize=1G none /dev/hugepages1G
qemu-system-x86_64 ... \
-object memory-backend-file,id=mem0,size=16G,mem-path=/dev/hugepages1G,share=on,prealloc=on,host-nodes=0,policy=bind \
-numa node,memdev=mem0 -m 16G
Cần lưu ý khi dùng huge page cho RT VM:
- Tắt Transparent Huge Page (khi cần,
transparent_hugepage=never) vì khi gom/chia trang có thể gây trễ, thay bằng huge page cấp tường minh. - Không dùng ballooning, KSM hay swap cho VM này (
<nosharepages/>,<locked/>). Xem các cơ chế này ở Memory Virtualization. - Huge page đã cấp cho VM thì không còn dùng cho việc khác, cần tính trước tổng dung lượng.
NUMA¶
Truy cập RAM hoặc thiết bị ở node khác tốn thêm độ trễ và dao động hơn. Nguyên tắc: vCPU, RAM và NIC của một VM nằm trên cùng một NUMA node.
lscpu | grep -i numa # core nào thuộc node nào
numactl -H # RAM mỗi node
cat /sys/bus/pci/devices/0000:3b:00.0/numa_node # NIC nằm ở node nào
- Chọn các core của VM từ node chứa NIC (hoặc accelerator) được gán cho nó.
- Ràng buộc bộ nhớ vào node đó (
<numatune>ở trên, hoặcpolicy=bindtrong QEMU). - Nếu VM bắt buộc vượt node, cấu hình topology NUMA cho guest khớp với host (
-numa node,...), để guest biết mà đặt bộ nhớ và thread cho đúng.
Cấu hình bên trong guest¶
Guest cũng là một Linux cần tuning như một máy real-time bình thường:
- Kernel parameter của guest:
isolcpus,nohz_full,rcu_nocbscho các vCPU dành cho workload, để lại vCPU 0 làm housekeeping. - Kernel
PREEMPT_RTnếu workload cần. - Huge page 1G trong guest (nếu app dùng, ví dụ DPDK), cấu hình tương tự host.
- Clocksource: với
invtsc,clocksource=tscthường ổn định hơnkvm-clock. Kiểm tracat /sys/devices/system/clocksource/clocksource0/current_clocksource. - Thread real-time dùng
SCHED_FIFO, khóa bộ nhớ bằngmlockall(), đặt affinity tường minh. - Không chạy dịch vụ không cần thiết trên vCPU cô lập, tắt
irqbalancetrong guest. - Hạn chế nguồn gây VM exit trên vCPU quan trọng: tránh truy cập thiết bị giả lập (PIT, HPET, đĩa emulated), ưu tiên thiết bị passthrough hoặc virtio với polling.
Điều cần nhớ: timer của guest vẫn gây VM exit ở mỗi lần hẹn giờ nếu không có hỗ trợ phần cứng. nohz_full trong guest giảm số lần đó vì tắt tick, nhưng không loại bỏ hoàn toàn. Vì thế cần đo, không nên chỉ tin cấu hình (xem phần dưới).
Đo bằng cyclictest trong guest¶
cyclictest (gói rt-tests) đo độ trễ từ lúc timer đáng lẽ đánh thức tới lúc thread thực sự chạy. Chạy trong guest, trên đúng các vCPU cô lập:
| Tham số | Ý nghĩa |
|---|---|
-m |
Khóa bộ nhớ (mlockall) |
-p 95 |
Ưu tiên SCHED_FIFO 95 |
-a 2-3 -t 2 |
Hai thread, gắn vào vCPU 2 và 3 |
-i 200 |
Chu kỳ 200 µs |
-D 1h |
Chạy 1 giờ |
-q -h 400 |
Chỉ in kết quả cuối, kèm histogram tới 400 µs |
Cách đọc và phương pháp:
- Max quan trọng hơn trung bình. Một lần vượt deadline đủ để hỏng.
- Chạy đủ lâu (nhiều giờ, lý tưởng qua đêm), vì sự kiện hiếm mới là thứ cần bắt.
- Chạy cùng lúc có tải:
stress-ngtrên core housekeeping, các VM khác, I/O mạng. Đo lúc rảnh không đại diện. - Đo baseline trên host (trên core cô lập tương ứng) trước, rồi đo trong guest. Chênh lệch giữa hai kết quả là chi phí ảo hóa, từ đó mới tìm nguyên nhân.
- Ngưỡng chấp nhận lấy từ deadline của ứng dụng, không có con số chung.
Khi thấy đỉnh trễ, xác định nó nằm ở đâu:
perf kvm stat record -a sleep 60 && perf kvm stat report # thống kê VM exit theo lý do
trace-cmd record -e kvm:kvm_exit -e kvm:kvm_entry -C 2 # theo dõi exit trên core 2 (host)
hwlatdetect --duration=300 # kiểm tra SMI/nhiễu từ firmware
Trong guest, các latency tracer của ftrace (xem Linux Embedded) giúp tìm lý do tắt preempt hay ngắt kéo dài.
Checklist¶
- BIOS: VT-x, VT-d, SR-IOV bật. C-state sâu, SMT tắt hoặc kiểm soát. Tần số cố định.
- Host:
isolcpus,nohz_full,rcu_nocbs,irqaffinity, tắtirqbalance. Huge page 1G dành sẵn. - VM: pin vCPU và emulator/iothread, bộ nhớ huge page cấp sẵn và khóa, ràng buộc NUMA,
host-passthrough. - Guest: cô lập vCPU, kernel (RT nếu cần), clocksource, huge page cho app.
- Đo:
cyclictestcó tải, so với baseline host, đọc max. - Nếu có thiết bị gán vào VM: kiểm tra IOMMU group, NUMA node của thiết bị, ngắt của thiết bị nằm đúng core.