在新Mac M1上的Vagrant下运行虚拟机
当VirtualBox不再工作时,找到一个可以工作的虚拟机替代品
如果你看到这篇文章,你可能最近已经升级到一个闪亮的新Macbook Pro或类似产品,运行新的M1处理器。
在你按下 "立即购买 "按钮之前,可能没有人告诉你的是,你用Vagrant和Virtualbox精心打造的所有现有的x86-64机器将不再运行!为什么?
为什么它不能运行?
你的虚拟机不再运行的主要原因是,VirtualBox在你的Mac上作为管理程序运行,依靠底层处理器来执行指令,而不是通过软件提供处理器模拟。
这一点很重要,因为旧的英特尔版本的Mac能够直接在底层硬件上运行x86-64版本的你喜欢的应用程序。新的M1是一个完全不同的芯片组(ARM),由于VirtualBox不是一个CPU模拟器,它不可能为你运行那些现有的图像。
远离VirtualBox
在2021年底深入研究了这个问题后,我觉得可行的选择相对较少。
让机器重新运行的 "最快 "选择是将我的VirtualBox .vdi文件迁移到一个开放的格式,如.qcow2,并在一个真正的模拟器(如UTM)下运行它们。
如果你需要以最小的改动快速启动你的虚拟机,这可能是你最好的选择。这也可能是Windows虚拟机的唯一真正选择,因为目前还没有可以合法购买到在ARM上运行的商业版本的Windows(尚未)。
有其他有用的文章在讨论这个特殊的解决方案,但在安装UTM后,你可能要找的命令是这个。
qemu-img convert -f vdi -O qcow2 myvirtualbox.vdi myreplacementvm.qcow2
然后你可以连接新的.qcow2磁盘并启动。
你为什么不这样做呢?
它很慢--比运行Hypervisor慢得多。这是一个了不起的工程,我真的很感谢那些建造它的人,但即使在我的有32Gb内存的Macbook Pro上,模拟的Windows运行起来也很慢。
对我来说更重要的是,Vagrant不支持UTM。
所以又回到了绘图板...
对Windows应用程序的一个附带说明
我的Windows虚拟机只是为了运行Mac上没有的Windows应用程序,当Wine在Mac OS上放弃32位模拟后停止运行时,我转而运行一个完整的虚拟机。
从那时起,时间已经过去了,Codeweavers的奇迹工作者有一个叫做Crossover的产品,在M1 Mac上运行得很好。
显然,它仍然依赖于苹果自己的Rosetta 2,但我的Windows应用程序在我的本地Mac环境中运行得很快,而且完美无缺。如果你只是需要运行一个Windows应用程序,这是你花的最好的59美元。但我想说的是...
获得对Vagrant的支持
我遇到的最大障碍是让Vagrant支持我所运行的虚拟机。我在CentOS虚拟机下运行了许多开发环境,Vagrant和VirtualBox的组合使得这些环境可以轻而易举地按需旋转,共享盒子,并以其他方式让我的Mac主机不受每个项目的影响。
转移到M1后,所有这些都停止了工作。
尽管还处于早期阶段,但我们看到其他虚拟化供应商,如VMWare和Parallels正在开发可在M1上运行的x86模拟。在撰写本文时,它们仍处于早期开发阶段,要么缺乏你所期望的VirtualBox的支持和功能,要么不被Vagrant作为供应商支持。
我对这些的主要关注,特别是在UTM下运行Windows之后,是速度。仿真在可执行文件和主机操作系统之间增加了一个复杂的层,在使用时可能会有一些速度上的牺牲。
根据你的使用情况,这可能是可以忽略不计或可以接受的,但正如你将进一步看到的,我个人在使用仿真时遇到了明显的性能问题。
介绍一下Docker
好了,我说了。
Docker是一个了不起的产品,但它与工程师之间似乎有点爱恨交加。对于这个项目来说,虽然它是一个不错的选择,特别是如果你以我下面描述的方式来配置它。
它当然不是完美的。它不像VirtualBox那样多功能(例如为你提供主机可访问的虚拟网络),它不是为在一个容器下运行整个操作系统而设计的,而且它比使用VirtualBox作为提供者更加 "麻烦"。
但我得到了它的工作。
Docker作为一个x86-64仿真器
你知道Docker可以在Mac的M1上运行一个x86-64的镜像吗?通过QEMU的魔力,你可以启动基于linux/x86-64的Docker容器,QEMU会对其进行仿真。
只要在Docker文件中这样指定就可以了。
FROM — platform=linux/x86–64 centos:latest
很好,是吗?
嗯,是的,也不是。
虽然它启动了,但随后所有的东西都被模拟了,包括你启动的子进程。
看看我们启动它时发生了什么。
警告:请求的镜像的平台(linux/amd64)与检测到的主机平台(linux/arm64/v8)不匹配,没有请求特定的平台。
而当我们登录时,看看已经启动的进程。
CentOS在M1(ARM)处理器上模拟运行x86_64进程
注意到每个子进程都在qemu-x86_64下运行?
非常聪明,但也比较耗费资源。
然而,它确实起作用了请记住,我们是在arm64处理器上运行x86进程。
在这个模型上更进一步,我安装了nginx
和php-fpm
来测试我的一个开发环境。我将在文章后面解释我是如何在一个容器上运行它们的,但这次试验的重要收获是执行速度。
对于一个通常在200毫秒内启动的应用程序,每个PHP页面请求的启动和运行平均需要2秒。
在隔离了网络和文件系统活动之后,很明显,PHP脚本的执行是主要的瓶颈。
这使我得出结论,如果可能的话,应该避免仿真。
那么有什么选择呢?
我的目标是能够在我的M1 Mac上找到一个由Vagrant管理的运行CentOS(或类似系统)的VirtualBox的可行替代品。
为了提高速度,我需要运行一个本地ARM镜像。
为了方便,该容器必须 "感觉 "像一个完整的虚拟机。
为了完整起见,我需要能够在Vagrant下管理这一切。
解决方案是在Docker下运行一个兼容操作系统的ARM Docker镜像。
拯救Fedora
我从一个Docker CentOS 8.5容器开始了这部分项目。如果不是因为我需要一个高于7.2的PHP支持版本,我可能会坚持使用它。
如果你的依赖性已经到位,你可以把以下内容换成你选择的systemd
操作系统。Fedora 35支持PHP 8.0,这对我的开发项目来说是完美的。
运行Docker单一容器
Docker被设计用来运行多个容器,每个容器都运行自己的单一的、独立的服务(例如,一个用于nginx
,一个用于php-fpm
等)。然而,正如我所说的,我想让它 "感觉 "像一个虚拟机。
我的解决方案是将Fedora 35作为一个单一的容器启动。
对大多数人来说,第一个 "麻烦 "是没有systemd可用,因为你不希望同时运行多个服务。另外,需要从主机上获得安全考虑的权限(--特权模式),并且很难或几乎不可能将客人的Docker容器转移到有效运行。
有各种方法可以解决这个问题,在以前的项目中,我曾使用'监督者'来启动多个进程。
然而,为了保持'虚拟机'的氛围,这次我用最优秀的github.com/gdraheim/do…,换掉了/usr/bin/systemctl
我将让你按照链接阅读文件,但第一段说明了一切。
This script may be used to overwrite “/usr/bin/systemctl”.It will execute the systemctl commands without SystemD!
那是什么意思?好吧,这意味着你可以使用类似的命令。
systemctl run nginx
但同样重要的是,这意味着你可以把你的容器的启动过程(PID 1)换成一个systemd
。
在你的Docker文件中,你只需要从上面的repo中复制相关的文件,并将其作为启动命令,比如下面这样。
RUN yum -y install python3COPY src/docker-systemctl-replacement/files/docker/systemctl3.py /usr/bin/systemctlRUN chmod 755 /usr/bin/systemctl
假设你把你的源文件和repos放在一个叫做src
的目录中,这样做的目的是。
- 安装 python3
- 将
systemctl
的容器副本覆盖为systemctl3.py
- 使得替代的可执行文件
就这样了!现在在启动时,你有一个完整的systemd
容器在运行。
把它放在一起
下面的Docker文件将启动一个 "熟悉的 "M1兼容、ARM64版本的Fedora 35(或更高版本)。
重要说明
我假设从现在开始你已经安装了Docker和Vagrant(Homebrew在这方面很不错)。
brew install --cask dockerbrew install vagrant
在你开始之前,先创建一个名为src
的子目录,并把gdraheim/docker-systemctl-replacement
拉到它那里。
mkdir srccd srcgit clone https://github.com/gdraheim/docker-systemctl-replacement.gitcd ..
然后编辑你的Docker文件,看起来像这样。
FROM fedora:latestENV container docker
现在让我们快速构建它并检查是否有错误。
docker build --rm -t mynewvm .
假设它是干净的,你现在可以在Docker下运行这台机器,但让我们向前跳,通过Vagrant启动它。
将Vagrant添加到组合中
在这一点上,你应该有一个完整的Docker容器,但在未来,我们可以让Vagrant同时为你构建和启动容器。
在与你的Docker文件相同的目录下工作,运行。
vagrant init
现在编辑Vagrant文件,使其看起来像下面这样。
Vagrant.configure("2") do |config| config.vm.define "default",primary: true do |master| config.vm.network "private_network", ip: "192.168.21.100" config.vm.network "forwarded_port", id: "ssh", host: 2222, guest: 22 config.vm.network "forwarded_port", id: "nginx", host: 8080, guest: 80 end config.vm.provider "docker" do |d, override| d.build_dir = "." d.remains_running = true d.has_ssh = true endend
这个文件是一个最基本的例子,但可以。
- 定义一个 "默认 "容器
- 添加一个192.168.21.100的容器IP
- 将主机端口2222映射到容器端口22,用于SSH访问
- 将主机8080端口映射到容器80端口,用于HTTP访问(这包括一个nginx演示)
- 告诉Vagrant使用同一目录下的Docker文件来构建docker容器。
- 该容器应保持运行
- Vagrant应该安装一个SSH密钥
然后运行。
vagrant up
一段时间后,你的Docker容器(或虚拟虚拟机!)应成功启动。
你可以在docker下检查它。
docker ps
当回到命令提示符时,运行。
vagrant ssh
这应该会让你登录到你新启动的机器。
快速查看一下,也可以确认nginx
已经启动并运行。
通过ps -ax查看你在systemd下运行的进程。
从你的主机操作系统(你的电脑),你也应该能够从8080端口获取默认的nginx
着陆页。
http://localhost:8080
nginx通过端口转发运行在8080端口上
这就是了!
现在你可以安装和添加额外的服务,并在systemd
下正常运行它们。
我希望你觉得这篇文章有用。如果有足够的兴趣,我将把上述文件放入一个公共的git仓库,以便于参考。
祝您好运!