桌面虚拟化踩坑与调优

很久很久都没有写技术博客了,于是觉得该把这篇构思已久(而这两天又多踩了几个坑)的文章写一下。
没写技术博客的这段时间里其实我的长进还挺大的,CSAPP 基本读完了,SICP 读了一部分,还有很多学校选的教材虽然并不要求读,但是我还是读了一部分,另外还看了关于 Linux Kernel 相关的书,也在自己组的学校 LUG 里从各位 dalao 那里学到了许多,这些基础知识对我设计和改造这个桌面虚拟化方案还是起了不小帮助的。
言归正传,具体谈谈自己的桌面虚拟化之旅。

起因

我开始研究桌面虚拟化,主要目的是想要用较低成本解决 home server + gaming machine。我也忘了一开始是什么原因我就想到了 GPU Pass-trough 这条路,于是我在 2017/10/10 花了 $125 买了一台 HP Z600 工作站,前前后后花了不少其他钱,把这台工作站升级到了如下配置:
– CPU 2 * Intel X5660
– RAM 32GB DDR3 RECC (超级便宜)
– MX100 256GB (我从国内用的笔记本上拆下来的)
– WD Gold 8TB (一开始是 WD Red 3TB + Seagate BarraCuda 2TB)
– Nvidia GTX 970 4GB
– Nvidia Quadro 600 (机子自带的卡,CPU没有核显)
这个方案的主要好处是,用了一堆垃圾配件所以便宜,而有 ECC 意味着更高的 uptime,能带来比较好的用户体验和作服务器使用,而桌面虚拟化配上多输入的显示器能轻松实现 massive multitasking (两张显卡分别直接向显示器输出)。

教程

写这篇的博客的主要原因是网上几乎没有比较好的 PCIe 直通桌面虚拟化的配置教程,我找到唯一一篇靠谱的是 Arch Wiki 的,链接在此 PCI passthrough via OVMF。这篇详细讲述了关于基于 UEFI (TianaCore / OVMF)的显卡直通虚拟机的配置,和一部分常见的 坑、排障指南、性能调优指南。文中部分内容并不是很详细,也没有提到多 NUMA 系统下的一些做法,还有一些虚拟化本身的坑,此文便记录一下我自己踩坑与优化体验的经历。

坑 & 优化(大致按时间顺序)

读 Arch Wiki 的 guide 略有些心动,便开始找便宜的老工作站,最后觉得价格合适也不老的过分就买了 HP Z600。一路到现在边用边趁有心情时研究优化用户体验,用到现在可以说体验已经被优化的很好了,可以 handle 生产力、游戏、多媒体 的任务,体现了一部分 Windows 平台对于 Linux 主力的开发者存在的很大意义。以下便是大致按时间顺序我 踩过的坑、用的优化方案(并不一定是修坑)、留下的一些 tips。

买机子

虽然 Z600 现在已经比较老了(我大半年前买的时候觉得还好),我也不推荐买这个机子,但是这个机子买的时候还是有坑的。我做足了“homework”便没有掉进坑里。这个机子的主板有两个版本,似乎用了不一样的 BIOS,老的一代不支持插槽一样的\s56\d\d处理器(例如 X5650),但是疑似升级 BIOS 不能解决问题,所以要买新一代的主板。我在论坛上翻到了这件事所以就在 eBay 上通过询问卖家主板上的丝印和查看主板的高清照片买到了配了新主板的机子。

存储技术选型 & lvmcache

