
前阵子在一台ARM服务器上折腾KVM虚拟化从确认硬件支持、装好libvirt到第一个ARM虚拟机正常联网跑业务前前后后花了两天时间。整个过程和x86平台上做KVM的思路高度相似但坑点完全不一样——固件、镜像、设备模型、网卡驱动稍不留意就会卡住。这篇就把ARM服务器虚拟化从KVM安装到虚拟机创建的完整流程、关键参数和排错经验整理出来给刚接触ARM平台的运维、虚拟化开发以及想用ARM服务器多开测试环境的同学做个参考。1. 为什么要在ARM服务器上做KVM虚拟化1.1 先搞清楚ARM和x86的虚拟化差异很多人一听到ARM服务器第一反应是“这玩意能不能跑虚拟机”其实ARMv8-A架构原生就支持虚拟化扩展KVM在ARM64平台上已经是主流方案。核心差异在于x86的虚拟化依赖Intel VT-x或AMD-VARM则靠异常级别EL2也就是hypervisor运行层。KVM作为Linux内核模块运行在内核态正好利用EL2把虚拟机的指令执行和资源访问隔离起来性能和裸机差距已经控制得很小。另一个差异是设备模型。x86上有Legacy BIOS/UEFI、PCIe拓扑、传统IDE/SATA控制器ARM平台上更常见的是QEMU virt机型和UEFI固件AAVMF。ARM虚拟机一般跑的也是ARM64内核所以在虚拟设备层面大量使用virtio——半虚拟化的virtio-net、virtio-blk、virtio-scsi几乎成了标配。如果你拿x86的镜像或x86的配置习惯去套ARM环境最容易踩的就是固件不对、机器类型不对、virtio驱动没进initramfs。1.2 为什么选KVM而不是容器或纯模拟器ARM服务器上的业务隔离方案无非几种Docker容器、QEMU纯软件模拟、KVM虚拟化。容器的隔离靠内核namespace和cgroup共享宿主机内核密度高、启动快但遇到需要独立内核、独立内核参数、独立驱动栈的场景就不够用了。纯QEMU模拟器比如Linux系统下的TTC、或者说纯粹用TCG模式跑不同架构镜像能模拟但速度感人不适合作为生产虚拟化方案。KVM选择的是“内核虚拟化用户态设备模拟”的组合KVM模块负责CPU和内存的虚拟化QEMU负责设备模拟和生命周期管理。这个组合的好处是稳定、成熟、生态大libvirt管理工具也齐全。我在实际项目里最终使用KVM因为我需要跑多个不同发行版的ARM系统镜像要有独立的网络栈、独立的系统服务、独立的崩溃恢复边界这些容器给不了。1.3 这套东西适合谁、能解决什么问题如果你是运维工程师手头有ARM服务器想在一台物理机上分出多个业务虚拟机如果你是嵌入式或底层开发者需要反复测试ARM64的发行版镜像又不想频繁折腾物理机或者你只是好奇ARM平台上虚拟化到底怎么做这篇文章的流程都可以直接抄作业。ARM服务器虚拟化的收益也很明确硬件利用率更高、环境隔离更干净、测试环境可以任意销毁重建。实践中最常见的几个用途包括跑ARM版云镜像做CI构建节点、验证ARM架构下的数据库和中间件兼容性、给没有图形环境的ARM服务器提供独立调试环境。2. 硬件准备与KVM依赖安装2.1 检查CPU虚拟化支持是不是白折腾的开始无论宿主机是什么发行版第一步永远是确认硬件和内核有没有准备好。最直接的检查方式lscpu | grep -i virtualization cat /proc/cpuinfo | grep -i virt ls -l /dev/kvmARM64平台上lscpu输出里会看到Virtualisation字段标成KVM/proc/cpuinfo里能看到virt特性关键字。最关键的是/dev/kvm这个设备文件libvirt和QEMU都要靠它和内核交互。如果/dev/kvm不存在大概率是内核没有加载kvm模块lsmod | grep kvm modprobe kvmARM平台常见的KVM模块名是kvm部分内核会按机器类型细分比如kvm_vhe或者kvm_nvhe但统一入口还是kvm。加载不了时先检查固件里有没有开启虚拟化扩展不少ARM服务器在BIOS/固件里默认关掉需要进UEFI设置打开才能看到/dev/kvm。内存和磁盘也提前算好。一个2GB内存的虚拟机配合20GB虚拟磁盘宿主机至少要留出4GB以上空闲内存磁盘分区建议用LVM或者预留独立卷组方便后期扩容。这个环节看起来基础但实际有相当比例的人前面全对最后倒在没有/dev/kvm上。2.2 KVM、QEMU和libvirt组件的安装细节以Ubuntu/Debian系ARM服务器为例安装命令并不复杂sudo apt update sudo apt install qemu-system-arm qemu-utils libvirt-daemon-system libvirt-clients virtinst -y这里有个容易忽略的点qemu-system-arm包提供的是QEMU arm和arm64系统模拟程序qemu-system-aarch64也在其中。如果只装了qemu-kvm或者x86的包后面启动ARM虚拟机经常会提示找不到可用的machine type。安装完成后启动libvirtdsudo systemctl enable --now libvirtd sudo systemctl status libvirtdlibvirtd正常后验证工具链是否就绪virsh version virsh list --all第一次执行virsh list如果提示连接失败看一下libvirt-daemon-system的服务是否挂在systemd下以及当前用户是否在libvirt组里。建议把常用账号加进libvirt和kvm两个组sudo usermod -aG libvirt,kvm $USER重新登录后日常操作就不用总加sudo了。ARM平台这一步和x86几乎一致差异主要在qemu包名和机器类型上。2.3 ARM平台安装阶段就要注意的两个细节第一发行版软件包要选对架构。ARM服务器一般跑aarch64版系统APT会自动安装arm64的包这点一般不会错。但如果你是从x86上把整个根文件系统迁移过来或者用了一些奇怪的第三方源可能出现包架构混装表现为QEMU启动时报exec format error或者无法识别PE格式。用file命令快速确认file /usr/bin/qemu-system-aarch64第二确认机器类型是否完整。QEMU的机器类型决定了虚拟总线拓扑ARM平台最常用的是virt-4.2、virt-5.2这些带版本号的机型它们通过ACPI和GPEX桥模拟PCIe设备。安装后可以用下面命令看看当前QEMU支持哪些qemu-system-aarch64 -machine help | grep virt实际生产环境中我基本只用virt机型它和KVM虚拟机配合最稳、virtio设备的支持也最完整。3. ARM虚拟机镜像的准备与格式处理3.1 从哪获取可靠的ARM镜像创建ARM虚拟机最关键的一步是拿到能启动的ARM64系统镜像。官方渠道首推各大发行版的cloud镜像比如Ubuntu Cloud Images里带arm64标识的qcow2文件Debian官网的generic/cloud arm64镜像也是直接可用的qcow2格式。这类镜像已经预装cloud-init、打过virtio驱动、可以调整根分区大小是KVM环境下最省事的起点。下载时留心三点文件名里是否明确写了arm64、aarch64别下成amd64qcow2镜像的SHA256校验值要核对下载后先用qemu-img info确认格式再创建虚拟机。部分镜像网站还会给出具体的虚拟机类型和最小内存建议也值得大致扫一眼。如果你需要的是老版本系统比如要跑一个CentOS 7 ARM兼容环境官方镜像链接可能已经下线此时需要本地手工构建或者寻找社区长期归档的镜像。社区镜像质量参差不齐我的原则是优先选维护时间久、更新频繁的项目下载后多跑几步自检避免带着脏镜像排错浪费半天。3.2 镜像格式qcow2和raw到底怎么选ARM虚拟化里img和qcow2两种格式都很常见。img通常是裸格式raw磁盘内容完全按偏移写入性能直接但占空间大快照和稀疏分配能力也很弱。qcow2是QEMU的写时复制格式支持稀疏文件、快照、压缩、加密日常使用更推荐。如果手里只有raw格式或者别的虚拟化格式可以转换qemu-img convert -f raw -O qcow2 input.img output.qcow2反过来也是同样的思路conver命令的-f和-O对调即可。转换完成后务必再看一眼qemu-img info output.qcow2重点关注disk size和virtual size。disk size是实际占用宿主机磁盘的空间virtual size是虚拟机的逻辑磁盘大小。如果你在宿主机看到的磁盘占用远小于预期文件大小说明稀疏特性生效了这是正常现象。3.3 seed镜像用cloud-init完成首次登录准备ARM cloud镜像默认不允许root直接登录也没有预设密码直接启动会卡在不知道该用什么账号进去。解决办法是准备一个cloud-init的seed镜像挂给虚拟机或者叫nocloud ISO。最常用的工具是cloud-localdssudo apt install cloud-image-utils -y cat seed.cfg EOF #cloud-config password: your-password chpasswd: { expire: False } ssh_pwauth: True EOF cloud-localds seed.iso seed.cfgcloud-localds会把配置写成一个ISO镜像启动时作为虚拟光驱挂载给虚拟机。cloud-init会在首次启动时读取它完成账号密码设置和SSH key写入。如果你不需要密码登录而是用密钥也可以在seed.cfg里加ssh_authorized_keys字段。这里还有个小细节不同发行版cloud-init读取的label不太一样Ubuntu要求ISO卷标为cidatacloud-localds生成的镜像默认就是cidata直接用就好。某个版本如果遇到启动后没有执行cloud-init先确认虚拟机的光驱总线是不是被识别为virtio而不是IDE。3.4 磁盘扩容和分区调整ARM cloud镜像默认虚拟磁盘一般只有2到10GB明显不够用。创建虚拟机前先给一个定制大小qemu-img create -f qcow2 arm-test.qcow2 30G把官方基础镜像的根分区整体倒进去再动态扩容qemu-img convert -O qcow2 base-arm64.img arm-test.qcow2 qemu-img resize arm-test.qcow2 30G光改虚拟尺寸还不够虚拟机启动后分区表和文件系统还是原来的大小进入虚拟机执行growpart和resize2fs或者更简单的方式是直接依赖cloud-init的growpart模块它会在首次启动自动扩展根分区。cloud-init配置里确认有growpart、resizefs这两个模块存在即可。如果用的是非cloud镜像才需要手动fdisk和resize2fs步骤会繁琐一些。4. 虚拟机创建完整实操流程4.1 virt-install一行命令创建ARM虚拟机当你已经准备好qcow2磁盘镜像和seed.iso之后用virt-install创建虚拟机比手写XML快得多而且参数语义非常清晰。下面是我实际用过的创建命令sudo virt-install \ --name arm-test \ --virt-type kvm \ --ram 2048 \ --vcpus 2 \ --disk path/var/lib/libvirt/images/arm-test.qcow2,formatqcow2,busvirtio \ --disk path/var/lib/libvirt/images/seed.iso,devicecdrom \ --os-variant ubuntu22.04 \ --network networkdefault,modelvirtio \ --graphics none \ --console pty,target_typeserial \ --import逐参数解释一下方便你按需调整--virt-type kvm指定走KVM加速而不是纯QEMU模拟如果写成qemu性能断崖下跌--ram和--vcpus按宿主机资源余量给ARM服务器核心多常见配置是4核8G起步--disk path指定磁盘路径format改成qcow2bus指定virtio这是ARM平台性能关键--disk devicecdrom把seed镜像挂成光驱cloud-init才能读到--network networkdefault走libvirt默认NAT网络外部再映射端口--graphics none配合--console pty纯命令行环境没有窗口也能进控制台--import告诉virt-install直接从已有磁盘启动不再进行系统安装流程执行完成后终端会显示虚拟机的域ID和状态。ARM平台下加--console pty,target_typeserial非常必要否则一旦网络没通你连备用的字符终端入口都可能没有。创建成功后用virsh管理virsh list --all virsh start arm-test4.2 为什么ARM平台要特别留意机型UEFI固件x86上默认BIOS就能启动绝大多数系统ARM却不行。ARM云镜像普遍带EFI分区引导流程依赖UEFI固件AAVMF。virt-install在ARM平台一般会自动探测并使用/usr/share/AAVMF/AAVMF_CODE.fd但如果你跳过virt-install手动virsh define一个手写XML忘了loader字段会直接启动失败。判断固件有没有配好的办法是看启动日志如果虚拟机一直停在黑屏卡死、或virsh start后立刻回到offline状态大概率是UEFI缺失。可以在virt-install命令里显式加上--boot uefi或者在XML中手动指定loader。这个细节直接决定镜像能不能引导我在ARM虚拟化里踩过的最深坑就是这里。4.3 网络拓扑NAT还是桥接ARM虚拟化里的网络配置和x86逻辑一致但实际用法有讲究。libvirt默认提供一个名为default的NAT网络虚拟机可以上网宿主机也能访问到虚拟机但外部主机无法直接连进来。如果虚拟机只用于内部测试用default最省事virsh net-list --all virsh net-start default如果虚拟机要对外提供服务建议改成桥接。宿主机配一个bridge接口虚拟机直接挂在物理网络里。以netplan为例可以新建一个bridge接口再用libvirt的virbr0之外的桥接方式。常见配置是把物理网卡加入br0network: version: 2 renderer: networkd ethernets: enp3s0: dhcp4: no bridges: br0: interfaces: [enp3s0] dhcp4: yes然后告诉libvirt使用系统已有桥接sudo virsh net-list --all # 使用bridge模式时直接在virt-install里写 --network bridgebr0,modelvirtio这里注意ARM平台虚拟机内网卡默认叫ens命名和硬件直通无关。网络模型强烈建议virtio模拟的e1000或rtl8139虽然启动兼容性高但性能差很多对ARM这种注重能效的平台不划算。4.4 创建后如何快速验证虚拟机状态创建流程结束不代表虚拟机真的可用我一般用下面几个命令依次验证virsh list --all virsh domifaddr arm-test virsh console arm-testvirsh domifaddr会列出虚拟机的接口IP前提是虚拟机内DHCP已经拿到地址。如果这里空白的大概率cloud-init没跑通或者网卡模型没配对。virsh console则是直接进入串行控制台登录后在guest内检查ip a df -h cat /proc/cpuinfo | grep -i model看到处理器是CPU implementer等ARM标识同时根分区已经扩容到目标大小这套ARM虚拟机就算真正创建成功且可以投入使用了。到这里流程基本走完但后面还有一堆实际使用中的坑值得专门写一写。5. 常见问题读取与排查技巧实录5.1 启动失败黑屏、立刻离线、固件不匹配ARM虚拟化最典型的启动问题就是黑屏或virsh start后虚拟机立刻回到关闭状态。排查顺序固定为先看libvirt日志再看QEMU输出。sudo tail -f /var/log/libvirt/qemu/arm-test.log日志里经常出现的关键词有“failed to initialize KVM”“Could not open /dev/kvm”“Unable to find any firmware”。三种情况应对完全不同/dev/kvm缺失按第2章检查内核模块和固件虚拟化开关firmware找不到安装AAVMF包并给virt-install补--boot uefi参数machine type不匹配在virt-install加--machine virt或者在XML中写明machinevirt-4.2有一次排查半天发现是模板里的machine写成pc了那是x86的机型ARM平台上直接起不来。建议创建前用qemu-system-aarch64 -machine help确认你用的机型在列表内。5.2 虚拟机起来了但IP拿不到网卡配置玄学另一个高频问题虚拟机状态是running但virsh domifaddr看不到IP。先别急着查DHCP先看网卡模型对不对virsh domiflist arm-test确认interface model是virtio。如果在boot阶段就加载不了virtio-net驱动虚拟机内部只会看到一个不存在的网卡。路径不对时通常虚拟机里ls /sys/class/net什么都看不到。解决办法是换一块非virtio网卡先启动进入系统把virtio相关内核模块放进initramfs重建后再切回virtio。还有一种情况是cloud-init的seed没挂载。检查虚拟机光驱是不是还在virsh domblklist arm-test如果光驱设备消失或没有挂载重新添加seed.iso并重启或者直接在控制台里手动cloud-init clean cloud-init init。实测中seed.iso文件权限也要注意如果属主不是root导致libvirt无法读取XML定义没问题但启动会挂载失败。5.3 磁盘性能差CPU大量浪费在IO等待上创建虚拟机时磁盘bus没写或者写了默认的ide/sataARM平台上性能会很难看。务必将磁盘总线指定为virtio也就是--disk pathxxx,formatqcow2,busvirtio。同时cache模式可以按场景调cachenone配合写回策略适合大多数数据库类应用cachewriteback适合IO频繁但能容忍掉电丢失业务的场景cachedirectsync最安全但性能最差我在实际项目里用cachenone加virtio-blk配合图片存储在NVMe盘上吞吐量接近宿主机裸盘性能。还可以给网络开多队列sudo virsh destroy arm-test sudo virsh edit arm-test在interface节点里加上driver多队列属性ARM平台多核性能优势才能发挥出来。5.4 ARM虚拟化常见问题速查表现象常见原因处理建议创建后立刻offline未指定UEFI固件加--boot uefi或加载AAVMF黑屏无输出machine type不对或缺少串口控制台使用virt机型加--console pty,target_typeserial虚拟机内看不到网卡网卡模型不是virtio且内核缺驱动改用modelvirtio重建initramfscloud-init没生效seed.iso未挂载或卷标不对检查domblklist用cloud-localds生成cidata卷标分辨率/显示异常使用了--graphics vnc但没有VNC环境改用--graphics none串口访问性能不达标磁盘总线和缓存模式不对busvirtiocachenone扩容不生效cloud-init没有growpart模块安装cloud-growpart或手动resize2fs这个表基本覆盖了我这两天踩到的全部问题。其中印象最深的还是固件问题ARM虚拟化确实比x86多了这一层门槛但只要理解UEFI和machine type这两个关键点整个流程就通顺得多。6. 一点经验延伸ARM虚拟化还能深入玩些什么个人实际体会到ARM服务器虚拟化最大的价值不只是“把虚拟机跑起来”而是服务体系逐渐完善。现在很多中间件都出了ARM版本比如JDK 11就有arm64版本不少数据库、微服务中间件也能直接跑在ARM虚拟机上。如果你要在ARM虚拟环境里搭建业务栈第一步不是急着装服务而是先把基础镜像和网络模型稳定下来再逐层加组件。我在这个项目上最后悔的就是最开始没有先把UEFI固件和virtio驱动验证完就批量创建虚拟机结果一次性制造了几台无法启动的废域。建议新接触的同学每一层都先跑一台最小虚拟机验证再把镜像模板固化成快照后续克隆就很快了。另外ARM虚拟化后续可以研究的主线还有不少virtiofs文件共享可以让宿主机和虚拟机共享目录PCI直通可以把GPU或网卡直接给到虚拟机甚至可以在ARM服务器上结合用户态网络模拟做一些边缘场景。早一点把这些能力用起来ARM服务器就不再是只能跑几个容器的“特殊硬件”而是能承载完整虚拟化业务的基础设施。