云计算实验二:KVM虚拟机与Docker容器资源隔离及性能对比 云计算这门课里实验二通常意味着要真正和虚拟化、容器打交道了。我前后帮几届同学看过他们的实验报告也自己从头完整跑过一遍最大的感受是大部分人不是不会敲命令而是敲完之后不知道自己敲的是什么。虚拟机起来了、容器跑通了、截图交上去了老师随口问一句为什么容器的启动比虚拟机快一个数量级人就卡住了。这篇按我自己做实验二的完整链路来写从环境准备、KVM 虚拟机创建、Docker 容器部署到虚拟化与容器化的资源隔离实测对比再到实验报告该怎么组织尽量把每一步为什么这么做讲透。适合正在做云计算课程实验、或者刚接触虚拟化与容器技术想动手跑一遍的人看命令可以直接抄但更建议看完解释再敲。1. 实验二的真实考点虚拟化与容器化到底在比什么1.1 从资源抽象这条主线理解实验设计很多人拿到实验指导书第一反应是把上面列的命令按顺序执行一遍然后截图交差。这种做法的结果就是报告里全是systemctl status的输出看不出任何思考。我建议先想明白一件事云计算这门课从第一节课起讲的就是资源池化——把一台物理机的 CPU、内存、磁盘、网络切成很多份按需分配给不同的使用者用完再收回来。虚拟机和容器都是实现这种切分的具体手段实验二的本质不是学会装两个软件而是亲手做一次资源切分并用数据说明两种切分方式的差别。理解了这个主线实验里的每一步操作就都有了归属。装 KVM 是为了在硬件层做资源切分装 Docker 是为了在操作系统层做资源切分后面的对比测试是为了量化这两层的代价差异。你的报告里只要有这条主线在哪怕数据不那么漂亮逻辑也是站得住的反过来就算你把十几个命令的截图全贴上去没有主线也只是一份操作记录。我在实际做的时候会在文档最开头写一段实验假设虚拟机因为要模拟完整的硬件环境启动慢、内存开销大但隔离性强容器共享宿主机内核启动快、开销小但隔离性弱。后面所有的采集数据都是用来验证或推翻这段话的。这个小动作看起来不起眼但写报告的时候能救命——你会清楚知道自己在收集什么、为什么收集。1.2 虚拟机与容器隔离层次决定了能力边界要把两者的差别说清楚绕不开隔离发生在哪一层这个问题。虚拟机是靠Hypervisor裸金属型如 ESXi宿主型如 KVM或者更准确地说 KVM 是内核模块 QEMU 用户态进程的组合把物理资源抽象成虚拟硬件每个虚拟机里有自己独立的内核、自己的设备驱动、自己的系统调用表。容器则不一样它跑在宿主的内核上靠namespace做视图隔离、靠cgroup做资源限制容器里的进程本质上还是宿主机上的普通进程只是被骗了以为自己独占了一整套系统。这个差别会带来一连串实际后果我在做实验二时把它们整理成了下面这张表报告里直接引用很省事对比维度虚拟机KVM容器Docker隔离层次硬件级含独立内核进程级共享宿主内核启动耗时数秒到数十秒数十毫秒到数百毫秒单实例内存基线通常 512MB 起步几 MB 到几十 MB镜像/磁盘占用GB 级MB 级分层共享内核版本自由度可任意更换受宿主内核约束安全隔离强度强边界清晰较弱共享内核存在逃逸风险适用场景异构系统、强隔离需求微服务、快速扩缩容这张表不是我凭空写的每一条后面都有实验数据背书比如启动耗时那一行是我用virsh start到 SSH 可登录计时和docker run到容器退出计时各跑十次取中位数得出的量级。数字的具体值和你用什么机器、什么镜像强相关但数量级的差距是稳定的这也是实验二最值得写进结论的东西。还有一个容易被忽略的点KVM 里的 QEMU 进程在宿主机上是可以ps看到的你ps aux | grep qemu就能看到一个带着一堆-device参数的进程这个进程内存占用基本就等于你分配给虚拟机的内存。而容器用docker top看到的就是你的应用进程本身。这个观察很直观我第一次注意到的时候才真正理解虚拟机是进程模拟出来的一台机器这句话。2. 环境准备让 KVM 和 Docker 在同一台机器上共存2.1 硬件虚拟化能力的确认与 BIOS 层面的坑第一件事是确认你的 CPU 支持硬件虚拟化并且已经在 BIOS 里打开了。云服务器厂商提供的机器通常支持嵌套虚拟化但默认可能没开这一点在实验开始前必须确认否则后面所有 KVM 相关操作都会卡在权限报错上。检查命令很简单egrep -c (vmx|svm) /proc/cpuinfo输出大于 0 说明 CPU 支持Intel 是 vmxAMD 是 svm输出 0 就说明没开或者被上层虚拟化屏蔽了。还有个更方便的工具是cpu-checker包里的kvm-oksudo apt install -y cpu-checker kvm-ok正常的输出里会明确告诉你/dev/kvm是否存在。/dev/kvm这个设备节点是 KVM 加速的关键没有它 QEMU 会退化成纯软件模拟TCG 模式性能差到没法做实验——我见过有同学的虚拟机开机要十几分钟最后发现就是/dev/kvm不存在一直在跑软件模拟。如果你用的是自己电脑上的虚拟机里再装虚拟机需要在宿主机的虚拟化软件设置里勾选虚拟化 Intel VT-x/EPT 或 AMD-V/RVI这类选项。这一步没做的话kvm-ok会直接告诉你KVM acceleration can NOT be used早点发现能省下大量时间。2.2 依赖安装与 libvirtd 服务的启动顺序确认硬件没问题之后就是装软件。Debian/Ubuntu 系和 RHEL 系的包名不一样我两个都列出来你按自己的系统选# Debian / Ubuntu sudo apt update sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \ bridge-utils virtinst virt-manager # RHEL / CentOS / 衍生发行版 sudo yum install -y qemu-kvm libvirt libvirt-devel virt-install bridge-utils装完之后必须启动 libvirtd 服务这是个后台守护进程virsh的所有命令都是通过它跟 QEMU 通信的。很多人装完就直接virsh list然后报Failed to connect to the hypervisor原因就是服务没起来sudo systemctl enable --now libvirtd sudo systemctl status libvirtdenable --now这个组合一次搞定开机自启和立即启动比先enable再start省事。状态里如果看到Active: active (running)就对了。接下来有个细节默认情况下普通用户没有权限操作 libvirt每次都得sudo很别扭。把自己加进libvirt和kvm组就能免 sudosudo usermod -aG libvirt,kvm $USER加完组必须重新登录或者newgrp libvirt否则组信息不生效你会怀疑人生地发现命令还是提示权限不足。我第一次踩这个坑的时候反复确认了好几遍用户组配置最后才发现是 shell 会话没刷新。2.3 镜像源与网络出口的提前配置实验二里要下载的东西不少虚拟机安装 ISO、Docker 镜像、各种依赖包。这些下载如果走默认源速度可能慢到让你怀疑实验环境有问题。我通常会在正式开跑前把两件事做完。第一件是把系统包管理器的软件源换成本地就近的镜像站这个各家发行版都有自己的配置方式改完之后apt update的速度能差出好几倍。第二件是给 Docker 配置镜像加速地址编辑/etc/docker/daemon.json{ registry-mirrors: [https://你的加速地址] }改完重启服务sudo systemctl restart docker然后docker info里能看到Registry Mirrors列表就说明生效了。这一步不做的话拉一个基础镜像可能要等十几分钟做对比实验时反复拉镜像的时间成本会非常高。另外提醒一句做实验前先看一眼磁盘剩余空间。KVM 的 qcow2 镜像是精简置备的你声明 20G 不代表立刻占 20G但随着虚拟机里写数据它会一直涨Docker 的镜像层、容器层、构建缓存也都堆在/var/lib/docker下面。我有一次因为没看空间跑到一半虚拟机写盘失败直接崩了日志里一堆 I/O error排查了半天才反应过来是磁盘满了。3. KVM 虚拟机的创建流程与参数取舍3.1 磁盘镜像格式qcow2 与 raw 的取舍创建虚拟机第一步是准备磁盘镜像。KVM 支持多种格式实验场景下基本在qcow2和raw之间选。这两个的差别不是随便定的直接决定了你的实验体验。raw是裸格式文件多大就占多大写入是直接的偏移量写入性能最好但不支持快照、不支持精简置备。qcow2是 QEMU 的写时复制格式支持精简置备、支持快照、支持压缩代价是多了一层元数据映射写入性能略低。实验场景我强烈建议用qcow2理由很实在你要做快照对比、要回滚、要反复重装系统qcow2一条virsh snapshot-create-as就能搞定raw做不到。而且精简置备让你能在有限的磁盘上创建多个虚拟机做多机实验的时候特别有用。创建命令sudo qemu-img create -f qcow2 /var/lib/libvirt/images/vm-exp2.qcow2 20G注意这里的20G是上限不是立即占用。用qemu-img info可以看到disk size和virtual size两个值前者是实际占用后者是声明容量随着虚拟机写数据前者会逐步逼近后者。如果你打算做性能对比实验可以额外创建一个raw格式的盘挂上去用fio跑一下顺序写和随机写数据差异在实验报告里是很好的加分项。我测过一次在同样的宿主机上raw的顺序写带宽比qcow2高出百分之十几到二十随机写差距更明显一些但具体数值和你用的存储介质关系很大机械盘和固态盘的表现完全不同。3.2 virt-install 关键参数逐项拆解有了磁盘镜像就可以用virt-install创建虚拟机了。这个命令的参数很多我按必须懂和可以抄分一下sudo virt-install \ --name vm-exp2 \ --memory 2048 \ --vcpus 2 \ --disk path/var/lib/libvirt/images/vm-exp2.qcow2,formatqcow2,busvirtio \ --cdrom /var/lib/libvirt/images/system.iso \ --network networkdefault,modelvirtio \ --graphics vnc,listen0.0.0.0,port5901 \ --os-variant generic \ --noautoconsole逐项说一下为什么这么写。--memory 2048是分配 2GB 内存注意这是独占的虚拟机一启动宿主机上就会少 2GB 可用内存QEMU 进程会逐渐把这个内存吃满。你的宿主机如果有 8GB跑两个这样的虚拟机就已经很紧张了。--vcpus 2分配两个虚拟 CPU。这里有个细节--vcpus默认是共享物理核的不是绑定的。如果你要做性能测试希望结果稳定可以用--cpuset把 vCPU 绑定到特定物理核上避免调度抖动。busvirtio这个参数值得单独说。KVM 支持模拟 IDE、SATA、SCSI、virtio 等多种磁盘和网卡控制器其中virtio 是半虚拟化驱动性能远高于模拟真实硬件的方案。原理是 virtio 让 Guest 里的驱动和宿主机的 QEMU 之间走一条专门优化过的通道省掉了大量模拟真实硬件寄存器的开销。用 IDE 模拟的话磁盘 IO 性能可能只有 virtio 的三分之一甚至更低。这个点在报告里写出来比单纯贴命令有分量得多。--os-variant generic是告诉 virt-install 目标系统的类型它会据此优化一些默认参数比如时钟源、驱动选择。能选具体版本就选具体的比如ubuntu22.04、centos-stream9优化会更精准。--noautoconsole是创建完不自动打开控制台适合脚本化操作。3.3 网络模式选择NAT 与桥接的适用场景KVM 默认会创建一个叫default的网络本质是一个 NAT 网络宿主机上会多出一个virbr0网桥网段通常是192.168.122.0/24虚拟机会从这个网段拿到 IP通过宿主机做 NAT 出网。这个模式开箱即用不用改任何配置做单机实验足够了。命令查看sudo virsh net-list --all sudo virsh net-dhcp-leases default第二条能看到当前分配出去的租约虚拟机的 IP 一目了然比进虚拟机敲ip addr方便。桥接模式则是把虚拟机直接接到物理网络上虚拟机会从路由器拿到和宿主机同网段的 IP局域网内其他机器可以直接访问它。配置起来麻烦一些需要在宿主机上把物理网卡桥接出去。以 Netplan 为例配置文件大概长这样network: version: 2 ethernets: eth0: dhcp4: false bridges: br0: interfaces: [eth0] dhcp4: true改完sudo netplan apply。这里要特别小心如果是远程连的机器桥接配置写错会直接断网你得去控制台救。我做实验的时候一般先确认有本地控制台或者带外管理可用再动网络配置。选哪个我的建议是单机做资源对比实验用 NAT 就够了省事如果实验要求多台虚拟机互相通信、或者要求从外部访问虚拟机上的服务比如跑个 Web 服务给同学演示那就得用桥接。桥接还有个好处是没有 NAT 那一层网络性能的损耗更小做网络吞吐测试时数据更干净。4. Docker 容器部署从镜像拉取到资源限制4.1 容器的启动链路与镜像分层Docker 的安装现在非常省事官方脚本一条命令搞定curl -fsSL https://get.docker.com | sudo sh sudo systemctl enable --now docker装完docker version能看到 Client 和 Server 两部分信息Server 那边有内容说明守护进程正常。容器启动的完整链路是docker run命令 → 客户端通过 API 发给 dockerd → dockerd 从镜像仓库拉取镜像如果本地没有→ 在镜像的只读层之上叠加一个可写层 → 创建 namespace 和 cgroup → 执行镜像里定义的ENTRYPOINT/CMD。整个过程里最慢的通常是拉镜像那一步镜像已经在本地的话从命令发出到容器跑起来基本就是毫秒级。镜像分层这个机制值得理解一下因为它解释了很多日常现象。Dockerfile 里每条指令都会生成一层只读层多层之间用联合文件系统overlay2 是现在最常用的叠在一起呈现为一个完整的文件系统视图。这样做的好处是层可以共享——你拉十个基于同一个基础镜像的容器底层那几层在磁盘上只有一份。这也是为什么容器占空间小这个说法要加限定条件单看一个容器是小的但如果你每个镜像都用不同的基础镜像总占用可能比你想象的大得多。用docker history可以看一个镜像的分层情况docker history nginx:latest你会看到每一层的创建命令和大小那些ADD/COPY引入大文件的行会明显比其他层大。4.2 用 cgroup 给容器戴上紧箍咒默认情况下容器可以使用宿主机的全部 CPU 和内存虽然会被调度器公平分配但没有硬上限。这在实际使用中很危险——一个内存泄漏的容器可能把整台机器拖垮。所以--memory和--cpus这两个参数是必须理解的。docker run -d --name web1 \ --memory512m \ --memory-swap512m \ --cpus1.0 \ --cpuset-cpus0 \ -p 8080:80 \ nginx:latest--memory512m是硬上限容器内可用内存超过这个值内核的 OOM killer 会介入把容器里的进程杀掉。--memory-swap512m这个参数容易搞错它指的是内存 swap 的总量设成和--memory相等意味着容器不可以使用 swap。如果你想要 512MB 内存 512MB swap应该把它设成1g。设成-1表示不限制 swap。这个坑我在第一次做资源限制实验时就踩了写了--memory512m --memory-swap1g还纳闷为什么free里看到的可用内存不是 512MB。--cpus1.0是 CPU 时间配额1.0 表示等价于一个 CPU 核心的算力。注意这不是绑定到某个核而是 cgroup 的 CPU 带宽控制——cpu.cfs_quota_us和cpu.cfs_period_us的比值。--cpuset-cpus0才是真正的绑定把容器限制在 0 号物理核上跑。这两个参数经常被混为一谈其实作用完全不同前者限制的是能用多少后者限制的是在哪用。验证限制是否生效除了docker stats还可以直接看 cgroup 文件系统cat /sys/fs/cgroup/system.slice/docker-容器ID.scope/memory.max cat /sys/fs/cgroup/system.slice/docker-容器ID.scope/cpu.max不同发行版的 cgroup 路径结构略有差异cgroup v1 和 v2 差别挺大但思路是一样的。这些文件的内容就是限制的权威来源比docker inspect更直接。4.3 端口映射与容器网络的实际表现-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口。底层是 dockerd 在宿主机上写了 iptables 的 DNAT 规则把发往 8080 的流量转发到容器的 IP 上。查看规则sudo iptables -t nat -L DOCKER -n这里有个现象值得在报告里提一句端口映射会带来额外的网络开销因为流量要经过一次 NAT 转换。如果你做网络性能对比测试直接测宿主机性能、容器 host 网络模式、容器 bridge 模式加端口映射这三组数据会呈现明显的阶梯状。我用简单的 HTTP 压测工具测过bridge 模式加端口映射相对宿主机直接跑吞吐大概低几个百分点到十几个百分点具体看包大小和连接数。这个差距在实验报告里可以作为容器网络不是零成本的佐证。容器默认的 bridge 网络里容器之间用容器名互相访问需要自定义网络默认的bridge网络不支持 DNS 解析。要玩多容器通信先建个自定义网络docker network create --subnet172.20.0.0/16 app-net docker run -d --name c1 --network app-net nginx docker run -d --name c2 --network app-net alpine sleep 3600 docker exec c2 ping -c 2 c1最后一条能 ping 通说明自定义网络里的内嵌 DNS 生效了。这个小实验花不了几分钟但对理解容器网络很有帮助。5. 实测对比启动时间、内存开销与计算性能5.1 测试方案设计控制变量比什么都重要做对比实验最容易犯的错误是变量没控制住。比如测启动时间虚拟机在机械盘上、容器在固态盘上那数据就没意义了。我在动手之前会先列一个清单确认两边的条件尽量一致同一台宿主机、同一个时间段测试避免其他负载干扰虚拟机内和容器内跑同样的基准程序每个指标至少采集 5 到 10 次取中位数而不是平均值平均值容易被偶发的极值拉偏测试前先跑一轮预热把缓存的影响降下去启动时间的测法要明确启动完成的判定标准。虚拟机的启动时间我是从virsh start发出开始到虚拟机内 SSH 可登录ssh -o ConnectTimeout1成功返回为止。容器则是从docker run发出到容器内命令执行完成退出。两个标准不完全等价但都反映了从下指令到能干活的时间作为量级对比是合理的报告里把判定标准写清楚就行。内存占用的测法虚拟机看宿主机的 QEMU 进程 RSS容器在空跑状态下看docker stats的 MEM USAGE。CPU 性能用sysbench cpu跑单线程固定时长记录 events per second。5.2 三组数据的采集与解读以我手上这台机器为例4 核 8G 的普通开发机固态盘跑出来的量级大概是这样的指标裸机KVM 虚拟机Docker 容器启动到可用耗时-约 15-25 秒约 0.3-0.8 秒空载内存占用-约 1.2-1.5 GB分配 2G 时约 5-15 MBsysbench 单线程 events/s约 1200约 1100-1180约 1190-1210磁盘顺序写带宽基准约为基准的 70%-85%约为基准的 90%-98%这组数字有几个地方值得展开说。启动时间的差距是数量级的虚拟机二十秒左右容器一秒以内。原因是虚拟机要完整走一遍 BIOS 自检、引导加载、内核初始化、systemd 拉起一堆服务的流程和真机开机没区别容器只是启动一个进程前面那些步骤全省了。内存占用的差距同样夸张。虚拟机分配了 2GQEMU 进程的 RSS 会逐步涨到接近 2G因为客户机认为自己有这么多物理内存会积极地把数据缓存进去。容器空载只占十几 MB因为它没有任何虚拟硬件要模拟也没跑额外的服务。但 CPU 性能的差距就小得多了基本在个位数百分比。这个结果很多人第一次看到会意外——不是虚拟机性能损失很大吗其实纯计算场景下KVM 是硬件辅助虚拟化客户机的指令直接在物理 CPU 上执行只有特权指令才需要 VM exit 陷入 Hypervisor而sysbench cpu这种纯计算负载几乎不触发陷入所以损失很小。性能损失明显的是 IO 密集和频繁系统调用的场景。磁盘 IO 的差距就明显了。virtio 虽然比全模拟快很多但客户机的 IO 请求还是要经过 QEMU 进程转一道多了一次用户态到内核态的往返。容器没有这一层直接走宿主内核的文件系统。所以你在磁盘测试里看到的百分比差距基本就是虚拟化那一层的开销。5.3 数据背后的解释为什么差这么多把这几个差异串起来看其实指向同一个结论虚拟化的开销主要不在计算而在模拟和转发。CPU 计算是硬件辅助的接近原生内存因为要做地址翻译Intel 的 EPT / AMD 的 NPT 就是干这个的开销也不大但 IO 设备模拟、中断处理、网络收发包这些操作每一样都要在客户机和宿主机之间穿梭累积起来的开销就很可观了。容器没有这层穿梭所以 IO 表现更好。还有一个容易被忽略的维度是密度。同样一台 8G 内存的机器跑 2G 的虚拟机最多跑 3 台还得给宿主留点跑容器的话几十个都没问题。这个差异在云计算场景里是决定性的——资源利用率直接对应成本。这也是为什么现在很多服务从虚拟机迁移到容器不是因为容器更好而是因为在隔离性需求没那么极端的场景下容器的性价比更高。反过来说容器也不是万能的。共享内核意味着内核漏洞的影响面更大如果你的业务需要跑不同版本的内核、需要强隔离比如多租户场景虚拟机仍然是对的选择。很多生产环境是两者混用——物理机跑虚拟机做资源隔离虚拟机里跑容器做应用编排这个分层架构的思路在实验报告里可以作为延伸思考写一段很能体现你理解了而不只是操作了。6. 实验报告怎么写才不像截图流水账6.1 结构问题—方案—数据—结论实验报告最常见的写法是按操作顺序罗列第一步装 KVM第二步建虚拟机第三步装 Docker……这种写法的问题是把过程当成了内容。老师要看的是你有没有理解而不是你能不能照抄步骤。我习惯用四段式组织每一部分要解决什么问题 → 我选了什么方案以及为什么 → 实际数据/现象是什么 → 由此得出了什么结论。以虚拟磁盘格式这一节为例要解决的问题是多台虚拟机共存时磁盘占用过大可选方案有 raw、qcow2、vmdk 等我选 qcow2 是因为它支持精简置备和快照代价是写入性能略低。然后我会给qemu-img info的输出截图展示 virtual size 和 disk size 的差异如果做了性能测试就把fio的数据贴上来。最后结论是实验场景下优先选择 qcow2性能优先的场景考虑 raw。这个结构的好处是即使你某个步骤失败了你也可以把失败过程和排查思路写进去反而是加分项。我见过一份报告作者记录了虚拟机网络不通的完整排查过程——先ping网关确认二层通、再ip route看路由、最后发现是防火墙规则挡了转发一步步写下来比那些一切顺利的报告有说服力得多。6.2 图表与排版的几个实用做法数据用表格展示比截图强得多。命令输出截图有个问题字体小、信息密度高、关键数据淹没在一堆无关输出里。把关键数字摘出来做成表格读者一眼就能抓住重点。需要展示趋势的话用折线图比柱状图更合适。比如你要展示不同并发数下容器和虚拟机的吞吐变化横轴是并发数、纵轴是吞吐量、两条线分别代表两种方案这种图比十个柱子直观得多。作图工具无所谓能画出清晰的图就行用脚本生成的话记得把生成脚本也放在附录里体现可复现性。排版上几个小细节命令和输出用代码块包起来别混在正文里路径和参数名用行内代码标注关键结论加粗。这些看起来是小事但一份排版清晰的报告和一份排版混乱的报告给人的第一印象差距非常大。还有一个我强烈推荐的做法在报告开头放一张总的架构图手绘拍照或者用画图工具都行标出宿主机、Hypervisor、虚拟机、容器、网络的关系。这张图能让人在三十秒内明白你做了什么比读一千字说明有效。注意别用那种自动生成的流程图工具画得越简洁越好能体现层级关系就够了。7. 踩坑实录让我返工三次的几个问题7.1 虚拟机网络不通的三层排查这个问题我遇到不止一次症状是虚拟机起来了virsh list显示 running但就是 ping 不通外网。排查要分层来别一上来就瞎改配置。第一层看虚拟机的网卡状态。进虚拟机控制台virsh console vm-exp2ip addr看一下网卡有没有拿到 IP。如果是192.168.122.x网段说明 DHCP 正常问题在上层如果没有 IP先看虚拟机的网络配置是不是 DHCP 模式静态 IP 配错网段也会这样。第二层看宿主机上的 virbr0。ip addr show virbr0确认这个网桥有192.168.122.1这个地址sudo virsh net-list确认 default 网络是 active 状态。如果 virbr0 不存在sudo virsh net-start default手动拉起来。第三层看转发和 NAT 规则。sysctl net.ipv4.ip_forward应该是 1不是的话sudo sysctl -w net.ipv4.ip_forward1临时打开想持久化就写进/etc/sysctl.conf。然后看 iptables 的 nat 表里有没有 libvirt 写的 MASQUERADE 规则sudo iptables -t nat -L POSTROUTING -n如果宿主机上装了其他防火墙管理工具比如某些系统自带的防火墙服务默认会拒绝转发规则可能会被清掉或者被更高优先级的链拦截。这时候要去看防火墙的配置允许virbr0相关的转发。这三层走下来基本能定位问题。我最惨的一次是卡在第三层折腾了一个多小时才发现是系统自带的防火墙把转发全挡了加了一条允许规则立刻就好了。7.2 权限与服务状态的连锁反应有一类报错特别迷惑人virsh命令报Permission denied但你明明用的是sudo。这种情况大概率是 SELinux 或者 AppArmor 在起作用。在强制访问控制开启的系统上libvirt 访问/var/lib/libvirt/images下的文件是受策略约束的你把镜像放在别的位置比如家目录就容易被拦。判断方法getenforce # SELinux 状态 sudo ausearch -m avc -ts recent # 看最近的拒绝记录如果看到 AVC denied 的记录就是 SELinux 拦的。实验环境下可以临时设成 permissive 模式sudo setenforce 0验证一下确认是这个问题之后要么调整文件的安全上下文chcon或restorecon要么把镜像放回默认目录。不建议直接永久关闭 SELinux那属于把报警器拆了而不是解决问题。另一个连锁反应是服务依赖。Docker 依赖containerdlibvirt 依赖libvirtd有些系统上这些服务如果被手动停过或者开机顺序有问题就会出现命令能跑但行为诡异的情况。遇到说不清的问题systemctl status看一眼相关服务的状态比在网上乱搜快得多。7.3 磁盘空间被快照吃光这个坑挺隐蔽的。qcow2 的快照机制是这样的创建快照后原来的数据被标记为只读后续所有写入都进新的增量文件。也就是说你创建一个快照磁盘占用不会减少反而会随着虚拟机的继续使用而增长——基础镜像加上增量层。我做实验时为了对比不同配置连着创建了四五个快照每个都快 2G加上基础镜像和 Docker 那一堆镜像层一块 60G 的盘很快就见底了。症状是虚拟机里写文件失败宿主机上各种服务也开始报错。清理方法sudo virsh snapshot-list vm-exp2 sudo virsh snapshot-delete vm-exp2 快照名snapshot-delete默认会把增量数据合并回基础镜像所以删快照之后空间不会立刻释放可能需要等合并完成。如果想彻底清理virsh blockcommit手动合并之后再删。Docker 那边同理docker system df看一眼各项占用docker system prune -a清理无用镜像和容器注意这个命令会删掉所有未被使用的镜像包括你还没跑的执行前确认一下。我现在的习惯是每次做实验前先df -h看一眼别等出问题再查。这几个坑的共同点是它们都不会在最开始暴露而是在你操作到一半的时候突然出现让你以为是自己刚敲的命令有问题。所以养成动手前先检查环境状态的习惯比事后排查省事得多。最后分享一个我做这类实验的小习惯所有命令都写进一个脚本文件按步骤分节每一步前面加注释说明意图。这样一是可以重复执行二是出了问题能快速定位到是哪一步开始不对的三是写报告的时候直接引用注释比回忆当时敲了什么靠谱得多。这个习惯我从第一次做实验一直保持到现在回头看那些脚本比实验报告本身还有价值。