macOS on KVM on Mac!

最近在公司的项目有一个诡异的需求,要做 macOS 的虚拟化。

其实,真正的需求是稳定可重现的 macOS 系统环境,而且同一物理机上的环境之间要有充分的隔离。考虑到 macOS 那一坨 Xcode、brew 什么的绞成一团浆糊,况且咱 Mac 机器数量也不多,还是直接上虚拟机比较靠谱。

正好,公司的虚拟化团队并没有成熟的 macOS 方案,于是乎,小小地摸鱼调研了一下在 Mac 上安装 Linux(Proxmox VE),再在上面安装 macOS 虚拟机的路子……

VirtualBox,失败的尝试

相信我,没有人会一开始就想到那么扭曲的方案的。每一个神志清醒的人,想在 Mac 上跑虚拟机,都会先尝试原生支持 macOS 的 hypervisor。

于是我们的老朋友 VirtualBox 光荣登场,安装程序、新建虚拟机顺利至极(除了 macOS 恶心的安装镜像压制环节)。可惜一跑压测,结果实在惹人嫌弃。

在我们的测试机上,压测的数据如下:

CPU (HPL)Memory (bandwidth)Disk (dd)
Bare-metal macOS199.38 Gflopsbaseline
(image)
R: ~3.21 GiB/s
W: ~1.814 GiB/s
macOS on VirtualBox on macOS95.707 Gflops
(~143.56 Gflops, -28%)
约 -1% ~ -3%
(image)
R: ~1.02 GiB/s (-68%)
W: ~1.366 GiB/s (-25%)

测试机:Mac mini 2018,处理器 6 核 12 线程,内存 16 GB

虚拟机:处理器 4 核,内存 8 GB

呃……虽然我喜欢开源项目,也愿意忍受一定的技术不足,但这也太拉垮了吧。

超过 1/4 的 CPU 性能归于虚无,IO 性能更是削到妈都不认得;内存性能虽然没啥损耗,但就这一项达标怎么用啊。

没办法,不是开源壬,信仰不足,只得放弃 VirtualBox 方案,另寻出路。

其间思考了一下商业化的解决方案,比如——Parallels Desktop。不管再怎么骂强迫更新、订阅制,别人自己写的 hypervisor 就是牛逼哄哄,CPU 和 IO 性能都能打到天花板。在自己的机器上,我还专门把 hypervisor 切回 macOS 自带的试了试,结果性能一瞬萎靡。(也许是我错怪了 VirtualBox,拉垮的是 macOS 的虚拟化框架。)

但是没钱,申经费也不知有多麻烦,公司又不可能用盗版。那还是继续找找开源方案吧。

于是就想到了 KVM。

Linux x Mac

扭曲地在 Mac 上装 Linux,实在也非本意,只是 Apple 的授权协议上限定了 macOS 只能装在 Mac 硬件上。Hackintosh 个人用用当然无人理会,但要是公司在用,谁知道会发生什么呢……

至于在 Mac 上装非 macOS 的操作系统,再在上面装虚拟化的 macOS 是否合规,VMware 的一款虚拟化产品给我们提供了保障:适用于 Mac mini 的 vSphere。在这个基于 Linux 的 hypervisor 上,是可以开 macOS 系统的 VM 的。

于是就装吧!掏出心爱的 Proxmox VE,准备大干一场。

谁知一场修罗浩劫就此展开。

经典 99%

首先是要关掉 Mac 的安全启动,允许从 U 盘的启动引导。Apple 官网上的一篇知识库文章详尽地讲述了这个过程,给人一种外部引导很容易的错觉。

实际上,Proxmox VE 安装 U 盘的引导确实很容易。ISO 镜像 dd 到设备,插上 USB 口,重启进入引导界面,选择 U 盘上的 UEFI 启动项,熟悉的界面扑面而来。于是满心欢喜地一路配置,安装条也识相地一路向前。

直到卡死在 99%,鼠标键盘全部冻僵。

卡住的那一步是「make system bootable」,看名字也是安装过程的最后一步。我就迷惑了,这难道是存心搞怪?停止的进度条让人回想起 BT 下载珍稀资源的上古时光。

在一番折腾后,我得出了以下几条重要结论:

  • 引导完全没有装上,拔掉 U 盘重启 mac,只会进入到网络恢复界面。
  • 安装盘自带的 debug 安装没有卵用,因为看日志需要点击「abort」退出安装程序,但卡住时整个电脑已经完全 freeze 掉。
  • 安装盘自带的引导助手可以用来引导系统,并正常登录,说明系统除引导的部分确实已安装完成。

虽然用安装盘可以引导,但我总不能永远插着个 U 盘用吧!

僵硬 Mac 会吃电子 Debian 吗

暂时缺乏突破口,我决定试试 Debian 和 Ubuntu。

先是 Debian,因为 Proxmox VE 是可以安装到 Debian 上的,所以就想曲线救国。于是在官网下了网络安装镜像(为了安装时拉包,还专门从桌子下方扯了根网线),烧进 U 盘,插入 Mac,重启——空空如也。

