恨铁不成钢的虚拟机——WSL2背后的代价
递进的需求
事情还得从之前《记一次虚拟机作为内网环境跳板机的实践》说起. 当时我们使用了VirtualBox, 算是较为优雅地实现了内网开发和互联网访问之间的问题, 但是随着项目的推进, 原来方案的一些弊端逐渐显现出来了. 而这个问题比之前的问题还要棘手. 无奈, 只能继续摸索, 好歹是给我又摸索出一条新路径.
开发与部署环境不一致
我们首先来梳理一下现在的网络拓扑结构:
数据库1的访问方式:

数据库1 数据库2的访问方式:

数据库1
像平时的HTTP请求还好说, 对其进行代理很简单, 只需要写一行配置, 然后再应用上就行
proxies = {"http": "http://192.168.56.191", "https": "http://192.168.56.191"}
requests.get("http://example.com", proxies=proxies)但是对于MySQL就很困难了. 这里有几个注意点:
- MySQL连接是基于TCP的, 所以HTTP代理无法引用于MySQL连接上. 只能使用
SOCKS5代理. - 无论是软件还是代码,
MySQL基本都无法设置代理. 但是MySQL连接能建立在SSH隧道上 - SSH连接一般在软件/代码中是可以使用“SOCKS5`代理的
所以, 如果我们写代码的时候就直连甲方数据库的话, 对于数据库1还好, 只需要给SSH配置一个代理. 但是对于数据库2, 就比较复杂了. 我们首先要给SSH配置一个指向虚拟机的代理, 然后还要在这上面再套一层SSH跳板机, 这样在代码里面基本不可行, 就算可行, 后面部署上去也要关闭(服务器上是可以直连的). 所以当时我们采用的是在本地建立一模一样的数据库, 最后部署上去的时候通过环境变量配置数据库连接.
本来这样是没问题的, 但是因为上述变化过大(本地环境与部署环境), 而且我们的权限受限(我们每次部署都要经过甲方, 让甲方给我们手动上传Docker镜像, 然后我们才能在容器云上进行部署, 所以每次部署沟通成本都巨巨巨巨巨高😭), 出现了两三次本地环境测试没问题, 部署环境上有问题的情况. 所以我们需要一个尽可能模拟部署环境的虚拟机才行.
VirtualBox操作卡顿
部署过程中, 我们要使用容器云平台进行操作, 而VirtualBox使用的是native API, 使用的是Hyper-V提供的半虚拟化接口(等会儿解释), 即使我给它开了6C8G的配置, 但是运行起来还是特别卡顿.

本来虚拟机只作为一个”内网跳板机”的话, 这种卡顿程度也不是不能接受, 因为毕竟我们只是内网认证那一两分钟会用到, 但是现在我们需要在内网使用容器云平台, 就必须对虚拟机有更多的操作了, 那这样的卡顿就是我们所无法接受的了.(本来也想过宿主机来访问内网, 但是这样的话宿主机就必须再配置一个代理才行, 而我的宿主机平时还挂着梯子, 所以想想就觉得繁琐且冗余, 遂放弃)
梳理需求
上面提到了我们现有的痛点, 那么我们的实际需求是什么呢?
- 核心需求: 能够在虚拟机中尽可能地模拟部署环境
- 体验需求: 保持虚拟机尽可能的流畅
- 功能需求: 同现在
VirtualBox能满足的所有需求
我们再来整理一下解决方案:
- 因为我们最终是通过
Docker打包到部署环境上, 所以我们的虚拟机也需要支持Docker, 而之前说的”连接内网必须安装的软件”是Windows平台, 所以虚拟机也必须是Windows平台 - 虚拟机里的
Windows需要支持Docker, 那么就必须支持嵌套虚拟化 - 虚拟机能够直连内网, 所以上面说的
SSH代理问题能够很大程度上缓解
所以现在我们要找的支持嵌套虚拟化的虚拟机管理软件了.
WSL2的代价
但是, 当我们打开VirtualBox的时候会发现, 嵌套虚拟化选项启用嵌套VT-x/AMD-V无法打开:

这个问题的根源还得追溯到Hyper-V与WSL2.
Hypervisor
NOTE经过考研的洗礼, 学到的操作系统知识开始派上用场了😎
操作系统里面会提到虚拟机管理程序(hypervisor), 且分为两种:
- Type-1(裸金属)类型的
hypervisor, 直接运行在硬件上, 不需要依赖于宿主操作系统, 可以直接访问硬件资源.Hyper-V就属于这一类型. - Type-2(托管)类型的
hypervisor, 运行在操作系统之上, 需要依赖于宿主操作系统来访问硬件资源.VirtualBox就属于这一类型.
而WSL2(全称Windows Subsystem For Linux)作为Windows官方出的Linux虚拟机, 其运行于Hyper-V之上, 当我们在Windows中启用WSL2的时候, 会一并开启Hyper-V功能. 而一旦我们开启了Hyper-V, 那么Windows操作系统本身也会变成一个在Hyper-V上运行的, 具有特权的”根分区”虚拟机. 这意味着Hyper-V会率先并独占性地控制CPU的虚拟化功能.
当 Hyper-V 启用后, 它就“抢”走了硬件虚拟化扩展的控制权. 此时, 当 VirtualBox 尝试访问这些已被占用的硬件资源时, 就会失败, 导致无法创建或启动64位虚拟机, 或者性能急剧下降. 这就是两者无法共存的直接原因.
为了解决这个问题,微软提供了名为“Windows 虚拟机监控程序平台”(Windows Hypervisor Platform, WHP)的API. 较新版本的 VirtualBox(6.0及以上)和 VMware Workstation都进行了更新,使其能够利用这个 API.
- 当这些软件检测到 Hyper-V 正在运行时,它们会切换到一个特殊的“后备”模式,不再尝试直接访问硬件,而是通过调用
WHP的API,将虚拟化任务“委托”给底层的 Hyper-V 来执行. - 这样,
VirtualBox就从一个直接的Hypervisor变成了一个Hyper-V的“客户端”,从而实现了共存. 不过,这种模式下因为多了一层转换,通常会导致一定的性能损失.

所以, 通过VirtualBox来实现我们上面的目的, 是行不通的. 当我们在美美使用WSL2带来的便利与好处的时候, 暗中已经标好了”这份便利”的价格, 那就是无法在除Hyper-V以外的虚拟机管理平台上使用嵌套虚拟化功能.
WSL2是没法妥协的, 其带来的便利要远远大于现在的麻烦. 所以我们如果想要达到之前的需求, 就必须将虚拟机迁移至Hyper-V
虚拟机大迁徙
既然都是虚拟机, 那么大家应该都会互通互认才对. 于是最开始我直接使用VirtualBox的导出功能, 将虚拟机进行打包导出. 但现实就是Hyper-V不认(🤡).
但是似乎也不是完全不认, 在上网搜索之后我发现, Hyper-V是认VHDX和VHD格式的, 我们导出的VirtualBox默认是OVA格式, 但它就是一个压缩包, 我们打开就能看到里面vmdk后缀的”虚拟机本体”(忽略这句话的不严谨之处, 理解我的意思即可😋). 之后我又查到, 有些工具可以在vmdk, vhd, vhdx之间进行互相转化, 既然vhdx是最新技术, 那么我直接转成vhdx想必也是极好的.
# 使用qemu-img将vmdk格式转为vhdx格式
qemu-img convert ubuntu18.04.4-s014.vmdk -O vhdx ubuntu18.04_20211223.vhdx但是问题出现了, 转换为vhdx之后在Hyper-V里一直开不了机. 后面又经过一番寻找, 从CSDN上找到一个帖子. 说:
必须导成
vhd格式# 使用VirtualBox自带的VBoxManage命令导出为vhd格式 VBoxManage clonehd virtualbox-medium.vdi hyper-V-medium.vhd --format VHD导入
Hyper-V的时候选择第一代
NOTE不过看CSDN那篇帖子下面说选择第二代也可以, 不过要在虚拟机设置里面关闭安全验证. 如果可以的话, 想必直接导出为
vhdx也是可以的. 具体未做尝试.
Hyper-V使用指引
以下是具体的Hyper-V使用步骤
Hyper-V虚拟机配置:启用
Hyper-V功能

打开
Hyper-V管理器
找到导出的虚拟机文件夹, 不出意外的话为
Win10_VirtualBox. 点击导入. 之后一直下一页就行
创建
外部虚拟交换机并绑定WIFI网卡, 命名为桥接. 注意, 当我们需要连接内网时, “允许管理操作系统共享此网络连接”选项不要勾选, 当我们平时需要使用WIFI正常联网的时候, 需要勾选此选项
创建
内部虚拟交换机, 最好命名为Static-NAT.Static-NAT网卡的作用主要是为虚拟机加一个固定IP, 以便后续作为宿主机的代理.
点击虚拟机的设置, 确保已添加了
桥接和Static-NAT两个网卡
启动并连接虚拟机, 密码为
changeme. 打开powershell输出ipconfig查看分配的Static-NAT的IP, 并固定此IP

至此, Hyper-V虚拟机便直接能用了.(因为我之前已经配置好了gostHTTP与SOCKS5代理, 以及WSL2的安装. 为谁辛苦为谁甜😿)
网络配置异同
看了上面的教程, 也许你会好奇第四点的备注:
注意, 当我们需要连接内网时, “允许管理操作系统共享此网络连接”选项不要勾选, 当我们平时需要使用WIFI正常联网的时候, 需要勾选此选项
为什么创建一个桥接网卡, 绑定WIFI网卡, 还会影响宿主机呢? VirtualBox都不会. 其实这就是两个类型的hypervisor的区别了.
这两种虚拟化软件的网络实现方式有着本质的不同(AI总结真好用🤪):
- VirtualBox(Type-2 Hypervisor)的网络模式:应用层挂钩
VirtualBox作为一个应用程序运行在Windows操作系统之上. 它的网络功能,比如“桥接模式”,是通过一个叫做**“网络筛选器驱动” (Filter Driver)** 的组件实现的.- 这个驱动会把自己“挂载”到指定的物理网卡(如Wi-Fi网卡)上. 它工作在Windows网络堆栈的一个较低层次,能够直接拦截和注入网络包.
- 为什么“丝滑”:当桥接时,
VirtualBox的驱动只是在旁边“监听”和“传递”进出物理网卡的数据包. 它并没有改变Windows对这个网卡的所有权和管理方式. 你的 Windows 系统仍然直接控制着Wi-Fi网卡,负责连接、认证等.VirtualBox只是作为一个“中间人”将虚拟机的网络包转发出去,并将发往虚拟机的包拿进来. 这个过程对宿主机的主要网络配置来说是非侵入性的,所以你感觉不到影响.
- Hyper-V (Type-1 Hypervisor) 的网络模式:系统层接管
Hyper-V是一个运行在硬件之上的虚拟机监控程序,Windows系统本身是运行在它之上的一个特权虚拟机.- 当创建一个**“外部虚拟交换机” (External Virtual Switch)** 并绑定到 Wi-Fi 网卡时,发生的事情要底层得多: *
Hyper-V会从Windows宿主机(根分区)手中夺走对物理 Wi-Fi 网卡的直接控制权. * 这个物理网卡现在直接被Hyper-V的虚拟交换机所拥有和管理. * 为了让Windows 系统还能上网,Hyper-V会创建一个新的虚拟网卡(你可以在“网络连接”里看到一个名为vEthernet (你的交换机名)的新适配器),然后将这个虚拟网卡连接到它刚刚创建的虚拟交换机上. - 为什么会“影响宿主机”:那个“网桥”,实际上就是物理网卡和宿主机的虚拟网卡通过 Hyper-V 虚拟交换机连接在一起的体现. 你的宿主机所有网络流量,现在都必须先经过这个虚拟交换机再出去. 这就是根本性的“重新布线”,而不是简单的“挂钩”. 特别是对于 Wi-Fi 这种需要管理连接状态(SSID、密码等)的适配器,这种模式很容易出问题,导致连接不稳定或性能下降.
我对”网桥”的理解就是一个”交换机”. “网桥”完成的功能也完全就是一个交换机完成的功能.(远远没有这么简单, 我搞了个网络拓扑图)

其中, 绑定WIFI网卡就需要一个网桥, 绑定以太网就不需要.
- 物理Wi-Fi网卡要先连接到一个无线路由器(AP)。这个连接过程需要认证,认证成功后,Wi-Fi网卡的物理MAC地址就被路由器记录下来了。
- 路由器只期望从这个已认证的MAC地址接收数据。
- 如果此时,Hyper-V虚拟交换机试图将一个来自虚拟机的、带有不同虚拟MAC地址的数据包通过物理Wi-Fi网卡发出去,路由器会认为这是一个非法的数据包并将其丢弃。
至此, 整个对Hyper-V的探索就结束了, 其中最令人困惑的还是它的网络部分, 因为其类型的不同(相较于VirtualBox), 导致Hyper-V的网络拓扑与平时我们的认知产生了较大的差异. 回顾最开始的需求:
嵌套虚拟化
Set-VMProcessor -VMName <VMName> -ExposeVirtualizationExtensions $true手动开启即可在虚拟机里面使用
WSL2流畅运行
这个的感知其实是最大的, 因为Hyper-V的高度优化, 现在在Hyper-V中虚拟机的使用十分的丝滑, 对虚拟机的操作是通过RDP进行的. 还可以方便的挂载宿主机的目录(唯一一点不太好的就是因为使用的是RDP, 即使是本地虚拟机, 从宿主机复制文件到虚拟机的速度也不太快)
满足之前实现的所有功能
这一项也完全没问题, 只要把
Hyper-V的网络搞明白之后, 后面的就和VirtualBox类似了
虽然嵌套虚拟化不是特别常用的需求, 但是就虚拟机的流畅度来看(目前只试了Windows), 以后将虚拟机都迁移到Hyper-V也未尝不可. 再加上Hyper-V支持管理远程虚拟机, 这样的话可用性就更强辣(大win特win!🤩)