因为有一块闲置而不大 (256 GB)的老SSD,便决定拿它作缓存盘,而真正存储使用机械硬盘(组这台机子的目的之一是 挂PT 和用来看 BDMV,所以需要较大的磁盘空间)。一开始我被人猛烈安利 ZFS,但是当做了一些调查发现 ZFS 的文件系统完全依赖 zpool 的冗余而自身对错误几乎没有 tolerance 的时候,果断放弃了 ZFS 方案,选择老老实实 ext4 + cache。而面对 cache 的选型,也有几个 option, 除了已经排除掉的 ZFS 相关的 caching solution, 还有 bcache、lvmcache 和其他一两个方案。因为当时很粉 lvm,觉得 lvm 异常清真,就选择了 lvmcache (然后 bcache 的内核代码就炸了)。lvmcache 本身很好用,但是发行版支持是个严重问题,debian 和 ubuntu 的自带内核都不支持 lvmcache,以至于安装盘都无法挂载使用了 cache 的分区,和按正常流程给系统磁盘开启 lvmcahce 之后无法启动。然后我似乎并没有仔细研究如何解决这个问题,而是去尝试了 Arch,惊喜的发现 Arch 原生内核是支持 lvmcahce 的,于是就决定 Host 系统用 Arch,到现在还在用。AUR 是比较 painful 的部分,但是整体还是非常好用的。于是我给 system 分区 和 data 分区都开了cache (现在看来真的很智障),而打算把 Windows 放在 data 分区的一个 qcow2 文件里。

USB Pass-through (part 1)

USB pass-through 可能在 VMM + libvirt + kvm 的组合下并不好用,当 USB-A 插头本身的不稳定性(接触不良)和软件用户体验设计不好结合到一起之后,总体真的体验极差。具体就是,配置好一个设备的 pass-through 之后,一旦设备断开,重新插回去(即使是同一个端口)不会被重新接上 guest,必须重新在 VMM 里删掉失效的 pass-through 并重新添加才可以用。我想到的一个可能的解决方案是穿一个 hub 进 guest,但是 hub 在 Windows 里直接提示 “不可识别的USB设备”,甚至没有给系统提供描述符,而通过这个 hub 接的设备就更不能使用了,host 和 guest 都不能读到 hub 上插的设备。于是为了解决键鼠问题,我找到了比 USB pass-through (即使是好用的)都更好的方案:Synergy。

Synergy

我最早知道 synergy 是通过 Linux Tech Tips,以为是个收费软件。结果人肉爬了他们官网发现有个 github,然后才意识到这是个 GPL 的开源软件,接着就找到了一个 CPP “scirpt” 注册机,似乎就是把一些注册信息 base64 一下 (毕竟激活验证的部分也是开源的),就很不要脸的没有付钱(却用了他们的binary)。SSL Encryption 作为 Pro 版唯一的特别 feature,我觉得体验很差,所以就算购买也推荐不买 Pro。Arch 下我直接装的 Synergy 包,然后使用了现成的 Systemd Service,Windows 下我找了一个 binary 然后配置了开机启动。就很科学的绕过了 USB Pass-through 的键鼠来回切换问题,玩 Arma 3 之类的游戏也完全没有问题。我把 Windows 配置在了 Arch 屏幕的左边,唯一的小坑是盲移鼠标容易触发 Gnome 左上角的全屏菜单,会让鼠标无法触到屏幕边缘,自然也无法触发 Synergy 的屏幕切换,此时戳 Esc 就好了。

显示器音频 & 中断类型

我最早选择的音频输出方案是从显卡的 DP 接到 Dell P2415Q 然后从显示器获取音频,这样获取到的音频有很大的 jitter,是由于不当设置的中断类型导致的,Arch Wiki 里直接有说解决方案,把 Line-based Interrupt 改成 Message-based Interrupt 即可。过程略微复杂,但是 Wiki 说的很清楚:Slowed down audio pumped through HDMI on the video card,便不再赘述。

USB Pass-through (part 2)

因为是用来打游戏的机子,自然需要手柄,而 USB pass-through 又是那样的 painful,自然想到了插上就不用再拔下的方案:蓝牙。于是买了一个蓝牙适配器插上了。但是一样由于 USB Pass-through 的不稳定,藍牙日常爆炸,以至于 Xbox One S 的手柄并不是很好用,而把 Google Home Mini 当蓝牙音箱的声音输出也不怎么流畅,常有 Jitter。另外一个发现是我在尝试优化音频方案的时候,从同学那拿了一个 USB 声卡,Windows 能正确识别,但是一旦发出一点声音声卡立刻掉线。我以为是声卡的兼容性问题,就去买了一个 SoundBlaster 的 USB 声卡,发现问题完全一样,便放弃了这个想法。

