1. 为什么要在飞牛fnOS上折腾Ubuntu虚拟机飞牛fnOS这两年在家庭NAS和轻量级服务器圈子里热度一直不低很多人拿它当媒体中心、文件仓库、备份节点来用。但用久了就会发现一个尴尬的事fnOS本身的应用生态虽然够日常使用可真要跑一些开发环境、编译任务、Docker容器编排、甚至是一些只有Linux桌面版才支持的工控软件时它的底层能力就显得捉襟见肘了。尤其是ARM架构的NAS设备很多x86平台上一行命令就能装好的东西到了ARM上要么没有预编译包要么依赖链断裂折腾半天跑不起来。我手头这台飞牛fnOS设备是ARM芯片的方案平时主要用来做家庭影音库和同步盘。但最近有个需求需要在本地跑一套Ubuntu环境来做Python数据处理和几个轻量级服务的测试。最开始想的是直接SSH上去装个Docker凑合结果发现fnOS的Docker对ARM镜像的支持并不完整很多官方镜像压根没有arm64的tag自己编译又太费时间。于是就有了这个方案——在fnOS上通过KVM跑一个Ubuntu虚拟机把ARM NAS的生产力真正释放出来。这个思路的核心逻辑其实很简单fnOS底层本身就是Linux内核只要内核编译时开启了KVM相关模块理论上就具备跑虚拟机的能力。ARM架构下的KVM虚拟化在服务器领域已经非常成熟像RK3588这类芯片本身就支持硬件虚拟化扩展。把Ubuntu跑在虚拟机里既能享受完整的Ubuntu软件生态又不会污染fnOS本身的环境出问题了直接删掉虚拟磁盘重来干净利落。这篇文章适合三类人看一是手里有ARM NAS设备、想扩展使用场景的玩家二是需要在低功耗设备上跑Linux开发环境但不想买额外硬件的开发者三是对KVM虚拟化感兴趣、想找个实际场景练手的技术爱好者。我会把整个流程拆开讲清楚包括前期检查、镜像选择、KVM配置、网络打通、性能调优以及我踩过的那些坑。提示ARM平台上的KVM虚拟化和x86平台在操作细节上有不少差异尤其是UEFI固件和virtio驱动的处理方式不能直接照搬x86的教程。2. 动手之前必须确认的三件事2.1 你的ARM芯片到底支不支持硬件虚拟化这是整个方案能不能成立的前提。ARM架构下硬件虚拟化扩展通常叫EL2异常级别支持不同芯片厂商的实现方式不一样。像瑞芯微RK3588、晶晨A311D这类芯片是明确支持KVM的但一些低端的ARM Cortex-A53/A55方案可能压根没做虚拟化扩展或者厂商在固件层面给关掉了。检查方法很直接SSH登录fnOS之后执行cat /proc/cpuinfo | grep -i Features在输出里找这几个关键字virt、hyp、kvm。如果能看到virt或者hyp说明CPU层面支持虚拟化。如果什么都没有那这个方案基本走不通别浪费时间了。另一个要确认的是内核模块lsmod | grep kvm正常情况下应该能看到kvm和kvm_arm或者kvm_arm_vgic这两个模块。如果没加载试试手动加载modprobe kvm modprobe kvm_arm加载失败的话大概率是fnOS的内核编译时没把KVM编进去这种情况要么换内核要么放弃。2.2 fnOS的存储空间够不够折腾虚拟机磁盘镜像、ISO安装镜像、快照文件这些都是吃存储的大户。一个最小化的Ubuntu Server安装大概需要8-10GB磁盘空间加上ISO镜像本身2-3GB再留点余量给快照和临时文件建议至少准备30GB的可用空间。用df -h看一下当前挂载点的剩余空间。fnOS通常会把数据盘挂载在/vol1或者/mnt下面具体路径看你的配置。我建议把虚拟机相关文件放在数据盘上不要放在系统分区免得系统更新或者重置的时候把虚拟机一起搞没了。df -h | grep -E vol|mnt|data如果空间紧张可以考虑用qcow2格式的稀疏磁盘它只会随着实际写入数据慢慢增长不会一上来就占满你分配的全部空间。2.3 内存和CPU核心怎么分配才合理ARM NAS设备的内存通常不大4GB是常见配置8GB就算比较宽裕了。给虚拟机分配内存的时候要留够给fnOS本身用的量一般建议fnOS保留1.5-2GB剩下的再分给虚拟机。举个例子8GB内存的设备可以给Ubuntu虚拟机分4GBfnOS自己留4GB这样两边都不会太紧张。4GB内存的设备就比较尴尬了最多给虚拟机分2GB跑个命令行版的Ubuntu Server还行图形界面就别想了。CPU核心方面ARM NAS通常是4核或者8核。给虚拟机分配2个核心是比较稳妥的选择既保证了虚拟机的响应速度又不会让宿主机卡顿。如果你的设备是8核可以给到4核但要注意散热ARM设备长时间高负载运行温度会比较高。设备内存建议分配给虚拟机建议CPU核心适用场景4GB1.5-2GB2核命令行服务、轻量脚本8GB3-4GB2-4核开发环境、Docker测试16GB及以上6-8GB4核多服务并行、编译任务3. Ubuntu镜像的选择与ARM架构适配3.1 为什么不能随便下个Ubuntu ISO就用这是ARM平台新手最容易踩的坑。x86平台上你从Ubuntu官网下载的ISO镜像可以直接用来装虚拟机因为它是通用的x86_64架构。但ARM平台不一样Ubuntu为ARM提供了专门的镜像而且根据用途分了好几种类型。如果你直接拿x86的ISO往ARM虚拟机上挂启动的时候会直接报错提示无法执行二进制文件或者架构不匹配。这个错误信息有时候不太直观新手可能会以为是虚拟机配置的问题其实是镜像本身就不对。ARM平台需要的Ubuntu镜像是arm64架构的下载的时候要认准文件名里的arm64标识。Ubuntu官方为ARM提供了两种主要镜像一种是给物理设备用的preinstalled镜像另一种是通用的live-server镜像。对于KVM虚拟机来说我推荐用live-server版本它的安装流程和x86版本基本一致兼容性也更好。3.2 具体下载哪个版本截至我写这篇文章的时候Ubuntu 24.04 LTSNoble Numbat是ARM平台上支持最完善的长期支持版本。22.04 LTS也很稳定但一些新的软件包版本会偏旧。如果你需要比较新的内核特性或者软件栈选24.04如果追求极致稳定22.04也没问题。下载地址在Ubuntu官方镜像站上找releases目录下的对应版本然后进release文件夹下载文件名类似ubuntu-24.04-live-server-arm64.iso的文件。文件大小大概在2-3GB左右。下载完之后建议校验一下SHA256确保文件没损坏sha256sum ubuntu-24.04-live-server-arm64.iso对比官网公布的校验值一致的话就没问题。3.3 镜像传输到fnOS上的注意事项把ISO文件传到fnOS上有几种方式SCP、Samba共享、U盘挂载。我一般用SCP简单直接scp ubuntu-24.04-live-server-arm64.iso userfnos-ip:/vol1/vms/iso/传输大文件的时候注意网络稳定性ARM NAS通常走的是千兆网口2-3GB的文件大概需要半分钟左右。如果中途断了重新传一遍比断点续传省事。传完之后确认一下文件完整性有时候SCP传输过程中出问题会导致文件损坏装到一半报错很让人头疼md5sum /vol1/vms/iso/ubuntu-24.04-live-server-arm64.iso注意ISO文件存放的目录最好和虚拟机磁盘文件放在同一个存储卷下这样创建虚拟机的时候不需要跨存储卷拷贝数据速度会快很多。4. KVM虚拟机的创建与核心参数配置4.1 用virt-install还是手动配置QEMU在fnOS上创建KVM虚拟机有两条路一条是用virt-install这个命令行工具它会帮你自动生成libvirt的XML配置文件另一条是直接用QEMU命令启动所有参数手动指定。对于新手来说virt-install更友好它会处理很多底层细节比如自动选择virtio驱动、生成合理的默认配置。但ARM平台上virt-install有时候会选错UEFI固件路径导致虚拟机启动失败。手动QEMU的方式虽然参数多但每一步都可控出了问题也容易定位。我个人的习惯是先用virt-install生成一个基础配置然后根据ARM平台的特点手动调整XML文件。这样既省事又灵活。先确认fnOS上装了必要的工具which virt-install qemu-system-aarch64 virsh如果缺哪个就装哪个。fnOS基于Debian用apt安装即可apt update apt install qemu-system-arm libvirt-daemon-system virtinst4.2 UEFI固件ARM虚拟机的启动关键ARM架构的虚拟机启动方式和x86有本质区别。x86上BIOS或者UEFI固件通常是主板自带的虚拟机直接调用就行。ARM平台上QEMU需要加载一个叫QEMU_EFI.fd的UEFI固件文件来模拟启动环境。这个文件通常在qemu-efi-aarch64或者edk2-aarch64包里。安装完之后固件文件一般在/usr/share/qemu-efi-aarch64/或者/usr/share/AAVMF/目录下。find /usr/share -name QEMU_EFI.fd 2/dev/null找到路径之后记下来创建虚拟机的时候需要指定。如果找不到装一下对应的包apt install qemu-efi-aarch64有些fnOS版本可能没有这个包那就需要从其他ARM Linux发行版的仓库里找或者直接从edk2的源码编译。编译过程比较折腾建议优先找现成的deb包。4.3 磁盘镜像格式的选择KVM支持多种磁盘镜像格式常用的有raw和qcow2。raw格式性能最好但文件大小是固定的你分配50GB它就占50GB。qcow2支持稀疏分配和快照文件会随着数据写入慢慢增长更适合存储空间有限的NAS设备。创建qcow2磁盘qemu-img create -f qcow2 /vol1/vms/ubuntu-disk.qcow2 40G这条命令创建了一个最大40GB的qcow2磁盘但实际占用空间只有几百KB。随着Ubuntu系统安装和数据写入文件会逐渐变大。如果你对性能有极致要求可以用raw格式但要做好空间规划。我实测下来qcow2在ARM NAS上的性能损失大概在5%-10%左右日常使用基本感觉不出来。4.4 完整的virt-install命令拆解下面是我实际使用的virt-install命令逐段解释每个参数的作用virt-install \ --name ubuntu-arm \ --memory 4096 \ --vcpus 2 \ --cpu host-passthrough \ --disk path/vol1/vms/ubuntu-disk.qcow2,formatqcow2,busvirtio \ --cdrom /vol1/vms/iso/ubuntu-24.04-live-server-arm64.iso \ --boot uefi \ --os-variant ubuntu24.04 \ --network networkdefault,modelvirtio \ --graphics vnc,listen0.0.0.0,port5901 \ --noautoconsole--cpu host-passthrough让虚拟机直接使用宿主机的CPU特性性能最好但迁移到其他机器上可能会不兼容。如果只是本地用这个选项没问题。--disk里的busvirtio指定了磁盘总线类型virtio是半虚拟化驱动性能比模拟的IDE或者SATA好很多。ARM平台上virtio的支持很成熟放心用。--boot uefi告诉libvirt用UEFI方式启动ARM平台必须用这个。有些版本的virt-install会自动检测但手动指定更保险。--graphics vnc开启了VNC远程控制台这样你可以从另一台电脑上用VNC客户端连接过去操作安装界面。端口我用了5901避免和fnOS本身可能占用的5900冲突。--noautoconsole让命令执行完直接返回不自动打开控制台。因为fnOS通常没有图形界面自动打开控制台会报错。4.5 安装过程中的VNC连接命令执行成功之后虚拟机就处于运行状态了等待你通过VNC连接过去完成Ubuntu的安装。用任意VNC客户端连接fnos-ip:5901就能看到Ubuntu的安装界面。安装过程和物理机上装Ubuntu没什么区别选语言、分区、设置用户名密码、选装软件包。有几点ARM平台特有的注意事项分区的时候Ubuntu的自动分区方案在ARM上也能用但如果你要手动分区记得EFI系统分区ESP要格式化成FAT32大小给512MB到1GB。ARM平台的UEFI固件对ESP分区比较挑剔太小了可能放不下启动文件。软件包选择方面如果你只是跑命令行服务选Ubuntu Server最小化安装就行。需要图形界面的话可以选Ubuntu Desktop但ARM NAS的性能跑桌面会比较吃力建议慎重。安装完成后虚拟机会自动重启这时候记得把ISO从虚拟光驱里弹出来否则又会从ISO启动进入安装界面。用virsh命令操作virsh change-media ubuntu-arm sda --eject或者直接编辑XML把cdrom那一行删掉。5. 网络配置让虚拟机和宿主机互通5.1 默认NAT模式的局限性virt-install默认创建的网络是NAT模式虚拟机会分配到一个192.168.122.x网段的IP通过宿主机做地址转换访问外网。这种模式下虚拟机可以主动访问外网和宿主机但宿主机和其他设备无法主动访问虚拟机。对于跑服务来说NAT模式很不方便。你没法从局域网的其他设备直接访问虚拟机上的Web服务或者SSH每次都要先登录fnOS再跳转很麻烦。5.2 桥接模式让虚拟机融入局域网桥接模式是更实用的选择。虚拟机会像一台独立的设备一样接入你的局域网获得和fnOS同网段的IP地址局域网内其他设备可以直接访问它。在fnOS上创建网桥需要修改网络配置。fnOS用的是NetworkManager或者systemd-networkd具体看版本。先确认当前网络管理工具systemctl status NetworkManager systemctl status systemd-networkd如果是NetworkManager可以用nmcli创建网桥nmcli con add type bridge ifname br0 nmcli con add type bridge-slave ifname eth0 master br0 nmcli con mod br0 ipv4.addresses 192.168.1.100/24 nmcli con mod br0 ipv4.gateway 192.168.1.1 nmcli con mod br0 ipv4.dns 192.168.1.1 nmcli con mod br0 ipv4.method manual nmcli con up br0把eth0换成你实际的网卡名称IP地址换成你局域网里空闲的地址。创建完网桥之后fnOS本身的网络会短暂中断SSH可能会断开建议在本地终端操作或者用nohup方式执行。然后在虚拟机的XML配置里把网络改成桥接interface typebridge source bridgebr0/ model typevirtio/ /interface改完重启虚拟机它就会从你的路由器获取一个局域网IP和fnOS平级。5.3 网络性能的实测对比我在自己的设备上做了个简单的对比测试用iperf3测虚拟机到宿主机的带宽网络模式平均带宽延迟NAT模式约850Mbps0.3ms桥接模式约920Mbps0.2ms差距不算大但桥接模式在延迟和吞吐量上都有优势而且省去了端口转发的麻烦。对于需要长期跑服务的场景桥接是更好的选择。注意桥接模式下虚拟机会占用一个局域网IP如果你的路由器DHCP地址池比较小记得给虚拟机分配一个静态IP或者在路由器上做MAC绑定避免IP冲突。6. 性能调优与日常维护6.1 virtio驱动到底有没有生效virtio是KVM的半虚拟化驱动框架磁盘、网络、显卡都可以用virtio驱动来提升性能。但前提是虚拟机里装了对应的驱动。Ubuntu的内核默认就包含了virtio驱动装好系统之后一般不需要额外操作。验证方法是在虚拟机里执行lsmod | grep virtio应该能看到virtio_pci、virtio_blk、virtio_net等模块。如果磁盘用的是virtio-blklsblk看到的磁盘设备名会是vda而不是sda这是正常的。网络方面ip link看到的网卡名通常是enp1s0之类的用ethtool -i enp1s0可以看到driver是virtio_net。6.2 CPU和内存的热调整KVM支持在虚拟机运行状态下调整CPU和内存但ARM平台的支持程度取决于内核版本和QEMU版本。内存热插拔需要虚拟机内核支持memory hotplugUbuntu 24.04的内核是支持的。调整内存virsh setmem ubuntu-arm 6G --live virsh setmaxmem ubuntu-arm 8G --config第一条命令把当前内存调到6GB第二条把最大内存限制设为8GB下次启动时可以分配更多。CPU热插拔在ARM上支持得不太好通常需要关机再改配置。改CPU数量的命令virsh setvcpus ubuntu-arm 4 --config --maximum virsh setvcpus ubuntu-arm 4 --config改完需要重启虚拟机生效。6.3 快照折腾前的后悔药在虚拟机上折腾新软件或者改配置之前先打个快照。出问题了直接回滚比重新装系统快得多。创建快照virsh snapshot-create-as ubuntu-arm snap1 before docker install查看快照列表virsh snapshot-list ubuntu-arm回滚到快照virsh snapshot-revert ubuntu-arm snap1qcow2格式的磁盘支持内部快照快照文件直接存在磁盘镜像里管理起来比较方便。但要注意快照会占用额外的磁盘空间而且快照链太长会影响性能。建议定期清理不需要的快照。6.4 开机自启与后台运行fnOS重启之后虚拟机默认不会自动启动。如果虚拟机里跑着重要的服务需要配置自启virsh autostart ubuntu-arm这条命令会在/etc/libvirt/qemu/autostart/目录下创建一个符号链接libvirt服务启动的时候会自动拉起虚拟机。另外确认一下libvirt服务本身是开机自启的systemctl enable libvirtd systemctl is-enabled libvirtd6.5 磁盘空间回收qcow2磁盘用久了删除文件之后宿主机上的镜像文件不会自动缩小。需要手动执行trim操作# 在虚拟机里执行 fstrim -av # 在宿主机上执行 qemu-img convert -O qcow2 /vol1/vms/ubuntu-disk.qcow2 /vol1/vms/ubuntu-disk-compacted.qcow2第一条命令让虚拟机通知宿主机哪些块已经不再使用第二条命令重新整理镜像文件把空闲块真正释放掉。整理过程中需要额外的磁盘空间存放新镜像整理完替换掉旧文件即可。7. 我踩过的那些坑7.1 UEFI固件路径错误导致虚拟机启动黑屏第一次创建虚拟机的时候virt-install自动检测的UEFI固件路径是/usr/share/AAVMF/AAVMF_CODE.fd但fnOS上实际的文件名是QEMU_EFI.fd路径也不一样。结果虚拟机启动之后VNC里一片黑没有任何输出。排查过程先用virsh dumpxml ubuntu-arm看XML里的loader路径发现指向了一个不存在的文件。手动改成实际路径之后问题解决。loader readonlyyes typepflash/usr/share/qemu-efi-aarch64/QEMU_EFI.fd/loader这个坑的教训是ARM平台上不要完全信任virt-install的自动检测创建完虚拟机之后检查一下XML里的loader路径。7.2 忘了弹ISO导致反复进入安装界面Ubuntu安装完重启之后如果没有把ISO从虚拟光驱里弹出来虚拟机会又从ISO启动再次进入安装界面。我第一次遇到的时候还以为安装失败了折腾了半天才发现是启动顺序的问题。解决方法前面提过了用virsh change-media弹出ISO或者编辑XML把cdrom设备删掉。更彻底的做法是调整启动顺序把硬盘放在第一位os type archaarch64 machinevirthvm/type loader .../ boot devhd/ boot devcdrom/ /os7.3 网络桥接后SSH断连创建网桥的时候fnOS的IP地址会从物理网卡转移到网桥上这个过程会导致SSH连接中断。如果你是在SSH会话里执行nmcli命令命令执行到一半连接就断了网桥可能只创建了一半网络配置处于混乱状态。我的做法是写一个脚本用nohup后台执行或者直接在设备的本地终端上操作。如果只能SSH操作可以用screen或者tmux保持会话tmux new -s bridge # 在tmux会话里执行网络配置命令 # 如果断连了重新SSH上来执行 tmux attach -t bridge7.4 内存分配过多导致宿主机OOM有一次给虚拟机分配了6GB内存但fnOS本身只有8GB总内存加上系统缓存和其他服务占用宿主机内存耗尽触发了OOM Killer把libvirt进程杀掉了虚拟机也跟着挂了。教训是给虚拟机分配内存的时候一定要给宿主机留够余量。一般建议宿主机保留至少2GB可用内存。可以用free -h观察实际内存使用情况动态调整虚拟机的内存分配。7.5 ARM平台上的Docker嵌套问题在Ubuntu虚拟机里跑Docker是常见需求但ARM平台上Docker的镜像生态和x86有差异。很多官方镜像没有arm64版本拉取的时候会报no matching manifest错误。解决办法有几个一是找社区维护的arm64镜像比如arm64v8/前缀的镜像二是用docker buildx自己构建多架构镜像三是用QEMU用户态模拟来跑x86镜像但性能损失很大只适合测试。我一般优先找arm64原生镜像实在没有再考虑自己构建。构建的时候注意基础镜像也要选arm64版本否则整条链都跑不通。8. 这套方案到底值不值得折腾从实际使用体验来看在飞牛fnOS的ARM设备上跑Ubuntu虚拟机性能表现比我预期的要好。日常的Python脚本执行、轻量级Web服务、Docker容器测试响应速度都很流畅。编译一些中小型项目也能接受比在宿主机上直接装环境要干净得多。资源占用方面虚拟机待机状态下大概占用300-500MB内存CPU占用率在5%以下。跑满负载的时候CPU会到80%-90%温度会上升10-15度建议给设备加个散热风扇或者放在通风好的位置。这套方案最适合的场景是你有一台ARM NAS平时主要做存储和媒体服务但偶尔需要跑一些Linux开发环境或者测试服务。用虚拟机的方式隔离出来既不影响NAS本身的功能又能随时切换到一个完整的Ubuntu环境里干活。不太适合的场景是需要长时间高负载运行、对性能要求极高的任务。ARM NAS的CPU性能和内存带宽毕竟有限跑编译或者大规模数据处理会比较吃力。这种需求还是建议上x86的迷你主机或者云服务器。最后分享一个实用小技巧如果你经常需要重建虚拟机环境可以把安装好的Ubuntu虚拟机磁盘做成模板。需要新环境的时候直接复制模板磁盘文件改一下虚拟机名称和MAC地址几分钟就能拉起一台全新的Ubuntu比从头安装快得多。