Vấn đề thực tế khi chạy KVM mà không có bridge
Mình chạy homelab với Proxmox VE quản lý 12 VM và container — playground để test mọi thứ trước khi đưa lên production. Trước khi chuyển sang Proxmox, mình dùng KVM thuần trên Ubuntu Server 22.04. Cái đau đầu đầu tiên: VM tạo xong, virt-manager báo đang dùng NAT, nhưng các VM không ping được nhau và không SSH được từ ngoài vào.
Nguyên nhân: KVM mặc định dùng NAT mode qua bridge ảo virbr0 do libvirt tạo — mặc định là dải 192.168.122.0/24, hoàn toàn tách biệt khỏi LAN của bạn. Tiện cho lab cá nhân, nhưng VM bị ẩn sau NAT. Muốn VM có IP thật trên LAN, ta cần tạo network bridge gắn vào card mạng vật lý và để VM dùng TAP interface kết nối vào bridge đó.
Bridge và TAP — Giải thích thẳng vào vấn đề
Tài liệu KVM thường giải thích hai khái niệm này riêng lẻ nên khi gặp lỗi rất khó biết debug ở tầng nào. Gom lại đây trước khi vào config.
Network Bridge (br0)
Bridge hoạt động như switch ảo Layer 2. Thêm card mạng vật lý (enp3s0) vào bridge, IP lúc này nằm trên br0 — không còn trên enp3s0 nữa. Bridge nhận traffic từ cả mạng vật lý lẫn các interface ảo gắn vào nó. VM kết nối vào bridge nhận IP từ DHCP router thật — giống hệt một máy tính cắm cáp mạng vào switch.
TAP Interface
TAP là virtual network interface hoạt động ở Layer 2 (Ethernet frames). Khi QEMU/KVM khởi động VM, nó tạo TAP interface (tap0, vnet0…) trên host. Một đầu TAP gắn vào bridge, đầu kia là card mạng ảo mà guest OS nhìn thấy.
Luồng dữ liệu: Guest NIC → TAP → Bridge → NIC vật lý → Router/LAN.
Tại sao không dùng TUN? TUN hoạt động Layer 3 (IP packets), còn VM cần Layer 2 để xử lý ARP, broadcast, và VLAN tagging. TAP mới đáp ứng được yêu cầu đó.
Cấu hình thực tế — Từng bước một
Bước 1: Xác định card mạng vật lý
ip link show
# Tìm tên interface đang có kết nối, ví dụ: enp3s0 hoặc eth0
ip addr show enp3s0
# Ghi lại IP hiện tại, subnet mask, gateway
Cảnh báo trước khi làm: sau khi thêm enp3s0 vào bridge, interface này mất IP ngay lập tức — IP chuyển sang br0. Nếu đang SSH qua enp3s0, kết nối đứt ngay khi chạy lệnh. Cách an toàn nhất là làm trực tiếp trên console, hoặc viết script gom toàn bộ lệnh rồi chạy một lần qua nohup sh bridge-setup.sh & — xong rồi SSH lại bằng IP mới trên br0.
Bước 2: Tạo bridge qua Netplan
Ubuntu Server dùng Netplan mặc định — đây là cách persistent, không mất cấu hình sau reboot. Backup trước:
sudo cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
Cấu hình DHCP:
# /etc/netplan/00-installer-config.yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: no # Tắt DHCP trên interface vật lý
dhcp6: no
bridges:
br0:
interfaces: [enp3s0] # Gắn enp3s0 vào bridge
dhcp4: yes # Bridge nhận IP từ DHCP
parameters:
stp: false # Tắt Spanning Tree (lab thường không cần)
forward-delay: 0
Hoặc IP tĩnh nếu router không có DHCP hoặc cần IP cố định:
bridges:
br0:
interfaces: [enp3s0]
addresses: [192.168.1.100/24]
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
parameters:
stp: false
forward-delay: 0
Áp dụng cấu hình:
sudo netplan apply
# Kiểm tra bridge đã tạo chưa
ip addr show br0
bridge link show
Bước 3: Tạo TAP interface thủ công (để hiểu cơ chế)
libvirt tự làm bước này khi khởi động VM. Nhưng làm thủ công một lần giúp hiểu rõ cơ chế — và debug nhanh hơn khi VM báo lỗi network:
# Tạo TAP interface
sudo ip tuntap add tap0 mode tap
# Bring up interface
sudo ip link set tap0 up
# Gắn TAP vào bridge
sudo ip link set tap0 master br0
# Kiểm tra — tap0 phải xuất hiện trong danh sách
bridge link show br0
Xóa sau khi test:
sudo ip link set tap0 nomaster
sudo ip tuntap del tap0 mode tap
Bước 4: Cấu hình libvirt để VM dùng bridge
Đây là cách mình dùng thường xuyên nhất — để libvirt quản lý TAP tự động:
# Tạo file XML định nghĩa network
cat << 'EOF' > /tmp/br0-network.xml
<network>
<name>br0-network</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>
EOF
# Đăng ký network với libvirt
virsh net-define /tmp/br0-network.xml
virsh net-start br0-network
virsh net-autostart br0-network
# Kiểm tra
virsh net-list --all
Gắn NIC của VM vào bridge. Cách thứ nhất — chỉnh qua virsh edit:
virsh edit vm-name
# Tìm phần <interface type='network'> và đổi thành:
# <interface type='bridge'>
# <source bridge='br0'/>
# <model type='virtio'/>
# </interface>
Hoặc gắn trực tiếp vào VM đang chạy:
virsh attach-interface --domain vm-name \
--type bridge \
--source br0 \
--model virtio \
--config --live
Bước 5: Khởi động VM và kiểm tra
virsh start vm-name
virsh console vm-name # Hoặc dùng virt-viewer
# Trong guest OS:
ip addr show # Phải thấy IP trên dải 192.168.1.x
ping 192.168.1.1 # Ping gateway
# Từ máy khác trong LAN:
ping <IP của VM> # Phải ping được
ssh user@<IP của VM> # SSH trực tiếp không qua NAT
Debug khi bridge không hoạt động
Bốn lỗi mình hay gặp nhất — và cách fix nhanh:
- VM không nhận IP qua DHCP: Chạy
bridge fdb show br0— TAP interface phải xuất hiện. Không thấy thì gắn thủ công:ip link set vnetX master br0. libvirt đôi khi tạo TAP nhưng quên gắn vào bridge sau khi restart host. - Mất SSH sau khi chạy
netplan apply: IP đã chuyển sangbr0. Lấy IP mới bằngip addr show br0trực tiếp trên console rồi SSH lại. - Packet bị drop dù bridge đúng: libvirt thêm rule iptables FORWARD. Kiểm tra
iptables -L FORWARD -n -v. Nếu policy DROP, thêm rule:iptables -I FORWARD -i br0 -j ACCEPT. - Bridge không forward packet dù rule iptables đúng: Kiểm tra
sysctl net.bridge.bridge-nf-call-iptables. Giá trị1nghĩa là bridge gọi iptables cho mọi packet — thử set về0:sysctl -w net.bridge.bridge-nf-call-iptables=0.
Tổng kết
Bridge + TAP là kiến trúc mạng nền tảng của hầu hết hypervisor phổ biến hiện nay — Proxmox VE, OpenStack Nova, hay libvirt trên Debian đều dùng nguyên lý này bên dưới, chỉ khác ở mức độ tự động hóa phần cấu hình.
Từ khi hiểu rõ luồng Guest NIC → TAP → Bridge → Physical NIC, mình giải quyết hầu hết ticket mạng VM trong vòng 10 phút. Lỗi thường chỉ nằm ở một trong ba điểm: TAP chưa gắn vào bridge, iptables block FORWARD, hoặc sysctl bridge-nf can thiệp.
Muốn đi sâu hơn, hai hướng đáng học tiếp: macvtap — VM dùng thẳng MAC layer của NIC vật lý, bỏ qua bridge, latency thấp hơn; hoặc SR-IOV — NIC vật lý tạo Virtual Functions cấp thẳng cho VM, performance gần bằng bare-metal. Với homelab thông thường thì bridge + TAP là đủ và dễ debug nhất.