数据安全性 & 磁盘性能

因为嫌弃红盘的性能和希捷绿盘 7×24 使用的故障率,遍买了一块 recertified 的 8T 金盘,拿到收验证过的确是正品 recertified,上电时间什么的也都是0。我先把 cachding 关掉,然后把 data 分区和里面的文件系统压缩到了实际大小多一些的大小,再将所有分区都移到了红盘上,接着删除绿盘上的 VG 和 PV 都删除,sync 之后就可以安全热拔绿盘了。接着我热插金盘,合并进 VG 里,再把所有分区移入金盘中,用 data 分区填充了剩余的空间,然后清干净了红盘上的 VG 和 PV。这次我顺带把 Windows 从一个 qcow2 文件中解放到了一个 lvm 分区,并且给这个分区和 Arch 的 / 都加上了 cahce (而 data 不再有cache),Windows 的磁盘性能也因此高了不少,既没有了 qcow2 的overhead,而且 ssd caching 也更 effective 了。另外推荐一个检查 lvmcahce 情况的小工具,能很方便的看到当前的 read & write hit ratio,了解 caching 的运行效果:lvmcache-stats

Meltdown & Spectre & 输入法卡顿

本来可能比较完美的一个 setup,用了没几个月出来了 Meltdown 和 Spectre 也是很不走运,因为这个 CPU 不支持 invpcid (虽然有 pcid),所以性能下降有点厉害?尤其是对系统调用?InSpectre 的检测是说性能是 Slower 的。于是支持 uwp 的输入法都会变得比较卡顿,而不支持 uwp 的,例如 Rime 的桌面模式,体验依然还行。关掉 Meltdown 的 mitigation 之后体验大幅提升,但是安全却成了问题。(当然宿主的 mitigation 肯定是开着的)

USB Pass-through (part 3)

为了解决没法科学的给 guest 接 USB hub (却可以使用 PCIe 设备)的情况,于是我决定买张 PCIe 的 USB 拓展卡试试。因为很便宜直接 eBay 买了一张。还没到货的时候正好周末无聊被大姐姐带去逛 microcenter,然后偶然发现一张 PCI (对,没有e)的 USB 2.0 拓展卡,觉得很有趣就买了回来。插上电脑发现真的也可以用 vfio 屏蔽掉启动加载,并且传进虚拟机里,这次 IOMMU group 不是很走运,和系统的火线绑在一起,但是反正我不用火线,就一起送进了虚拟机。我把以前的 USB 3.0 hub 接在这个板子的接口上,完美使用,从此 Windows guest 就有了独立的 USB 接线板,用 yubikey 什么的都方便了很多。

CPU Pinning & NUMA awareness

在做性能调优的过程中,我发现我之前做了傻事,我给了 6+2 个泡在8个不同核心上的逻辑处理器给了虚拟机,但是虚拟机并没有做到 NUMA awareness,于是大概就有各种数据在 QPI 上飞来飞去,自然性能就比较差。后来把内存从 16GB 砍到 12GB,同时 bind 第二个处理器的 6 个核心的第二线程,性能就好了不少。

vfio 爆炸 & USB Pass-through (part 4)

前几天滚系统,滚完系统重启完虚拟机就开不了机了,Tianacore 的画面都显示不出来,看 status 显示 crashed,就开始研究原因。dmesg 里说 vfio-pci: probe of 0000:00:08.0 failed with error -22,只针对 PCI USB 拓展卡,但是并没有针对显卡。我检查了一便 vfio 的设置感觉并没有问题,就把锅推给了拓展卡,于是把拓展卡拔掉,用我的最后一个 PCIe 槽插了那张 USB 3.0 的拓展卡,重新配置 vfio-pci 针对的 硬件ID,重新 mkinitcpio 和配置虚拟机之后就好了。

QPI 音频卡顿 & 蓝牙中断

更换了 PCIe 的 USB 3.0 拓展卡带来了新的问题,我的音频开始每过几十秒停个两三秒,进度条会停,桌面保持响应,卡顿时会出现 Home mini 断线 & 重连提示音(说明蓝牙断了),于是我认为是USB的问题。我意识到 PCIe 卡的位置问题,可能是因为我的 PCIe 接在了第一个槽,对应第一个 CPU,而我虚拟机在第二个 CPU 上。我把虚拟机内存降到了 10GB 然后 pin 了第一个 CPU 的六个第二线程,就解决了问题。

Synergy Jitter

一个“历史遗留”问题是 Synergy 在进行高速下载的时候(例如 6MB/s steam 下载),会有很大的延迟和卡顿,输入体验就很差。我在解决上一个问题的时候偶然看到有人说让 Synergy 单独用一个虚拟网络适配器就可以了。我于是直接开了个 Virtio 的网络适配器,装好 Fedora 给的 Windows 驱动,直接是 10G, iperf3 测速(到宿主)也有 5G 左右,我还是比较满意的。而我发现当我用这个 NIC 来上网的时候,即使在 samba stream 蓝光 Synergy 都不卡,更别提 steam 下载游戏的那点影响。然后我才发现 libvirt 默认的虚拟 NIC 是 只有百兆的 rtl8139 (扶额。

大页内存

最后一个优化是 Arch Wiki 上有写的,关于使用大页内存减少 Page Fault。(讲真我上学期看书和自己手写了一个 Page Table 的实现之后才完全理解这块)。我的 CPU 支持 pdpe1gb 所以我就按 Static huge pages 部分所写直接预留了 10GB 的 hugepages 给虚拟机,有一些能感知的性能提升,而且我使用 Windows guest(开机会清零所有内存)和 IOMMU 本来就没法动态给虚拟机提供内存,只要虚拟机一开机就会 allocate 所有的内存,而现在则变成了 host 开机 hold 所有虚拟机的内存,我觉得从我的应用看区别几乎不存在。

Samba

Samba 非常好配,踩了两个小坑,一个是分不清 samba.service 和 smbd.service。samba 是在有域控的时候用的,单纯文件共享用 smbd 才对,用 samba 启动会报错。Samba 简单文件共享配置可以参考这个:SambaServerSimple。因为很多时候是高带宽低延迟应用(过一个虚拟网桥),需要在[global]段里加一行配置来保证性能:socket options = IPTOS_THROUGHPUT TCP_NODELAY SO_KEEPALIVE。另外如果想利用 Linux 下的软链接来 link 到下载和视频文件夹之类的地方,可以在[global]字段里添加如下内容:

follow symlinks = yes
wide links = yes
unix extensions = no

此时需要注意安全问题,虽然配置 unix extensions = no 之后能规避一些安全风险,但是这仍然涉及到一个 samba 的常见业务逻辑漏洞(也因此默认不再解析 symlink):Claimed Zero Day exploit in Samba 强烈建议阅读之后再做此设置。

总结

以上就基本是我配置和使用一个桌面虚拟化系统几个月之后留下的全部经验和教训。希望能对各位有所帮助。至于最早让我相信桌面虚拟化行得通的是其实是 Linux Tech Tips 的两个视频:2 Gaming Rigs, 1 Tower – Virtualized Gaming Build Log7 Gamers, 1 CPU – Ultimate Virtualized Gaming Build Log。观看这两个视频(和相关的几个其他 LTT 的视频)也许会让你对桌面虚拟化的效果有更多的了解。如果简单概括的话,我觉得计算性能过关,IO性能欠佳,用户体验略微粗糙但是完全能用,不论是做 生产力工具(比如 Linux 工作站上的 OneNote)还是 游戏设备。

One Reply

Leave a Reply

Your email address will not be published. Required fields are marked *