Cài đặt KVM/QEMU production-ready trên CentOS Stream 9: Bridge Network, SELinux và firewalld đúng cách

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Ảo hóa “chạy được” và ảo hóa “production-ready” là hai thứ rất khác nhau

Mình đã thấy không ít setup KVM trên server với cấu hình kiểu: SELinux để permissive, firewall tắt hoàn toàn, VM dùng NAT mặc định virbr0 của libvirt. Lý do? “Cài vậy cho nhanh, sau tính.” Vấn đề là cái “sau” đó thường đến vào lúc tệ nhất — khi VM cần nhận IP từ DHCP nội bộ, khi security audit, hoặc khi service trong VM không accessible từ ngoài dù firewall trông có vẻ đúng.

Công ty mình còn vài con server CentOS 7, và bài toán migrate sang AlmaLinux đã xử lý xong rồi. Song song đó, mình rebuild toàn bộ hạ tầng KVM từ đầu trên CentOS Stream 9 — bridge network thật sự, SELinux label đúng chỗ, firewalld FORWARD rule đầy đủ. Bài này là những gì rút ra từ quá trình đó, bao gồm cả vài lần VM “im lặng” không nhận IP mà nguyên nhân hóa ra chỉ là một dòng firewall bị thiếu.

Stack ảo hóa: KVM, QEMU, Libvirt — mỗi thứ làm gì?

Hay gặp nhất là cài xong mà không biết khi lỗi xảy ra thì tầng nào chịu trách nhiệm. Hiểu stack trước giúp troubleshoot nhanh hơn nhiều:

  • KVM: Module nhân Linux, biến CPU x86 thành hypervisor. Chỉ lo CPU và memory virtualization.
  • QEMU: Emulator xử lý I/O — disk, network card, USB… KVM + QEMU kết hợp cho hiệu năng gần native.
  • Libvirt: Management layer — cung cấp API thống nhất, daemon libvirtd, và CLI virsh.
  • Virt-Manager: GUI frontend cho libvirt, kết nối remote qua SSH — tiện để quản lý nhiều VM từ laptop.

CentOS Stream 9 là upstream của RHEL 9. Những gì vào CS9 hôm nay sẽ có mặt trong RHEL 9.x vài tháng sau. Kernel 5.14.x, KVM patches đầy đủ, lifecycle dài — stack này được support nghiêm túc.

Cài đặt KVM và các công cụ liên quan

Bước 1: Xác nhận CPU hỗ trợ hardware virtualization

# Kiểm tra flag vmx (Intel) hoặc svm (AMD)
grep -E --color=auto 'vmx|svm' /proc/cpuinfo | head -3

# Hoặc dùng virt-host-validate để check toàn diện hơn
virt-host-validate

Không có output từ lệnh grep? Vào BIOS bật VT-x (Intel) hoặc AMD-V trước. Không có hardware virtualization, KVM không chạy được — không có workaround nào hết.

Bước 2: Cài package group virtualization

# Cài đầy đủ package group ảo hóa
dnf install -y @virtualization

# Cài thêm Virt-Manager nếu host có GUI, hoặc dùng từ máy local
dnf install -y virt-manager virt-viewer

# Công cụ quản lý disk image VM
dnf install -y libguestfs-tools guestfs-tools

Package group @virtualization kéo theo qemu-kvm, libvirt, libvirt-client, virt-install và tất cả dependencies. Một lệnh, xong.

Bước 3: Bật libvirtd và kiểm tra KVM module

systemctl enable --now libvirtd
systemctl status libvirtd

# KVM module phải được load
lsmod | grep kvm
# Output mong đợi: kvm_intel (Intel) hoặc kvm_amd (AMD) + kvm

Cấu hình Bridge Network — bước quan trọng nhất

NAT network mặc định (virbr0) của libvirt chỉ dùng được cho lab cá nhân. VM sau NAT không nhận IP từ DHCP server nội bộ, và các port service không accessible trực tiếp từ bên ngoài. Production cần bridge network — VM xuất hiện trên mạng vật lý như một máy thật, lấy IP trực tiếp từ DHCP của router hoặc switch.

Tạo bridge với nmcli

Xem tên interface vật lý trước (ens3, eth0, enp2s0… tùy máy):

nmcli device status

Giả sử interface vật lý là ens3:

# Tạo bridge interface br0
nmcli connection add type bridge con-name br0 ifname br0

# Gắn card mạng vật lý vào bridge
nmcli connection add type ethernet slave-type bridge \
  con-name br0-ens3 ifname ens3 master br0

# Cấu hình IP tĩnh cho bridge (thay bằng IP thực của bạn)
nmcli connection modify br0 \
  ipv4.addresses "192.168.1.100/24" \
  ipv4.gateway "192.168.1.1" \
  ipv4.dns "8.8.8.8,8.8.4.4" \
  ipv4.method manual

# Tắt STP nếu không có switch loop (tăng tốc bring-up)
nmcli connection modify br0 bridge.stp no

# Activate
nmcli connection up br0
nmcli connection up br0-ens3

# Xác nhận
nmcli connection show --active
ip addr show br0

Cấu hình firewalld cho bridge traffic

