搞虚拟机最让人头疼的问题之一,就是网络。我刚开始用KVM那会儿,默认的网络配置是NAT,虚拟机虽然能上网,但是别人从外面连不进来,想开放一个服务还要在宿主机上做端口映射,机器一多,映射关系写在纸上的都有。后来把Bridge Networking这块研究明白,才觉得把KVM真正打通了。这篇文章就来完整复盘一遍“Create and Configure Bridge Networking For KVM in Linux”这件事:什么是Linux bridge、为什么需要它、怎么从零搭建、怎么让KVM虚拟机接入、出问题了怎么排查。不管你是刚接触KVM的新手,还是在帮公司搭虚拟化环境的老手,文章里的操作方法和思路都能直接用上。
1. 先搞清楚:桥接网络到底解决了什么问题
1.1 默认KVM的NAT网络,坑在哪里
装好KVM之后,libvirt默认会创建一张虚拟网卡叫virbr0,同时生成一个virtual network叫default,默认网段一般是192.168.122.0/24。虚拟机创建的时候,选“NAT”或者“Virtual network default”,拿到的就是从这张虚拟网络分配出来的IP。这套方案用的是Linux内核的iptables NAT功能:虚拟机出网的包,源地址会被伪装成宿主机的IP。
NAT方案最让人抓狂的地方,是被访问能力太弱。你在虚拟机里起了一个web服务,想给局域网同事试用,就得在宿主机上配端口转发,比如把宿主机的8080端口转发到虚拟机的80端口,而且每个要对外开放的服务都得单独转一次。虚拟机少的时候,记在文档里还能忍;虚拟机一多,经常是昨天还能访问的服务,今天不知道哪条iptables规则被覆盖了,排查到怀疑人生。
另外,NAT下的虚拟机没法直接和局域网里的物理机通信。虽然有些场景可以靠端口转发绕过去,但一旦涉及广播、组播、混杂模式这类应用——比如做软路由测试、抓广播包、跑集群的组播心跳——NAT基本上就废了。这也是为什么很多人在认真部署KVM之前,第一件事就是先把桥接网络搞定。
1.2 Linux bridge:把内核变成一台交换机
理解Linux bridge最直接的方式,就是把它看作一台虚拟交换机。你可以在任意一台Linux机器上,把内核里的一部分网络功能抽出来,当作一台二层交换机来用。物理网卡、虚拟机的虚拟网卡,都是这台交换机上的接入端口,它们处在同一个广播域里,直接通过ARP广播就能找到对方。
桥接之后,整个链路是这样一个结构:
text复制虚拟机(eth0) -> vnet0 -> br0 <- eth0 <- 局域网交换机 <- 其他机器
br0这个逻辑接口,承担了宿主机自己的网络身份,IP、MAC、路由表都在它上面。物理网卡eth0不再是独立的网络接口,它变成了br0的一个member端口,本身不配IP,只管收发二层帧。这个过程官方文档里叫enslave,把物理网卡拉进网桥做从属。
这个机制的好处主要有三个。第一,虚拟机可以获得局域网DHCP分配的IP,或者手动配置一个和宿主机同网段的IP,从外部网络看就是一台独立的物理机。第二,不需要端口转发,SSH、HTTP、数据库、SMB这些服务全都走正常流程,该通的协议都可以通。第三,二层畅通,广播包、组播包都能正常收发,很多依赖二层的服务终于能跑了。
1.3 什么时候用桥接,什么时候用NAT
我实际项目里的选择逻辑很简单,按下面这张表判断:
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 虚拟机只需要上外网、不需要被外部访问 | NAT | 配置最简单,隔离性最好 |
| 虚拟机要对外提供Web/SSH/API服务 | 桥接 | 免端口转发,直接局域网可达 |
| 多台宿主机做虚拟化集群 | 桥接 | 跨宿主机需要二层互通 |
| 内网已有DHCP/DNS/域控 | 桥接 | 虚拟机直接入网,纳入统一管理 |
| 学习练手、快速测试 | NAT | 不容易把宿主机网络搞挂 |
注意:桥接和NAT不是二选一。一台宿主机可以同时有br0(桥接)和virbr0(NAT)两个虚拟网络,不同虚拟机按业务需求接入不同的桥。网桥之间默认不互相转发,隔离上也比较放心。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前准备:环境检查与网络规划
2.1 检查内核模块和依赖工具
Linux bridge功能在内核里,绝大多数发行版默认已经加载,但为确保万无一失,先跑几个命令看一眼状态:
bash复制lsmod | grep bridge
lsmod | grep br_netfilter
ip link add test_bridge type bridge && ip link del test_bridge
第一条检查bridge模块,第二条检查br_netfilter,第三条是临时创建一个bridge再删掉,用来验证内核是否真正支持创建操作。
如果模块没加载,手动加载一下:
bash复制sudo modprobe bridge
sudo modprobe br_netfilter
br_netfilter这个模块很容易被忽视,它的作用是让iptables规则能作用于桥接流量。没有它,桥接流量默认绕过了iptables的FORWARD链,在某些版本和配置组合下,虚拟机之间连不上,或者宿主机上的Docker也会出问题。所以我的习惯是装完KVM后先把这两个模块都确认一遍。
2.2 安装网络管理工具
桥接管理工具的选择主要看发行版和个人习惯,我用的组合大概是这样的:
- Debian/Ubuntu经典方式:bridge-utils(提供brctl命令),配合ifupdown(/etc/network/interfaces)
- Ubuntu新版本:netplan.io,用YAML配置,内部调networkd或者NetworkManager
- CentOS/RHEL/Fedora:NetworkManager + nmcli,或者老版本CentOS 7的network-scripts
安装命令因为发行版不同略有差别,统一列一下:
bash复制# Debian / Ubuntu
sudo apt update
sudo apt install -y bridge-utils netplan.io iproute2
# CentOS / RHEL / Rocky / AlmaLinux
sudo yum install -y bridge-utils iproute
工具不在多,够用就行。我个人建议至少装一个bridge-utils,因为brctl show查看网桥成员时输出非常直观,比ip命令更友好,排查问题时一眼就能看清物理网卡和虚拟网卡有没有进到桥里。
2.3 网络信息记录与规划
在改动任何网络配置之前,先把当前状态完整记录下来。这一步看似多余,实际是止损的关键。我现在帮客户排查网络问题时,第一件事就是记录现场信息:
bash复制ip addr show
ip route show
cat /etc/resolv.conf
需要记录的无非三样:
- 物理网卡名称:这个极关键,不同机器千奇百怪,eth0、ens33、enp2s0、em1都有。
- 当前IP地址、子网掩码、网关IP。
- DNS服务器地址。
规划上有一条铁律:新创建的br0,其IP、网关、DNS要和原物理网卡完全一致,这样桥接后宿主机的网络不会断。尤其是远程管理的服务器,这个IP一旦配错,SSH直接掉线,只能去机房控制台或者IPMI上重新配,非常痛苦。如果准备给虚拟机预留一段独立IP(比如192.168.1.200到192.168.1.220),可以提前标记好,避免将来虚拟机重复占用宿主机的IP。
3. 实操:三种创建桥接网络的方法
3.1 用brctl命令做临时测试
最快的验证方式,是直接用命令行创建临时网桥,不改任何配置文件,系统重启就消失。这种方式适合用来验证硬件和链路是否正常,也适合在正式落配置前先做一轮快速验证。
bash复制sudo brctl addbr br0
sudo brctl addif br0 eth0
sudo ip addr del 192.168.1.100/24 dev eth0
sudo ip addr add 192.168.1.100/24 dev br0
sudo ip link set br0 up
sudo ip route add default via 192.168.1.1 dev br0
这里每一步的用意我说明一下:
brctl addbr br0:创建虚拟网桥br0,相当于放好了一个交换机,但还没插网线。brctl addif br0 eth0:把物理网卡eth0加进网桥,变成网桥的一个端口。ip addr del 192.168.1.100/24 dev eth0:把eth0上的IP去掉。物理网卡一旦变成网桥的成员端口,就不能再持有IP,否则会出现地址冲突。ip addr add 192.168.1.100/24 dev br0:把原IP挪到br0上,让宿主机继续以这个IP对外通信。ip link set br0 up:让网桥进入up状态。ip route add default via 192.168.1.1 dev br0:重新添加默认路由。
我一般还会顺手确认一下DNS,因为删除物理网卡IP后resolv.conf里可能残留旧配置:
bash复制sudo sh -c 'echo "nameserver 223.5.5.5" > /etc/resolv.conf'
验证:
bash复制brctl show br0
ping -c 3 192.168.1.1
ping -c 3 223.5.5.5
能通,说明物理链路、内核模块、网卡驱动都没问题。接着你就能用这套临时桥接去创建一台测试虚拟机,确认虚拟机从局域网DHCP拿地址或者手动配IP后能互访。测试确认OK,再去做持久化配置,这样能最大程度减少改坏配置文件的风险。
提示:命令行临时桥接有一个大坑,系统一旦重启,网络就没了,因为物理网卡的IP已经被删掉,而br0又没有写入任何配置文件。所以临时测试务必在非生产环境的机器上做,最好用tmux或screen挂一个会话,随时能恢复。
3.2 持久化配置文件:Debian/Ubuntu经典写法
确定临时方案没问题后,再把配置写入/etc/network/interfaces。Debian 10、Ubuntu 20.04这些版本都认这套写法:
bash复制auto lo
iface lo inet loopback
auto br0
iface br0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 223.5.5.5 114.114.114.114
bridge_ports eth0
bridge_stp off
bridge_fd 0
bridge_maxwait 0
修改后重启网络服务:
bash复制sudo systemctl restart networking
或者更准确一点,只操作相关接口:
bash复制sudo ifdown eth0 && sudo ifup br0
几个bridge参数解释一下:
bridge_ports:要让哪些物理接口加入网桥,多个接口用空格分隔。bridge_stp:生成树协议,默认建议off。只有网络里确实存在环路风险时才需要开,开了反而会带来几十秒的收敛延迟。bridge_fd:forward delay,即端口从blocking到forwarding的等待时间,设为0可以加快启动速度。bridge_maxwait:等待物理接口就绪的时间,保持默认或设0即可。
3.3 Ubuntu新版本用netplan
Ubuntu 18.04之后,netplan成了默认网络配置工具。它用YAML描述配置,语法比interfaces好读,但配置格式一旦写错,apply的时候就会报错甚至断网。我的习惯是先把配置文件写好,用netplan generate做语法检查,最后才netplan apply。
假设现有物理网卡是enp3s0,原IP是192.168.1.100,配置文件示例:
yaml复制network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: false
bridges:
br0:
interfaces: [enp3s0]
addresses: [192.168.1.100/24]
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [223.5.5.5, 114.114.114.114]
parameters:
stp: false
forward-delay: 0
执行:
bash复制sudo netplan generate
sudo netplan apply
如果一切正常,ip a s br0能看到IP,路由和DNS也按规划生效。关于renderer,我强烈建议用networkd,因为NetworkManager在某些版本上和netplan配合时,bridge的stp参数会被忽略,而且networkd在纯服务器环境下更干净。桌面版Ubuntu默认renderer可能还是NetworkManager,如果不想动桌面联网功能,可以保留,但一定要做测试。
3.4 用nmcli操作NetworkManager创建桥接
CentOS、RHEL、Fedora这些系统,只要NetworkManager在运行,用nmcli创建桥接是比较省心的方案。命令语义清楚,还能自动管理连接状态:
bash复制sudo nmcli con add type bridge ifname br0 con-name br0
sudo nmcli con modify br0 ipv4.addresses 192.168.1.100/24
sudo nmcli con modify br0 ipv4.gateway 192.168.1.1
sudo nmcli con modify br0 ipv4.dns "223.5.5.5 114.114.114.114"
sudo nmcli con modify br0 ipv4.method manual
sudo nmcli con add type ethernet ifname eth0 master br0 con-name bridge-slave-eth0
sudo nmcli con up br0
这里有一个细节:master br0参数会自动把eth0设置为网桥的端口,同时停用eth0原本的IP配置。所以桥接创建完成后,不需要手动去删eth0上的IP,NetworkManager在内部已经处理好了。
在执行nmcli con add type ethernet那一步时,系统可能会自动激活eth0原来绑定的连接,导致网络闪断一下。这是正常现象,不用慌,等br0连接起来就恢复了。查看状态:
bash复制nmcli connection show
brctl show br0
NetworkManager的连接名和接口名不一定一样,用nmcli -f NAME,DEVICE connection show可以看得更清楚。
4. 让KVM虚拟机真正用上桥接网络
4.1 用virt-manager图形化配置
如果习惯图形界面,virt-manager创建虚拟机时,在选择虚拟网络那一步会看到几个选项:
- Virtual network 'default':NAT
- Bridge device:直接连接到物理网络
- Host device:直接分配物理网卡给虚拟机
选Bridge device,在下拉框里找到br0,然后继续。网卡型号建议选virtio,性能最好。如果虚拟机上跑的是Windows,没装virtio驱动之前可能会认不出网卡,可以先选e1000,装好驱动再改回virtio。
对已存在的虚拟机调整网卡,操作也不复杂:关机,打开设置,选择网卡设备,把Network source改成Bridge device,指定br0,保存启动。virt-manager的界面会把桥接设备自动列出来,点选就行。
4.2 用virsh命令行配置
virsh是KVM的标配管理工具,脚本化配置和批量操作基本都靠它。要给现有虚拟机修改网卡设备,执行:
bash复制virsh edit <vm-name>
把interface段改成下面的样子:
xml复制<interface type='bridge'>
<mac address='52:54:00:aa:bb:cc'/>
<source bridge='br0'/>
<model type='virtio'/>
</interface>
也可以用命令直接为运行中的虚拟机动态添加一块桥接网卡:
bash复制virsh attach-interface <vm-name> bridge br0 --model virtio --config --live
--config表示持久化到磁盘配置,--live表示立即生效。两个参数一起加,重启虚拟机后配置也不会丢。
如果虚拟机还没创建,用virt-install可以一步到位:
bash复制virt-install \
--name testvm \
--ram 2048 \
--disk path=/var/lib/libvirt/images/testvm.qcow2,size=20 \
--os-variant ubuntu20.04 \
--network bridge=br0,model=virtio \
--graphics none \
--location /path/to/iso
--network bridge=br0,model=virtio这项直接指定虚拟机的网卡接在br0上,装完系统后虚拟机天然就在桥接网络里。
4.3 使用libvirt的bridge类型网络定义
libvirt自己也在管理虚拟网络,默认的default就是一个NAT网络。为了让virt-manager里能直接看到一个叫br0-network的选项,也可以把br0注册成libvirt网络:
创建文件bridge-network.xml:
xml复制<network>
<name>br0-network</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>
注册并启动:
bash复制sudo virsh net-define bridge-network.xml
sudo virsh net-start br0-network
sudo virsh net-autostart br0-network
注册之后,虚拟机XML里的网络引用可以写成:
xml复制<interface type='network'>
<source network='br0-network'/>
<model type='virtio'/>
</interface>
说实话,我自己其实更常用type='bridge',因为语义直接。不过,如果管理大量虚拟机,把网络统一收编到libvirt network资源池,好处是集中管理、迁移时自动跟随宿主机网络配置,规模大了会省很多事。
5. 桥接连通性验证:从宿主机到虚拟机
5.1 宿主机本地检查
创建完网桥,第一步在宿主机上验证结构正确:
bash复制brctl show br0
输出里的interfaces列应该能看到eth0。如果已经有虚拟机在运行,还能看到vnet0、vnet1这类虚拟网卡,说明虚拟机已经接进了桥。
接着验证宿主机的网络是否正常:
bash复制ip addr show br0
ip route show
ping -c 3 192.168.1.1
ping -c 3 223.5.5.5
网关和外网都能通,说明网桥IP、默认路由没问题。
5.2 创建测试虚拟机验证桥接转发
最直接的验证,是创建一台测试虚拟机。启动虚拟机,在虚拟机内查看网卡:
bash复制ip addr show
桥接模式下,虚拟机应该能拿到局域网DHCP分配的IP,或者手动配置一个同网段IP后,能ping通宿主机和局域网内其他机器。从宿主机反向ping虚拟机:
bash复制ping -c 3 192.168.1.88
再换一台局域网内的物理机去ping虚拟机。三端都通,桥接基本就稳了。
如果还想验证二层转发是否正常,可以在虚拟机里抓包:
bash复制sudo tcpdump -i eth0 arp
能看到ARP广播包,说明二层是通的,虚拟机和局域网其他设备的广播域是同一个,这才是真正意义上的桥接。
5.3 检查虚拟机网卡驱动类型
虚拟机内执行:
bash复制ethtool -i eth0
driver那一行如果是virtio_net,说明用的是virtio半虚拟化驱动,性能最好。如果是e1000或者rtl8139,说明是模拟的老网卡类型,性能一般。Linux发行版基本都自带virtio驱动,Windows虚拟机则需要额外安装。装Windows时先把网卡模型设为e1000,装好驱动后再改回virtio,不然设备管理器里会看到一个带感叹号的未知设备。
6. 常见问题与排查技巧实录
6.1 宿主机断网、SSH掉线怎么救
这是操作桥接网络时最高频的事故。原因通常是物理网卡的IP删掉了,但br0的IP没来得及配,或者网关路由丢失。恢复的思路是回滚操作,把IP加回物理网卡。如果你操作时开着tmux或screen,直接在会话里执行恢复命令;如果是远程SSH断掉,只能通过控制台或IPMI进去处理。
恢复命令:
bash复制sudo ip addr del 192.168.1.100/24 dev br0
sudo brctl delif br0 eth0
sudo brctl delbr br0
sudo ip addr add 192.168.1.100/24 dev eth0
sudo ip link set eth0 up
sudo ip route add default via 192.168.1.1 dev eth0
提示:远程操作前,先写一个fix-bridge.sh恢复脚本放在本地,万一断网,至少有个快速恢复路径。我自己习惯把恢复命令直接按图索骥写好,不追求优雅,关键时候能救命。
6.2 虚拟机桥接后仍然不能上网
按这个顺序排查:
- 宿主机上运行
brctl show br0,确认有没有对应的vnet接口;没有说明虚拟机的网卡没接好。 - 虚拟机内运行
ip addr show,确认网卡是up状态,有没有拿到IP。 - 在虚拟机里ping网关。网关不通,就要检查br0的IP和掩码是否和局域网一致。
- 如果虚拟机手动配了静态IP,确认没有和局域网内其他机器冲突。
- 检查宿主机iptables FORWARD链:
bash复制sudo iptables -L FORWARD -n -v
如果FORWARD链默认策略是DROP,桥接流量可能被拦。libvirt的default NAT网络会自动插入放行规则,但手动创建的br0不会自动加规则。临时放行用:
bash复制sudo iptables -I FORWARD -i br0 -j ACCEPT
sudo iptables -I FORWARD -o br0 -j ACCEPT
要持久化就写入iptables规则文件,或者用firewalld的rich rule配置。
- 用
ip neigh show确认虚拟机的MAC地址有没有出现在宿主机的ARP表里。能学到MAC,说明二层已经通了。
6.3 网桥创建失败:“Device or resource busy”
这个错误一般是物理网卡还在被别人占用。可能的情况有:
- 没有把物理网卡上的IP删掉就直接addif。
- NetworkManager还在管理这张物理网卡,把端口配置抢占了。
- 有别的软件(比如Docker)改动了网卡配置。
处理方式:
- 先停用物理网卡的连接:
nmcli device disconnect eth0 - 确保网卡上没有残留IP:
ip addr flush dev eth0 - 再重新执行
brctl addif。
Docker和KVM混用时,iptables被Docker改乱是常见问题,建议先确认Docker的bridge驱动没有占用你系统的物理网卡。真到了要同时跑Docker和KVM的局面,网络规划就要更仔细,避免两边互相干扰。
6.4 STP关着还是不关
小型环境我建议关掉STP,因为STP会引入端口收敛延迟。网桥链路一旦发生拓扑变化,端口要经历listening和learning状态,整体要等几十秒,你会明显感觉“网络卡了”。执行:
bash复制sudo brctl stp br0 off
但如果你桥接的网络里有多个交换设备形成了环路,比如两台宿主机都桥接同一个物理交换机,同时虚拟网线又互连了两台虚拟机,这种情况下必须开STP,否则广播风暴能直接把网络打瘫。判断方法:开了STP还是收到大量重复广播帧,那基本就是环路,需要检查链路拓扑。
6.5 VLAN隔离时别把多个子接口塞进同一个br0
网桥本身不感知VLAN。你把eth0.100和eth0.200两个VLAN子接口都塞进同一个br0,等于把两个VLAN二层打通了,隔离直接失效。正确做法是为每个VLAN建独立的网桥:br0.100和br0.200,或者用Open vSwitch做VLAN-aware的桥接。KVM对虚拟网络的VLAN隔离要求高时,Open vSwitch更合适,不过那是另一个话题了。
7. 从生产实践聊聊桥接的取舍
7.1 性能与安全怎么平衡
桥接模式绕过了NAT,数据包转发路径更短,理论延迟更低。实际测试中,virtio网卡配合桥接,虚拟机的网络吞吐能接近物理网卡。代价是虚拟机完全暴露在局域网里,安全边界得靠虚拟机自身的防火墙、交换机ACL和VLAN来补。生产环境中我的习惯是:能跑内网服务的虚拟机用桥接,需要出公网的流量走NAT,或者专门放一台网关虚拟机做集中转发,两头分开,既保证性能又隔离风险。
7.2 多网卡时怎么规划
宿主机有多块物理网卡的场景很常见。比如前端业务网卡eth0、后端存储网卡eth1、管理网卡eth2。我的规划是:
- eth0绑br0,跑生产虚拟机业务流量。
- eth1绑br1,跑存储网络,比如Ceph、NFS这类流量。
- eth2不动,作为宿主机管理口,避免一切桥接操作影响管理通道。
这样虚拟机的存储流量、管理流量、业务流量互不干扰,故障面也收窄。就算某个桥配置出了问题,管理口和存储口还能保住,远程还能救回来。
7.3 热迁移场景下桥接网络的坑
在线迁移虚拟机时,目标宿主机上必须有同名的网桥,并且和源宿主机在同一个二层网络。如果两台宿主机在不同机柜、不同VLAN,甚至跨三层,虚拟机迁过去之后网络就断了。做集群规划时,第一件事就是统一网桥名称和网段,别让每台机器自己搞一套。我自己在做小规模私有云的时候就踩过这个坑:两台宿主机的桥都叫br0,但一个在192.168.1.100网段,一个在192.168.2.100网段,VLAN都不通,热迁移一跑,虚拟机直接失联。最后只能把虚拟机迁移回原宿主机再慢慢调整网络,费了好大劲。
7.4 动手前先留好后路
最后分享一个保命习惯:修改网络配置之前,先备份当前配置文件,同时把恢复当前网络状态的命令写成一个脚本。比如:
bash复制cp /etc/netplan/01-netcfg.yaml /root/netplan-backup-$(date +%F).yaml
再写一个rollback.sh,内容是恢复原IP到物理网卡并重启网络的命令。真出问题时,执行一下,网络还原了,然后才有时间和心情去研究问题根源。别看这操作土,每次我接手别人的KVM宿主机或者自己改生产机器的网络配置,都是这套流程。踩过的坑多了以后,你会发现很多事故不是不会修,而是出问题时没有后路,明明是五分钟能解决的,最后折腾一晚上。
另外,日常维护KVM网络还要养成一个习惯:每个网桥在创建时就把用途写在注释里,写配置文件时加上注释,或者维护一份接口对应表。虚拟化环境里网络本来就是最容易乱的部分,多花两分钟做记录,后面排查问题能省几个小时。