它不认 Debian 安装盘。

我晕菜了,一度以为拿的这个古董 U 盘突然升天。反复检查多次,用了好几种烧录工具,无一成功。直到我想起来 Mac 是纯 UEFI 引导的「现代机器」,然后看了下烧进去的分区表,MBR。

破案了,Debian 安装盘至今不支持标准的 EFI 引导,也就是没有使用 GUID 分区表。宽容的 PC 不会在意这些细节,而先(jiāng)进(yìng)的 Mac 则将其拒之门外。

此妖定然有解,只需要在 Windows 上使用 Rufus,将烧录分区表设定为 GPT 应该就可。但人在公司实在没有 Windows,现场 VirtualBox 装一个实属 overkill。

于是先快进到 Ubuntu。

说到这里,又要骂一通 Canonical,好好的官网给一堆第三方工具弄得花里胡哨,默认安装盘也改成了什么 LiveCD,20.04 版更是把旧版的 ISO 改名为 legacy,让我一顿好找。安装器也是有毒,LiveCD 会卡死在启动界面,而所谓的 legacy 版却顺利加载。

顺利的只是前面,正当我以为 Ubuntu 会技高一筹(至少是 GUID 分区表)时,经典一幕又发生了,安装程序卡死在了「grub-install dummy」。

WTF?为什么要给 dummy 安装 GRUB?为什么会卡死在这一步?我怀着天地不仁的沉痛心情硬重启了电脑,却出现了意外的场景:Mac 在本地磁盘上找到了引导,但进去后是 GRUB menu。

似乎有救,我 Google 到了 GRUB 回春大法,硬是引导进了系统。然后尝试修复引导,然而 grub-install 命令一跑,可怖的画面出现了:invalid opcode

CPU panic 了。这不是吐核了,核直接烂了。

硬重启。GRUB menu。linux、initrd、boot。回到 shell。我感觉自己似乎正在接近真相。

先是 Google,谁不期望自己遇到的问题早在一百年前就有人在 StackOverflow 上完美解答了呢?可是将 Mac、Linux、GRUB、invalid opcode 这几个关键词任意组合,结果寥寥无几。单是「invalid opcode」这个词组,搜出来的东西都是天马行空风马牛不相及。

我绝望了。难道是 GRUB 当前版本的神秘天坑?还是 Apple 阻止 Linux 的邪恶计划?整整一天摸出的鱼仿佛化为空气,顺着 Mac 的风扇气流融入天际。

然后我在某不知名的小论坛上看到了一句(很可惜,我没有存下 URL),invalid opcode 可能与权限有关。这句话仿佛撕开了紧绷的思绪,一切都慢慢有了条理:安装 UEFI 引导时,系统固件里存的引导项其实也会被修改;以 Apple 如此封闭之德性,允许引导第三方 OS 已是恩赐,断然不可能开放修改固件设定的接口。

于是对 grub-install 命令使用了男人。果然,在满屏蝇头小楷里(毕竟 Linux 直插 4k),我看到了两个不应缺席的选项:「–no-nvram」和「–no-uefi-secure-boot」。

事实证明,grub-install 默认不光会尝试更新主板 NVRAM 里的引导项,还会尝试给 UEFI 的安全引导存储区写入自己的证书。Mac 就好像遭遇强暴的烈女子,直接掐了自己的 CPU。

接下来就简单了。加上这两个选项,稀里哗啦秒装,重启完美进入系统。

可是,我想要的是 Proxmox VE 啊。

太子换狸猫

Ubuntu 已经在 Mac 上跑起来了,但我想要 Proxmox VE,想要它优雅的 UI 和丰满的内置能力。眼下 Debian 是不好装的,Proxmox VE 只差临门一脚,这又如何是好。

我先幻想了几分钟的 takeover.sh,但由于过于抽象,没能忍心下手。想到 Proxmox VE 是开源的,那我掏出那步的安装命令,给它 patch 上那两个选项不就好了?

果然在 GitHub 上找到了安装程序的镜像仓库,以及不分青红皂白直接调用 grub-install 的万恶之源(其实是 Apple 吧)。

随后观察了一下安装盘的结构,发现安装程序在 squashfs 里,于是解压、修改、打包,这时却遇到了如何塞回 ISO 同时保留 GUID 分区表的问题。

呃……我好像不会啊。再一次陷入困境。这时我又想到了最开始用过的 debug 安装模式,会在安装程序启动之前打开一个 shell,那我只要在这时修改一下安装脚本……

就这么干。进入 debug 安装,修改 /usr/local/bin/proxinstall​ 文件中 ​prepare_grub_efi_boot_esp​ 函数里调用 ​grub-install​ 的语句,加上「–no-nvram」和「–no-uefi-secure-boot」,然后启动安装器。

成了!It works!安装结束拔掉 U 盘,重启后直接进入熟悉的 Proxmox VE 登录界面。虽然没有得到可重用的免补丁安装介质,但至少装出来的系统是稳定的。