Chỗ này hay bị bỏ sót nhất. Sau khi tạo bridge, firewalld cần biết interface này thuộc zone nào. Quan trọng hơn — cần rule FORWARD để traffic giữa VM và mạng ngoài đi qua được:

# Gán br0 vào zone (public hoặc trusted tùy mức độ tin tưởng mạng)
firewall-cmd --permanent --zone=public --add-interface=br0

# Rule FORWARD — thiếu cái này VM không ping ra ngoài được
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 \
  -i br0 -o br0 -j ACCEPT

# Nếu VM cần NAT ra internet qua host (trường hợp bridge ra mạng nội bộ không có internet)
firewall-cmd --permanent --zone=public --add-masquerade

firewall-cmd --reload
firewall-cmd --list-all --zone=public

Lỗi hay gặp nhất sau khi setup bridge: VM lấy được IP nhưng không ping ra ngoài. Kiểm tra ngay rule FORWARD — đó là nguyên nhân trong 9/10 trường hợp.

SELinux và KVM: Cấu hình đúng thay vì tắt đi

Mình hiểu tại sao người ta tắt SELinux — khi đang gấp và nó block thứ gì đó, setenforce 0 là phản xạ đầu tiên. Nhưng với hypervisor thì rủi ro khác hẳn: nếu có process escape từ VM, SELinux là lớp containment cuối cùng giữa attacker và toàn bộ host. Tắt đi là mất cái đó. Đọc audit log không tốn quá 5 phút.

SELinux context cho VM disk image

Libvirt mặc định lưu image tại /var/lib/libvirt/images/ — context virt_image_t đã đúng sẵn. Nếu dùng thư mục khác:

# Ví dụ dùng /data/vm-images
mkdir -p /data/vm-images
semanage fcontext -a -t virt_image_t "/data/vm-images(/.*)?" 
restorecon -Rv /data/vm-images

# Xác nhận context
ls -Z /data/vm-images

Xử lý SELinux denial khi gặp lỗi

# Xem denial gần đây liên quan đến qemu-kvm
ausearch -c 'qemu-kvm' --raw | audit2why

# Tạo policy module từ denial (thay vì tắt SELinux)
ausearch -c 'qemu-kvm' --raw | audit2allow -M my-qemukvm
semodule -X 300 -i my-qemukvm.pp

# Một số SELinux boolean hữu ích cho KVM
setsebool -P virt_use_nfs 1        # Nếu dùng NFS storage cho VM
setsebool -P virt_use_samba 1      # Nếu dùng Samba

Tạo VM và quản lý với Virt-Manager

KVM host chạy headless không phải vấn đề. Virt-Manager từ laptop connect vào qua SSH là đủ:

# Chạy trên máy local có GUI
virt-manager --connect qemu+ssh://[email protected]/system

Muốn spin up nhiều VM cùng lúc — ví dụ 10 VM cho test environment — CLI nhanh hơn nhiều so với click GUI từng cái:

virt-install \
  --name centos9-vm1 \
  --memory 2048 \
  --vcpus 2 \
  --disk path=/var/lib/libvirt/images/centos9-vm1.qcow2,size=20,format=qcow2 \
  --cdrom /tmp/CentOS-Stream-9-latest-x86_64-dvd1.iso \
  --network bridge=br0 \
  --os-variant centos-stream9 \
  --graphics vnc,listen=127.0.0.1 \
  --noautoconsole

Các virsh command cần dùng thường xuyên

# Danh sách VM
virsh list --all

# Start / Shutdown graceful / Force stop
virsh start centos9-vm1
virsh shutdown centos9-vm1
virsh destroy centos9-vm1

# Xem thông tin tài nguyên VM
virsh dominfo centos9-vm1

# Snapshot trước khi update
virsh snapshot-create-as centos9-vm1 snap-before-update \
  "Before system update" --disk-only

# Console (cần serial console trong VM)
virsh console centos9-vm1

Troubleshoot nhanh các lỗi hay gặp

Lỗi: “error: Failed to connect socket to ‘/var/run/libvirt/libvirt-sock'”

# Kiểm tra service
systemctl status libvirtd

# User phải thuộc group libvirt
usermod -aG libvirt $(whoami)
# Đăng xuất và đăng nhập lại, hoặc:
newgrp libvirt

Lỗi: VM không nhận được IP

# Bridge có gắn interface vật lý chưa?
brctl show br0

# Kiểm tra firewalld
firewall-cmd --list-all --zone=public

# Log libvirt để xem nguyên nhân
journalctl -u libvirtd --since "30 minutes ago" | grep -i 'error\|warn'

Kết luận

Cài KVM trên CentOS Stream 9 không phức tạp. Nhưng sự khác biệt giữa lab setup và production setup nằm gọn ở ba chỗ: bridge network thay vì NAT, SELinux label đúng thay vì tắt đi, và firewalld FORWARD rule để VM traffic đi được. Ba thứ này cộng lại chỉ mất thêm khoảng 20 phút so với cài mặc định, nhưng tiết kiệm nhiều giờ debug về sau.

Bước tiếp theo nếu muốn đi xa hơn: thêm VLAN trên bridge để isolate traffic giữa các nhóm VM, hoặc chuyển sang storage pool với LVM thay vì file image để cải thiện I/O performance. Nhưng đó là câu chuyện của sau khi foundation này đã chắc.

Share: