在 macOS 上运行 Linux 系统有两种方式:(1) 使用商业虚拟机软件 (如 Parallels Desktop) ; (2) 使用 Docker 虚拟化技术。哪种方式在性能、内存、I/O 上更优呢?
我们知道,Docker 在 Linux 上利用了 Linux 原生支持的容器方式实现资源和环境的隔离,直接利用宿主内核,性能接近原生。然而,在 macOS 上却仍然需要虚拟化的技术。早期的 Docker 干脆直接在开源的 VirtualBox 中构建虚拟机,性能低下。后期的 Docker 基于轻量化的虚拟化框架 HyperKit 开发,该框架又是 macOS 10.10 后 Apple 官方发布的 Hypervisor.framework 二次开发,据说性能得到很大提升。
作为商业化虚拟机的佼佼者的 Parallels Desktop,提出了自己的 Parallels Hypervisor。因此,Docker 和 Parallels Desktop 在虚拟化技术上谁更胜一筹呢?或者说,开源和商业化闭源的虚拟化技术谁更强?通过一番测试,我的结论是 Parallels Desktop 完全吊打 Docker 。
估计大部分的用户和我一样,运行 linux 虚拟机不需要图形界面,仅仅需要一个 ssh 通过终端连接即可。Parallels 官方的这份指引介绍了如何打开 Parallels Desktop 的 headless mode。基本按照下图设置就行:
官方的 headless mode 就是让虚拟机以后台进程的形式运行,同时不显示 GUI 界面。注意这里并不是终止了 GUI 界面相关进程,而仅仅是不显示而已,就好比一个电脑主机没有插上显示器。以 Ubuntu 18.04 为例,打开 headless mode 通过 ssh 后登入虚拟机后,我们通过系统活动监视器会发现虚拟机内存占用比较高,约 1G 左右。而通过 htop 命令显示虚拟机内的内存占用如下图:
可以发现,与图形界面相关的 gnome-desktop 套件占用了大量内存。由于图形界面的存在,负责虚拟机与 macOS 宿主通信的 prl_disp_service
也会相应占用更多内存,而且 gnome-desktop 套件经常自动触发后台更新与维护服务导致 prl_disp_service
占用大量 CPU (这就是为什么 Linux 虚拟机经常在用户没有任何操作的时候 CPU 保持高占用,风扇狂转)。
如何关闭掉图形界面呢?我们知道,不同的 linux 发行版采用不同的桌面环境 (Destop Environment, DE), 依次去手动指定关闭比较繁琐。况且,ubuntu 17 之后采用的 gnome-desktop 桌面环境包含了大量特定组件,以往通过 DM (Display Manager) 去关闭 DE 的方式仍然会残留大量 gnome 相关的进程,浪费内存。
早期 Linux 系统的 init (PID=1 的 root 进程) 的实现 SysV init 中提出了 run-level 的概念,run-level = 2, 3, 4 代表多用户非图形化界面,run-level = 5 代表多用户图形化界面。近年来,主流 Linux 发行版的 init 基本替换为了更先进 systemd。systemd 向下对 SysV 兼容,run-level = 2, 3, 4 对应于 multi-user.target, run-level = 5 对应于 graphical.target。systemd 的最大特点是面向目标 (target), 我们定义一个要启动的目标并注明它的依赖条件,systemd 负责满足所有依赖条件并执行目标。通过查看 graphical.target 的依赖链,我们发现,系统要启动图形界面,首先启动 multi-user.target 多用户非图形化界面,然后启动 display-manager.service,再由 display-manager.service 的依赖链依次往下触发大量相关服务。因此,我们只需要在系统启动时,只启动到 multi-user.target 即可,这样的话可尽最大可能避免启动不需要的 GUI 相关服务。
操作方法:终端运行
sudo systemctl set-default multi-user.target // 默认是 graphical.target
# 然后重启
sudo reboot
此时再 ssh 登入虚拟机会发现内存占用降低到 100M 出头,同时 prl_disp_service
因为没有了图形界面内存占用也会降低到可以忽略不计。下图是 ubuntu 虚拟机内部的内存占用和 macOS 系统显示的虚拟机内存占用 (我进一步移除了占内存大概 10M 的 snapd 服务进程):
可以看到,关闭图形界面相关的进程后,Parallels Desktop 中的 ubuntu 虚拟机内存占用得到极大改善,100M 出头的内存占用几乎可以忽略不计,甚至比随便一个 mac 原生应用还轻量。更牛逼的是,前面提到,Parallels Desktop 支持让虚拟机以后台进程的形式运行,也就是这时我们可以完全退出 Parallels Desktop 主程序而保持虚拟机本地活跃,依然可以 ssh 登入虚拟机,太强了!
下面,我将做些简单的测试,从各个维度对 Parallels Desktop 和 Docker 展开对比。
硬件平台: mac mini late 2018 i-8500B 32G ram
虚拟机平台: Parallels Desktop 15.1.2 (47123), Docker 2.2.0.3(42716)
虚拟对象: Ubuntu 18.04.1
为保证公平对比,虚拟平台的设置均为 CPU 2 核,内存 1G,虚拟内存 2G:
(1) 启动速度对比
Parallels Desktop: 启动主程序,大概不到 3s。Ubuntu 冷启动大概 14s, 从休眠态启动大概 1s (秒启动)。
Docker: docker 主进程启动大概 12s,Ubuntu 镜像启动速度忽略不计。
结论: 在真实使用中,Parallels Desktop 上用户肯定会采用从休眠态恢复虚拟机,因此这里 Parallels Desktop 胜出,而且体验好太多。
(2) 内存占用
Parallels Desktop: 主程序内存占用 100M 左右 (主进程可退出),虚拟机内存占用 100M 左右(见第一小节图片),负责 macOS 与虚拟机通信的后台守护进程合计占用 30M 左右 (如下图):
Docker: 仅仅启动主进程,不启动任何容器,docker 依赖的 hyperkit 内存占用居然超过 1.3 G (见下图)!ubuntu 容器内存占用 250M+。
结论:Parallels Desktop 完胜,不解释。Docker 主程序打开什么也不干就常驻 1G + 内存,体验实在是太差了。
(3) 磁盘 IO:
利用 dd 命令测试 1G 文件下的磁盘写入速度:
dd bs=1M count=1K if=/dev/zero of=test oflag=dsync && rm test
由于 macOS 上的 dd 命令 (BSD 系) 和 ubuntu 不一致,因此利用 homebrew 安装 GNU 版本的 dd: brew install coreutils
,保持和 ubuntu 的一致性。GNU 的 dd 命令路径在: /usr/local/opt/coreutils/libexec/gnubin/dd
。
分别执行三次读写速度测试命令,对比原生 macOS, Parallels Desktop,Docker 三者如下:
可以发现, Parallels Desktop 下虚拟机内的磁盘写入速度非常接近原生 macOS, 而 Docker 就惨不忍睹了。经常在网上看到无数人吐槽 Docker 磁盘 I/O 缩水太严重,今日小测果然如此。
结论:Parallels Desktop 完胜,不解释。
(4) CPU 性能:
对 1G 文件进行 md5 运算,作为粗略性能比较:
dd if=/dev/zero bs=1M count=1K | md5sum
速度对比如下:
可以看到,Parallels Desktop 的 CPU 利用效率也是比 Docker 高出不少。另外,比较神奇的是,原生 macOS 用 md5sum 测试的性能居然不如 Parallels Desktop,是因为 macOS 做了特殊的设定还是什么原因我表示不清楚,毕竟 macOS 基于 BSD,有很多隐藏差异可能我并不知情。
结论:Parallels Desktop 完胜,不解释。
总结: macOS 上使用 Linux 环境,建议采用 Parallels Desktop 虚拟机而不是 Docker, 前者资源占用更少,性能更高。可以看出 Parallels Desktop 作为 macOS 上虚拟化技术的一哥,确实做的出色。这也难怪有用户在论坛 提问 Apple Hypervisor 和 Parallels Hypervisor 谁更强时,客服霸气回复 Parallels Hypervisor is the best one.
另外,本文没有使用 VMware 虚拟机是因为在本人的日常体验中,VMware 相比 Parallels Desktop 有着肉眼可见的差距。
2020.2.27 更新: Docker on Parallels
经评论区有位朋友提醒,可以将 Docker 建立在 Parallels 上使用,我就去调研了一番。
在 Docker 版本 1.12 之前,要想在 macOS 上运行 docker,就必须依赖 Docker Machine 这个工具。Docker Machine 允许我们将构建与管理容器的 Docker Engine 建立在虚拟机上,这样我们就可以让 Docker 在多个平台上运行。版本 1.12 前的 Docker 使用 Docker Machine 构建与 VirtualBox 虚拟机之上,因此性能造人诟病。后来的版本基于 Apple 提出的 Hypervisor.framework 开发,被称为“原生” Docker 应用。
因此,依赖 Docker Machine,我们可以直接在 Parallels Desktop 上运行 Docker。从 Parallels Desktop 官网 上看到,Docker Machine 并未完全支持 Parallels Desktop,想要尝鲜可以去 docker-machine-parallels 页面安装使用。
安装 docker-machine-parallels 比较简单:
brew install docker-machine-parallels
整个 docker-machine-parallels 工具才 12 MB。
终端运行:
docker-machine create --driver=parallels prl-dev
就可以构建一个基于 parallels hypervisor 的名为 prl-dev 的 Docker 客户端。从执行过程我们可以看到,中途下载了大概 60M 左右的 boot2docker,相关文件均在 /Users/$USER/.docker
目录下。
打开 parallels desktop 软件,我们可以看到多了一个 docker 虚拟机:
点击开关按钮启动此虚拟机 (等价于命令行执行 docker-machine start prl-dev
)。
终端执行 docker-machine env prl-dev
,输出一串自定义环境变量命令,复制这些命令终端输入,我们就可以愉快地运行 docker-machine-parallels 了。此时终端执行 docker
调用的将会指向我们构建的 prl-dev
了。
我们再来进行相关简易性能测试:
(1) 启动时间:启动 prl-dev
在我的 mac mini 大概耗时 17s 左右。
(2) 内存占用:prl-dev
本质上是一个虚拟机,在不运行任何容器的情况下,prl-dev
进程使用内存大概 80M 左右,确实比 Docker Mac 版好太多。
启动一个 ubuntu:18.04 容器,由于官方镜像组件比较少,容器的内存占用约 60M。
(3) 磁盘 IO:
利用 dd 命令测试 1G 文件下的磁盘写入速度,结果如下图:
可以看到此时磁盘 IO 性能较 Docker Mac 版有了显著提高,基本非常逼近前面的 Parallels Ubuntu 虚拟机了。由于差别比较小,为了防止是硬盘的正常 IO 波动,我通过多次测试,发现确实 prl-dev
比 Parallels Ubuntu 虚拟机在磁盘 IO 的写入性能上稳定低 100MB/s左右。
(4) CPU 性能:
对 1G 文件进行 md5 运算的结果如下:
同样是较 “原生” 的 Docker Mac 有明显提升,但仍略低于 Parallels Ubuntu 虚拟机。
至此,我们得出结论,Parallels Hypervisor 的技术确实不错,基于它构建 Docker 比官方的原生 app 有明显性能提升,且更省内存资源。另外,磁盘空间占用也更省了 (~440M vs 2G)。