以前常听人说用 Mac 就会逆 Apple 者亡,这回算是亲身体验了一把。

把苹果放进企鹅里

此时,Mac mini 运行着它或许从未想象过的系统,等待着主人的指令。主人两眼一抹黑,毕竟从没在 KVM 里装过黑苹果。

幸好,到了这一步,已经有比较多的资料了。有一篇教程极为详尽地教授了如何在 Proxmox VE 里安装 macOS,大致按照它的流程操作就行。

但这并不意味着最后的考验已经结束。至少,我在 Mac mini 2018 上用 Proxmox VE 6.2 装 macOS 时,与此教程有如下出入:

  1. 获取 OSK key 的博文链接已经失效,不过可以看 Web Archive,或者直接把密钥搜出来。
  2. 虚拟机磁盘缓存策略应选择 「No cache​」,使用教程中的「Write back​」性能会原地去世。
  3. 虚拟机磁盘在装好系统前不应勾选 「SSD emulation​」和「​Discard」​,否则无法正常分区;安装完成后应勾选此两选项,否则会降低 IO 性能。

但有的地方也不能不听教程的话,比如:

  1. 不将机器类型设为 ​q35​,会导致 macOS 无法正常启动。
  2. 不将 CPU 类型设为 ​Penryn​(而是设为 ​host​),会导致 VM 时间以八倍速流逝 🐸。

别问我这些都是怎么知道的。

总之,macOS 就这么在装在 Mac 上的 Linux 里的 KVM 中跑起来了!

其实还有一些需要调教的地方,但因为只是调研,测试机也不打算长久使用,所以教程里固化 UEFI 引导及之后的操作都没有做。

Linux 的胜利

千辛万苦,千难万险,Apple 设下的重重障碍已然消逝不见。但我关心的还是性能,要是最后还不如 macOS 上的 VirtualBox,那我可能也会随风凋零溘然长逝了。

于是开始跑分程序,一番折腾后结果如下:

CPU (HPL)Memory (bandwidth)Disk (dd)
Bare-metal macOS199.38 Gflopsbaseline
(image)
R: ~3.21 GiB/s
W: ~1.814 GiB/s
macOS on VirtualBox on macOS95.707 Gflops
(~143.56 Gflops, -28%)
约 -1% ~ -3%
(image)
R: ~1.02 GiB/s (-68%)
W: ~1.366 GiB/s (-25%)
Bare-metal Linuxabnormalsimilar
(image)
R: ~1.954 GiB/s (-39.1%)
W: ~1.908 GiB/s (+5%)
macOS on KVM on Linux137.60 Gflops
(~206.4 Gflops, +3.5%)
约 -1% ~ -3%
(image)
R: ~1.648 GiB/s (-50%)
W: ~1.812 GiB/s (-0%)

跑分结果有点一言难尽。特别扎眼的是 IO 性能,Linux 裸机读性能相比 macOS 裸机竟然有近 40% 的损耗。然而在 Google 之后,我发现这个型号的机子硬盘读性能在 2 GB/s 左右是合理的。到底是系统差异,还是两台 Mac mini 体质不一,就不得而知了。

但 CPU 和 IO 比起 VirtualBox 来,都异常亮眼。KVM 的 CPU 分数高出 43%,按核数换算竟然高于 macOS 裸机。虽然 Linux 上没能跑通 HPL(丢人),但想到 macOS 的巨大多后台进程,应该裸机就是高于 macOS 的。而读性能就算比 macOS 裸机直测低了 50%,还是比 VirtualBox 里的 VM 快了 64%,写性能更是几乎没有损耗

而且,Linux 做宿主机还有特别的好处,那就是同时运行的 macOS 虚拟机数量可以喜加一。因为 Apple 在授权协议中不仅限制了 macOS 只能跑在 Mac 上,还限制了虚拟化副本的数量。总结来说,规则如下:

  1. 仅在 Apple 硬件上可以运行一套 macOS。
  2. 在运行有 macOS 的 Apple 硬件上,可以额外在虚拟化环境中安装两套 macOS。

所以,如果宿主机是 Linux,按此协议其实可以同时在 Mac 上运行三套虚拟化的 macOS,因为协议没有规定第一条中的「一套 macOS」必须直接安装于物理机上。虽然理论上在 Linux 上跑 VirtualBox 也符合此情形,但……还是拜拜吧👋。

综上所述,我宣布,macOS 配 VirtualBox 就是渣,Linux 万岁!

总结

其实,正如开头所说,这只是趁着上班摸鱼做的一些比较有趣的事。虽然和业务有关,但最后未必会落地。Mac 虚拟化的挑战依旧很多,除了机器少,如此繁琐的安装步骤也不适合大规模安装(网络启动组播 dd?)。况且需求不急,最后多半不了了之。

所以,这篇文章不过是博客停更五月有余的回归娱乐作。最近几个月发生的事情实在太多,姑且用这样一篇轻松且内容不太搜得到的 writeup 扫去过往,迎接新生吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注