很多站长在把业务从"一台大机器"往"多台小机器 + 内网互联"迁移时,第一个撞上的问题就是:内网的机器该不该全部互相信任? 数据库、缓存、后台管理端口如果和面向公网的 Web 服务处在同一个二层广播域里,一台机器被拿下的代价就是整个内网沦陷。这篇文章讲的是用 VLAN(虚拟局域网) 把一台物理交换机(或者一台装了虚拟交换机的宿主机)切成若干个逻辑隔离的网络,让"前端只能碰前端、数据库只跟指定服务器说话",从网络层就断了横向移动的路径。
这不是网络工程师的专属话题。现在个人站长越来越多地用 Proxmox、KVM、Linux bridge 甚至云厂商的私有网络来跑多台机器,VLAN 的配置门槛已经从"必须懂交换机命令行"降到了"会写几条 ip / bridge / vlan 命令"。读完你会明白 VLAN 到底是什么、tagged 和 untagged 差在哪、跨 VLAN 通信靠什么、以及最常见的五个配置坑。
一、先讲清楚:VLAN 解决的到底是什么问题
一台普通二层交换机的工作方式很朴素:它维护一张 MAC 地址表,收到一个帧就表里查一下目的 MAC 在哪个口,然后从那个口发出去。如果没查到(比如广播帧、或者未知单播),它就泛洪给除了入端口以外的所有口。这意味着默认情况下,接在同一台交换机上的所有设备处在一个广播域里——它们不仅能互相 ARP 到,还能收到彼此所有的广播包。
问题就来了:公司或个人的机器一多,广播域一大,广播风暴的风险、噪声流量、以及最重要的安全边界模糊就都出现了。你可以靠防火墙(iptables/nftables)在主机层做隔离,但那要求每台机器都维护一套规则,还容易被 misconfiguration 或者容器绕过(这也是 #1303 ufw 那篇里提到的经典坑)。
VLAN 的做法不同:它在交换机这一层就把一个物理交换机切成多个逻辑交换机,每个 VLAN 是一个独立的广播域,默认彼此完全不可见。要给两个 VLAN 之间放行流量,必须显式地经过一台三层设备(路由器、或者 Linux 主机开启转发)。这就是网络层隔离的关键——默认拒绝,需要才放行,而不是依赖每台主机自觉。
二、802.1Q 帧:tag 到底加在哪里
VLAN 的标准是 IEEE 802.1Q。它的原理非常简洁:在以太网帧的源 MAC 地址后面、类型字段前面,插入一个 4 字节的 VLAN Tag。这 4 个字节里包含 12 位的 VLAN ID(VID),取值范围 1–4094(0 和 4095 保留)。12 位意味着理论上最多 4096 个 VLAN,实际可用 4094 个,对绝大多数场景绰绰有余。
关键概念是 tagged(带标签)和 untagged(不带标签),这对应交换机端口上的两种模式:
Access 口(接入口)
接终端设备(服务器网卡、PC、打印机)的端口。它属于某一个 VLAN,进出这个端口的帧都不带 tag。也就是说,终端设备自己根本不知道 VLAN 的存在,它发出去的普通以太网帧,到了交换机的 access 口,由交换机负责打上对应 VLAN 的 tag;回程帧则由交换机剥掉 tag 再交给终端。这让老设备、不支持 VLAN 的网卡也能正常工作。
Trunk 口(干道口)
交换机之间级联、或者交换机连路由器/Linux 主机的端口。它承载多个 VLAN 的流量,帧带 tag 传输。只有带着 tag 的帧,对端才知道这个帧属于哪个 VLAN。这就是为什么两条 trunk 链路上不同 VLAN 的流量可以混在一起跑却互不干扰——它们靠 tag 区分。
理解这两点的直接推论是:同一个 VLAN 内的通信靠二层交换完成(不经过路由),不同 VLAN 之间的通信必须经过三层转发。这是后面所有配置和排错的根基。
三、在 Linux 上玩 VLAN:ip link 与 bridge 命令
个人站长最可能遇到的场景不是买一台二层交换机,而是在一台 Linux 宿主机上用虚拟网络把多台虚拟机/KVM 隔离。这完全可以做,而且不需要交换机的 Web 管理界面——Linux 内核原生支持 802.1Q。
假设宿主机有一张物理网卡 eth0,我们想在它上面划出 VLAN 10(前端)和 VLAN 20(后端):
# 加载 8021q 模块(多数内核已经内置,这步是保险)
modprobe 8021q
# 在 eth0 上创建两个 VLAN 子接口
ip link add link eth0 name eth0.10 type vlan id 10
ip link add link eth0 name eth0.20 type vlan id 20
# 启用它们
ip link set eth0.10 up
ip link set eth0.20 up
# 查看结果:VLAN ID 会显示在 interface 行
ip -d link show eth0.10创建出来的 eth0.10 和 eth0.20 就是两个独立的三层接口。它们共享同一张物理网卡,但内核在处理时会给帧打上不同的 tag,所以逻辑上完全隔离。接着给它们配上不同网段的地址:
ip addr add 192.168.10.1/24 dev eth0.10
ip addr add 192.168.20.1/24 dev eth0.20现在宿主机本身同时属于两个 VLAN,它是这两个网段的三层网关。把它接到一台真正支持 802.1Q 的交换机 trunk 口上,就能让 VLAN 10/20 的流量延伸到整个机柜。
用网桥把虚拟机的口接进指定 VLAN
虚拟机(KVM/libvirt)或者容器要接入某个 VLAN,通常通过 Linux bridge。有两种连接方式,效果不同,很多人在这里踩坑:
# 方式 A:让网桥整条口都属于某个 VLAN(untagged 接入)
brctl addbr br-vlan10
brctl addif br-vlan10 eth0.10
# 方式 B:网桥自己配 vlan_filtering,逐口打标签(更接近交换机 trunk 语义)
ip link add name br0 type bridge vlan_filtering 1
ip link set br0 up
bridge vlan add dev eth0 vid 10 tagged
bridge vlan add dev vnet0 vid 10 pvid untagged方式 A 简单粗暴:桥接在 VLAN 子接口上,挂到网桥的虚拟机全部落在 VLAN 10,且虚拟机看到的是不带 tag 的帧。适合"我想让这批虚拟机就在一个隔离网段里"的场景。
方式 B 是 Linux 桥的 VLAN-aware 模式(内核 3.8+),语义和物理交换机一致:bridge vlan add 可以给桥的每个端口单独指定它属于哪些 VLAN、是 tagged 还是 pvid(端口默认 VLAN)。这才是真正的"一台 Linux 虚拟交换机",一台宿主机上不同虚拟机可以落在不同 VLAN,共享物理口而出向带各自 tag。
四、跨 VLAN 通信:路由,以及绕不开的"三层转发"
VLAN 隔离之后,前端 VLAN 10 的 Web 服务器要访问后端 VLAN 20 的数据库,怎么办?答案是:必须有一个三层设备同时属于两个 VLAN,并在它上面开启 IP 转发。这台设备通常就是你的 Linux 宿主机(单臂路由),或者一台专门的路由器。
# 宿主机开启 IPv4 转发
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.d/99-forward.conf
# 查看路由表,确认两个网段都在
ip route show
# 然后必须放行转发(否则 ip_forward 开了也没用,防火墙默认 DROP)
nft add rule inet filter forward iifname "eth0.10" oifname "eth0.20" accept
nft add rule inet filter forward iifname "eth0.20" oifname "eth0.10" ct state established,related accept这里有一个无数人踩过的坑:开了 ip_forward=1,防火墙的 FORWARD 链默认策略却是 DROP。包能到宿主机、却被内核丢掉,表现为"ping 得通网关、ping 不通对端"。排查时先看 sysctl net.ipv4.ip_forward,再看 nft list ruleset 或 iptables -L FORWARD -n -v 的计数器有没有涨。计数器涨了说明被策略丢,不涨说明包根本没到。
如果只是想放行必要的端口而不是全通,就把上面对应规则写细:数据库只允许 Web 服务器的 IP 访问 3306,其余一律 drop。这才是 VLAN 隔离的真正价值——边界清晰,规则可数。
五、五个必踩的坑
坑 1:物理交换机的 trunk 口忘了放行目标 VLAN
Linux 侧配得天衣无缝,机房交换机上那条 trunk 口只 allow 了 VLAN 1,结果 VLAN 10 的帧全被丢。802.1Q trunk 上"允许哪些 VLAN 通过"是交换机端口的一个属性(Cisco 叫 switchport trunk allowed vlan,华为叫 port trunk allow-pass vlan),默认往往只允许 VLAN 1。这是跨设备场景第一顺位要查的项。
坑 2:两边 VLAN ID 不一致
一边 ID 10、另一边 ID 20,看着是同一个网段却死活不通。VLAN ID 是全局约定,必须在链路两端严格一致。用 ip -d link show 或交换机 show vlan 核对。
坑 3:MTU 少算了 4 字节
802.1Q 在帧里插了 4 字节 tag。如果链路上两边 MTU 都卡在 1500 而且是硬性不能扩,那么某些大包在这个 VLAN 上会比非 VLAN 链路更容易触发分片。多数情况下网卡和交换机都能处理 1522 字节的"baby giant",但虚拟化环境下(VXLAN over VLAN 之类叠加封装)很容易把可用 MTU 吃穿。这类故障的现象是"小请求正常、大文件传一半卡住",正是 #1433 MTU 那篇的排查套路。
坑 4:DHCP 广播跨不了 VLAN
VLAN 隔离了广播域,DHCP 的 DISCOVER 是广播包,自然跨不出去。想让每个 VLAN 内的机器自动拿 IP,需要在每个 VLAN 里各放一个 DHCP 服务,或者在三层设备上配 DHCP Relay(dhcrelay/中继)把请求转发到统一 DHCP 服务器。dhcrelay -i eth0.10 -i eth0.20 192.168.1.100 是常见写法。不做中继就手动配静态 IP,别以为是网卡坏了。
坑 5:把 VLAN 当成了安全银弹
VLAN 隔离的是二层广播域,它不是加密,也不是防火墙。同一台交换机上如果有人能把网卡配成 trunk 并伪造 tag(VLAN hopping),理论上能跳到别的 VLAN——防御手段是交换机端口设成 access 模式、禁用 DTP 自动协商、把 native VLAN 改成一个没人用的 ID。对个人站长来说更实际的一句话是:VLAN 划好了,三层之间的访问控制还得靠防火墙写规则,两者是配合关系,不是替代关系。
六、一个真实场景的复盘:数据库为什么能被外网扫到
我见过一个很典型的案例:某站长搭了 Web + DB 两台云主机,图省事把两台都接了公网 IP,靠 MySQL 的 bind-address 和用户授权来"保护"数据库。某天他换了台新机器部署,导数据时为了快,临时把 3306 端口对 0.0.0.0 开放,忘了收回去。三天后数据库被拖库——攻击者根本没碰 Web 站,直接扫 3306 弱口令进来的。
如果当初用私有网络/VLAN 把 DB 放在一个只跟 Web 主机互通的隔离网段,这个"临时开放"根本不可能暴露到公网。这就是网络层隔离和主机层配置的本质区别:主机层的口子容易忘、容易漏、容易被容器绕过;网络层的隔离是默认的、结构性的。VLAN(或云上的私有子网、安全组)值得每个多机站长花半天时间做一次。
七、落地清单
如果你打算给自己的多机架构加上这一层,按这个顺序走:
- 先画图:哪些机器需要互访、走哪些端口,画出 VLAN 划分和三层边界,再动手配。
- 先在三层设备上验证 ip_forward 与 FORWARD 策略,别配完 VLAN 才发现跨网段不通。
- 每个 VLAN 一个独立网段,避免网段重叠导致路由歧义。
- trunk 上显式 allow 需要的 VLAN,不用的 VLAN 一律不放行。
- 把 VLAN 当作"物理隔离的软件版"来理解:它决定"能不能看见",防火墙决定"能不能访问",两者叠加才构成完整的边界。
VLAN 这东西,配一次可能就半小时,但它换来的是整个内网结构的清晰。对一个认真对待自己站点的站长来说,这笔投入的性价比几乎是最高的——它不解决某个具体 bug,但它让"某台机器被打穿以后会发生什么"这个问题,从"看运气"变成"看设计"。