libvirt macvlan + VSOCK:局域网直连虚拟机,宿主零断网风险

八月 31, 2026 [虚拟化与容器] #libvirt #qemu #虚拟化 #网络

libvirt macvlan + VSOCK:局域网直连虚拟机,宿主零断网风险

背景

给一台 aarch64 宿主(192.168.6.8,KVM)上的新虚拟机做局域网直连(局域网其他机器能直接访问 VM),但有两个顾虑:

最终方案是组合拳:

  1. libvirt macvlan(macvtap,type=direct bridge 模式)直接挂 enp2s0f0 —— 完全不动宿主网卡配置,零断网风险,LAN 可达 VM
  2. macvlan 的代价:宿主机 ↔ VM 互不可达(物理网卡不接收 guest MAC 的帧)
  3. 用 VSOCK(virtio-vsock,cid=3)补上宿主机 → VM 的专属 SSH 通道

关键认知(先搞懂再动手)

事实说明
macvtap bridge 模式guest 帧直接进出物理网卡,LAN 可达 guest;但宿主 NIC 不收 guest MAC 帧 → 宿主↔guest 不通,这是硬限制,不是配置错误
sshd 不监听 AF_VSOCKsshd 只绑 TCP :22;VSOCK 是独立地址族,需要 guest 内 socat 桥接 VSOCK-LISTEN:22 → 127.0.0.1:22
/dev/vsock 要模块宿主需 vsock + vmw_vsock_virtio_transport(/dev/vsock 才会出现);QEMU aarch64 的 vhost-vsock-pci 可用
用系统 libvirtdsession 模式(qemu:///session)用户进程没 CAP_NET_ADMIN,建不了 macvtap;用 qemu:///system(root libvirtd)
镜像架构aarch64 宿主只能跑 arm64 guest,amd64 ISO 装上也是全模拟、慢到没法用
macvlan 网络定义 ≠ 连接定义<forward mode='bridge'><interface dev='...'/></forward> 只是声明"哪些物理网卡可用于 macvtap 直连"的资源池,net-start 不创建任何设备、不加 iptables/DHCP/DNS;type='direct' 网卡上的 dev/mode 才是真正创建 macvtap 的参数(macvtap 的 bridge/vepa/private/passthrough 模式是每条网卡自己的属性),所以两处都要写
NoCloud seedcloud-init 首次启动自动挂载卷标 cidata 的 CDROM(seed.iso),读 user-data/meta-data/network-config 完成无人值守初始化(建用户/注入密钥/主机名/runcmd),相当于安装应答文件,初始化完即可弹出

步骤

0. 安装依赖(宿主机)

sudo apt install -y cloud-image-utils virtinst qemu-utils socat whois
# cloud-localds(cloud-image-utils)/ virt-install / qemu-img / ssh ProxyCommand 的 socat / mkpasswd(whois)

1. 定义 macvlan 网络(macvtap 直连)

<!-- macvlan0.xml -->
<network>
  <name>macvlan0</name>
  <forward mode="bridge">
    <interface dev="enp2s0f0"/>
  </forward>
</network>
virsh net-define macvlan0.xml && virsh net-start macvlan0 && virsh net-autostart macvlan0

VM 网卡最终是 type='direct' + mode='bridge',直接挂物理网卡:

<interface type='direct'>
  <source network='macvlan0' dev='enp2s0f0' mode='bridge'/>
  <model type='virtio'/>
</interface>

为什么网络里已声明 dev='enp2s0f0',网卡上还要再写一遍 dev/mode?网络 XML 只是"可用物理网卡池"(net-start 不创建设备),macvtap 模式是每条网卡自己的属性,type='direct' 网卡是自包含的连接定义,libvirt 不会从网络定义里替你填参数。想省事可改用 <interface type='network'><source network='macvlan0'/></interface> 让 libvirt 自动从池里选网卡并默认 bridge 模式,但单网卡时两者等价。

2. 镜像与 seed

seed.iso 的作用:Ubuntu cloud 镜像出厂不带用户/密码,首次启动时 cloud-init 自动查找卷标 cidata 的可挂载卷(即 seed.iso),读里面的 user-data/meta-data/network-config 完成无人值守初始化——建用户、注入 SSH 公钥、设密码、主机名、执行 runcmd。相当于"安装应答文件",初始化完成后即可弹出。cloud-localds 只是把 YAML 打包成 ISO 的工具(来自 cloud-image-utils 包),seed.iso 不需要下载。

# arm64 cloud 镜像(~590MB)
wget -O /data/vm/noble-server-cloudimg-arm64.img \
  https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img
qemu-img convert -f qcow2 -O qcow2 noble-server-cloudimg-arm64.img u24.qcow2
qemu-img resize u24.qcow2 50G

seed 由 1 个必建文件 + 2 个可选文件组成,全部放在 /data/vm 下:

seed.yaml(必建):用户/密钥/开机动作(DHCP 默认,无需 network 配置):

#cloud-config
hostname: u24
users:
  - name: smyl
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo
    shell: /bin/bash
    lock_passwd: false
    passwd: <mkpasswd -m sha-512 生成的密码哈希>
    ssh_authorized_keys:
      - <宿主公钥,cat ~/.ssh/id_ed25519.pub 的内容>
ssh_pwauth: true
runcmd:
  - modprobe vsock_virtio || true
  - apt-get update -qq && apt-get install -y -qq qemu-guest-agent
  - systemctl enable --now qemu-guest-agent

meta-data(可选,不建则 cloud-localds 自动生成):

instance-id: u24-001
local-hostname: u24

network-config.yaml(可选,DHCP 场景不需要):

version: 2
ethernets:
  all:
    match:
      name: "en*"
    dhcp4: true
    dhcp6: true

生成 ISO 并验证(注意 passwd 必须是 sha-512 哈希,不能写明文):

cd /data/vm
cloud-localds /data/vm/seed.iso seed.yaml
# 完整三件套:cloud-localds -v --network-config network-config.yaml seed.iso seed.yaml meta-data
isoinfo -f -i /data/vm/seed.iso   # 验证:应列出 /user-data /meta-data(/network-config 可选)

3. 建 VM

virt-install --connect qemu:///system \
  --name u24 --memory 4096 --vcpus 4 \
  --disk path=/data/vm/u24.qcow2,format=qcow2,bus=virtio \
  --disk path=/data/vm/seed.iso,device=cdrom \
  --network network=macvlan0,model=virtio \
  --os-variant ubuntu22.04 --import --graphics none --noautoconsole

4. 挂 VSOCK 设备(cid=3)

<!-- vsock.xml -->
<vsock>
  <cid auto="no" address="3"/>
</vsock>
virsh attach-device --config u24 vsock.xml   # 注意 --config 只写配置,需重启 VM 才生效
virsh start u24

cid 是什么:Context Identifier,AF_VSOCK 地址族里的节点地址,作用相当于 IP——VSOCK 通信用 (cid, port) 二元组寻址(类比 TCP 的 (IP, 端口)),cid 在整台宿主机上全局唯一(所有 VM 共享一个 VSOCK 地址空间)。保留值(linux/vm_sockets.h):

cid含义
0xFFFFFFFFVMADDR_CID_ANY(通配)
0VMADDR_CID_HYPERVISOR(虚拟化层)
1VMADDR_CID_LOCAL(本机进程间)
2VMADDR_CID_HOST(宿主机,固定不变)
≥3各 guest 自行分配

cid=3 没有特殊语义——QEMU 规定 guest cid 最小为 3(2 及以下全被保留),所以 3 是合法的最小值,第一个 VM 惯例上就用它(libvirt <cid auto='yes'> 自动分配时也从 3 开始),之后每个 VM 换个不重复的数字(4、5…)。对应到本文:宿主连 VSOCK-CONNECT:3:22 就是"连 u24 这台机器";反过来 guest 要连宿主机则固定用 cid=2。

guest-agent channel 不需要手动挂,virt-install 自动加了 org.qemu.guest_agent.0。

5. guest 内做 VSOCK → TCP 桥接

sshd 不监听 AF_VSOCK,需要 socat 转发(VM 内):

apt install -y socat
cat > /etc/systemd/system/vsock-sshd.service <<'EOF'
[Unit]
Description=VSOCK to TCP sshd bridge
After=network.target

[Service]
ExecStart=/usr/bin/socat VSOCK-LISTEN:22,fork,reuseaddr TCP:127.0.0.1:22
Restart=always

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable --now vsock-sshd.service

6. 宿主侧 ssh 别名

# ~/.ssh/config
Host u24
    HostName localhost
    ProxyCommand socat - VSOCK-CONNECT:3:22
    User smyl
    HostKeyAlias u24
    ServerAliveInterval 30

首次连接需 ssh -o StrictHostKeyChecking=accept-new u24 记录 host key(HostKeyAlias 保证存到 u24 名下,hostname 无所谓)。

7. 持久化(宿主重启后自动恢复)

echo -e "vsock\nvmw_vsock_virtio_transport" | sudo tee /etc/modules-load.d/vsock.conf
virsh net-autostart macvlan0   # 网络自启
virsh autostart u24            # VM 自启

验证

# LAN 直连(其他机器也能这样访问 VM)
ssh smyl@192.168.5.193        # DHCP 分配的 IP

# 宿主机走 VSOCK(不依赖网络,网卡挂了也能连)
ssh u24

VM 的 DHCP IP 通过 guest-agent 查询(macvtap 下宿主 ARP 表看不到 guest,domifaddr --source arp 不灵):

virsh qemu-agent-command u24 '{"execute":"guest-network-get-interfaces"}'

踩过的坑

总结