双系统的终极方案 - WSL2

双系统的终极方案 - WSL2

作为一个开发者主力系统使用 Linux 系统的好处是显而易见的,大部分的技术的安装、演示,都是以 Linux 系统为基础的,而且很多技术或者工具压根儿就不兼容 Windows 系统,比如 Docker。主力系统使用 Linux,开发体验将有很大提升。

主要是我这段时间主要从事运维相关的工作,对 Linux 的使用也很有心得,我感觉有这个能力了可以用好 Linux

其实从很早的时候想将主力操作系统切换为 Linux,希望能在一个跟服务器环境相同的环境中开发,这样的话,我们就可以使用 Nginx、Docker 这种非常方便的工具了,对我们的开发和日常工作都将带来极大的便利,但是经过一定的调研之后(主要调研 Ubuntu+GNOME),发现 Ubuntu 到目前为止依然有一些无法解决的问题,有这些问题的存在,让我认为现在将主力操作系统切换到 Linux 并不明智:

诸如此类的原因让我觉得完全放弃 Windows,转向 Linxu 并不明智,这个时候我想到了,作为一个成年人,我的想法应该是我全都要

于是我想到了双系统方案、Linux 虚拟机、还有 WSL。这三种方式我基本上都尝试过,然后我了解到 WSL2 经过一段时间的优化已经足够好用,简单体验之后,发现使用起来确实非常丝滑,非常方便,最终决定采用 WSL2 来实现双系统,我相信这也是目前为止(2024 年 2 月 28 日)双系统的终极方案。

先说结论:

WSL2 的文件系统跟 Windows 是互通的,而且通过配置可以让 WSL2 中的 Linux 发行版跟 Windows 共用网卡端口,给人的感觉就是像是一个电脑里真的安装了两个独立又互通的操作系统,这两个操作系统同时共享电脑的磁盘、网卡等硬件。WSL2 可以浏览 Windows 的文件,Windows 也可以浏览 WSL2 的文件,在 WSL2 中启动一个服务占用了 8080 端口,在另一台电脑访问 当前电脑的ip:8080,可以正常访问 WSL2 中的服务,非常惊艳。

安装

在线安装

官方文档:安装 WSL | Microsoft Learn。官方文档写的很好,没有必要看别人的博客

首先打开 powersehll,运行以下命令行查看当前支持哪些 Linux 发行版

wsl --list --online

然后选择一个版本进行安装

wsl --install -d <DistroName> 

比如当前(2024 年 2 月 28 日)支持 Ubuntu-22.04

wsl --list --online
以下是可安装的有效分发的列表。
使用 'wsl.exe --install <Distro>' 安装。

NAME                                   FRIENDLY NAME
Ubuntu                                 Ubuntu
Debian                                 Debian GNU/Linux
kali-linux                             Kali Linux Rolling
Ubuntu-18.04                           Ubuntu 18.04 LTS
Ubuntu-20.04                           Ubuntu 20.04 LTS
Ubuntu-22.04                           Ubuntu 22.04 LTS
OracleLinux_7_9                        Oracle Linux 7.9
OracleLinux_8_7                        Oracle Linux 8.7
OracleLinux_9_1                        Oracle Linux 9.1
openSUSE-Leap-15.5                     openSUSE Leap 15.5
SUSE-Linux-Enterprise-Server-15-SP4    SUSE Linux Enterprise Server 15 SP4
SUSE-Linux-Enterprise-15-SP5           SUSE Linux Enterprise 15 SP5
openSUSE-Tumbleweed                    openSUSE Tumbleweed

我们可以直接通过以下命令安装,注意,Windows11 中默认使用的是 WSL2,而不是 WSL1

wsl --install -d Ubuntu-22.04

实际上,Ubuntu-22.04 是通过微软的应用商店下载的

安装完成之后提示重启操作系统,重启之后会自动打开 PowerShell,并让用户设置 Ubuntu-22.04 系统的用户名密码即可登录 Ubuntu-22.04

后续,如果我们想进入 Ubuntu-22.04,可以在 Windows Terminal 中进入,安装 Ubuntu-22.04 的时候,已经自动在 Windows Terminal 中添加了相关链接。选择 Ubuntu 22.04.03 LTS 登录我们刚刚安装的 Ubuntu-22.04

然后,我们可以通过以下命令,查看所有安装的发行版,

wsl --list --verbose

简化版为 wsl -l -v

在结果中,可以看到 WSL 安装的所有的发行版的版本,其中 VERSION 列表示的是 WSL 当前的版本,Windows11 中默认使用的是 WSL2。

wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-22.04    Running         2

自己准备发行版 ISO 安装

官方教程:导入要与 WSL 一起使用的任何 Linux 发行版 | Microsoft Learn

过程很简单,到 Docker Hub 上下载 CentOS 的镜像,打成 tar 包,然后导入到 WSL 中即可。以后有时间再弄吧,TODO

日常使用

命令

WSL 管理 Linux 发行版(Distro)/ 子系统 的方式,有点类似 Docker 管理镜像的方式:WSL 就是一个类似于 docker 的管理程序,wsl 可以安装多个发行版,然后可以单独控制每一个发行版的启动和关闭。

wsl 命令的格式如下

wsl.exe [参数] [选项...][命令行]

其中我们一般省略 .exe

直接执行 wsl,会进入默认的子系统,如果当前没有子系统启动,则会先启动默认的子系统,然后进入默认的子系统。

后面我们会学习到 wsl --set-default <Distro> 用于设置默认的子系统,其中 --set-default 可简写为 -s。在当前博客中,<Distro> 就是 Ubuntu-22.04
通过 wsl -l --all -v 查看所有子系统的时候,默认子系统前面有星号

参数:运行 Linux 二进制文件(一般我们称之为命令 command)的参数,如果未指定 shell(例如 /bin/bash),wsl.exe 将启动默认的 shell。

每一个命令都有其对应的二进制文件,比如 cpmv 这类的,在 /bin 都有其对应的文件。

选项:

用于管理子系统的参数

用于在子系统中管理分发版的参数:

其实常用的也就是:

注意,关闭 Terminal 是不会关闭 Linux 系统的。需要通过 wsl --shutdown 关闭子系统和 WSL2 或者通过 wsl --terminate <Distro> 或者 wsl -t <Distro> 关闭指定的子系统。

隐藏桌面图标

参考博客:Reddit - Dive into anythingBrainByteZ » [Windows 11] Hide/Remove WSL Linux icon from Desktop

具体的原理是:Registry Tweak - Add or Remove System Icons from the Desktop

安装完 Ubuntu 22.04 之后,桌面出现了图标:

这个图标无法删除,想要隐藏的话,没有找到官方提供的方法,只找到一个国外大神想到的办法。

具体做法是,到 [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel] 这个目录下,创建一个名称为 {B2B4A4D1-2754-4140-A2EB-9A76D9D7CDC6} 类型为 DWORD (32-Bit) 的十进制的值,值为 1,然后重启,即可隐藏图标

配置用户

WSL 修改默认用户_wsl 设置默认用户-CSDN 博客

首先在 Linux 子系统中设置 root 用户密码,我这里设置的是 root/root

sudo  passwd root

然后配置以后都用 root 用户登录,修改 Linux 子系统的默认登录用户,命令为:

<DistributionName> config --default-user <Username>

当前安装的发行版是 Ubuntu-22.04,其在微软应用商店下下载,下载的软件执行器的名字为 ubuntu2204.exe,因此当前执行的就是

ubuntu2204 config --default-user root

再次启动 wsl,默认登录的就是 root 用户了。

手动的安装发行版不能这样设置,因为这些发行版没有可执行启动器。 可以改为使用 /etc/wsl.conf 文件来更改导入的发行版的默认用户。请参阅 高级设置配置 文档中的“自动装载”选项。

但是现在都 .wslconfig 了啊,不用 wsl.conf 了啊

可以用 wsl --user <Username> 或者 wsl --distribution <Distribution Name> --user <User Name> 来启动。

配置子系统

配置文件:WSL 中的高级设置配置 | Microsoft Learn

我们在 VMware 虚拟机中创建虚拟机的时候,可以配置虚拟机使用的 CPU 核心数,占用的内存,WSL 中也可以配置这些信息。

我们当前使用的是 WSL2,因此只需要关注 .wslconfig

使用 .wslconfig 为 WSL 上运行的所有已安装的发行版配置全局设置

配置项:

key value default 说明
内核 (kernel) path Microsoft 内置内核提供的收件箱 自定义 Linux 内核的绝对 Windows 路径。
内存 大小 Windows 上总内存的 50% 或 8GB,以较小者为准;在 20175 之前的版本上:Windows 上总内存的 80% 要分配给 WSL 2 VM 的内存量。
处理器 number Windows 上相同数量的逻辑处理器 要分配给 WSL 2 VM 的逻辑处理器数量。
localhostForwarding boolean true 一个布尔值,用于指定绑定到 WSL 2 VM 中的通配符或 localhost 的端口是否应可通过 localhost:port 从主机连接。
kernelCommandLine string 空白 其他内核命令行参数。
safeMode boolean false 在“安全模式”中运行 WSL,这会禁用许多功能,应用于恢复处于错误状态的发行版。仅适用于 Windows 11 和 WSL 版本 0.66.2+。
swap 大小 Windows 上 25% 的内存大小四舍五入到最接近的 GB 要向 WSL 2 VM 添加的交换空间量,0 表示无交换文件。交换存储是当内存需求超过硬件设备上的限制时使用的基于磁盘的 RAM。
swapFile path %USERPROFILE%\AppData\Local\Temp\swap.vhdx 交换虚拟硬盘的绝对 Windows 路径。
pageReporting boolean true 默认的 true 设置使 Windows 能够回收分配给 WSL 2 虚拟机的未使用内存。
guiApplications 布尔 * true 一个布尔值,用于在 WSL 中打开或关闭对 GUI 应用程序 (WSLg) 的支持。仅适用于 Windows 11。
debugConsole 布尔 * false 一个布尔值,用于在 WSL 2 发行版实例启动时打开显示 dmesg 内容的输出控制台窗口。仅适用于 Windows 11。
nestedVirtualization 布尔 * true 用于打开或关闭嵌套虚拟化的布尔值,使其他嵌套 VM 能够在 WSL 2 中运行。仅适用于 Windows 11。
vmIdleTimeout 数量 * 60000 VM 在关闭之前处于空闲状态的毫秒数。仅适用于 Windows 11。
dnsProxy bool true 仅适用于 networkingMode = NAT。布尔值,通知 WSL 将 Linux 中的 DNS 服务器配置为主机上的 NAT。设置为 false 会将 DNS 服务器从 Windows 镜像到 Linux。
networkingMode** string NAT 如果值为 mirrored,则会启用镜像网络模式。默认或无法识别的字符串会生成 NAT 网络。
firewall** bool 如果设置为 true,则 Windows 防火墙规则以及特定于 Hyper-V 流量的规则可以筛选 WSL 网络流量。
dnsTunneling** bool false 更改将 DNS 请求从 WSL 代理到 Windows 的方式
autoProxy* bool false 强制 WSL 使用 Windows 的 HTTP 代理信息

其中

具有 path 值的条目必须是带有转义反斜杠的 Windows 路径,例如:C:\\Temp\\myCustomKernel

具有 size 值的条目后面必须跟上大小的单位,例如 8GB 或 512MB

值类型后带有 * 的条目仅在 Windows 11 中可用。

值类型后带有 ** 的条目需要 Windows 11 版本 22H2 或更高版本。

这些设置是试验性功能的选择加入预览,我们的目标是将来将其设为默认设置。

.wslconfig 节标签:[experimental]

设置名称 默认值 说明
autoMemoryReclaim string disabled 检测空闲 CPU 使用率后,自动释放缓存的内存。设置为 gradual 以慢速释放,设置为 dropcache 以立即释放缓存的内存。
sparseVhd bool false 如果设置为 true,则任何新创建的 VHD 将自动设置为稀疏。
useWindowsDnsCache** bool false 仅当 wsl2.dnsTunneling 设置为 true 时才适用。如果此选项设置为 false,则从 Linux 隧道传输的 DNS 请求将绕过 Windows 中的缓存名称,以始终将请求放在网络上。
bestEffortDnsParsing** bool false 仅当 wsl2.dnsTunneling 设置为 true 时才适用。如果设置为 true,Windows 将从 DNS 请求中提取问题并尝试解决该问题,从而忽略未知记录。
initialAutoProxyTimeout* string 1000 仅当 wsl2.autoProxy 设置为 true 时才适用。配置启动 WSL 容器时,WSL 等待检索 HTTP 代理信息的时长(以毫秒为单位)。如果代理设置在此时间之后解析,则必须重启 WSL 实例才能使用检索到的代理设置。
ignoredPorts** string Null 仅当 wsl2.networkingMode 设置为 mirrored 时才适用。指定 Linux 应用程序可以绑定到哪些端口(即使该端口已在 Windows 中使用)。通过此设置,应用程序能够仅侦听 Linux 中的流量端口,因此即使该端口在 Windows 上用于其他用途,这些应用程序也不会被阻止。例如,WSL 将允许绑定到 Linux for Docker Desktop 中的端口 53,因为它只侦听来自 Linux 容器中的请求。应在逗号分隔列表中设置格式,例如:3000,9000,9090
hostAddressLoopback** bool false 仅当 wsl2.networkingMode 设置为 mirrored 时才适用。如果设置为 True,将会允许容器通过分配给主机的 IP 地址连接到主机,或允许主机通过此方式连接到容器。始终可以使用 127.0.0.1 环回地址,此选项也允许使用所有额外分配的本地 IP 地址。仅支持分配给主机的 IPv4 地址。

值类型后带有 * 的条目仅在 Windows 11 中可用。

值类型后显示 ** 的条目需要 Windows 版本 22H2 或更高版本。

我们可以自定义

[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
firewall=false

[experimental]
autoMemoryReclaim=gradual  # gradual  | dropcache | disabled

移动子系统

我发现,子系统实际上安装到了 C:\Users\<username>\AppData\Local\Packages\Canonical...\LocalState\ext4.vhdx,也就是说,保存在 C 盘,

参考博客:windows subsystem for linux - Where is WSL located on my computer? - Ask Ubuntu

到目前为止,WSL 没有提供自定义安装路径的配置。比较好的办法是装好子系统之后再移动子系统,也就是先通过在线安装安装好子系统,之后再导出成一个文件放到你想要的位置,然后再导入到 WSL 中。

另一个办法是手动去下发行版文件(tar 文件),放到目标目录,然后解压再安装,不过这样就缺失了很多功能。

操作步骤是:

首先,关闭所有子系统

wsl --shutdown

查看所有分发版本

wsl -l --all -v

导出分发版为 tar 文件到 G 盘

wsl --export Ubuntu-22.04 G:\WSL\ubuntu22.04.tar

注销当前分发版

wsl --unregister Ubuntu-22.04

重新导入并安装分发版在 G:\WSL\ubuntu22.04

wsl --import Ubuntu-22.04 G:\WSL\ubuntu22.04 G:\WSL\ubuntu22.04.tar --version 2

此外,我还发现了一个工具 LxRunOffline,但是对 WSL2 的支持好像很差,有 bug,例如:Issue with WSL2 Ubuntu 22 trying to use LxRunOffline - Microsoft Q&A,反正官方的指令又不长,就用官方指令算了。

自定义 wsl 安装位置以及多 wsl 共存 | 骏马金龙

子系统桌面

子系统可配置其使用桌面,比如可以装 GNOME 或者 KDE,我找过了,有教程,但是没有必要,就用 Windows 桌面来访问子系统算了,反正文件系统是互通的。

设置 wsl 开机自启

方法如下:

  1. WIN+R 运行 shell:startup 打开启动目录,其实就是 C:\Users\<user_name>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

  2. 在此目录中创建文件 wsl-start.vbs

  3. wsl-start.vbs 中填充如下内容,Ubuntu-22.0 需替换为你使用的发行版名称。

    Set ws = CreateObject("Wscript.Shell")
    ws.run "wsl -d Ubuntu-22.04", vbhide
    
  4. 想要停止 WSL 开机自启,直接删除掉 wsl-start.vbs 即可。
    wsl 开机自启之后,我们可以在 wsl 中设置一些开机自启的服务,这样就可以做到 Windows 启动的时候 wsl 中的一些服务也自启。
    为了不让 wsl 的开机自启影响到 Windows 的性能,推荐设置一下 wsl 的内存占用,CPU 占用的限制就不必了,一般用不到

开发相关

Python

Ubuntu22.04 自带 python3.10。

WSL 的 DNS

WSL 的 DNS 是自己写死的,不会使用宿主机的 DNS。在文件 /etc/resolv.conf 中可查看,也可以配置宿主机的配置文件 wsl.confgenerateResolvConf = false 不让此文件自动生成。

网络设置 mirrored - 重点

如果你进入子系统的时候看到这个错误,说明子系统的网络没有配置好。

检测到 localhost 代理配置,但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理。

别人也遇到了同样的问题:Github Issue

这个问题和下面这个问题其实是一个问题:如何实现 WSL 使用宿主机的代理,比如 Clash?

参考博客:在 WSL2 中使用 Clash for Windows 代理连接 - East Monster 个人博客

在 WSL 2.0.5 版本 后,一些特性得以稳定,比如 镜像模式网络。启用镜像模式网络会将 WSL 更改为全新的网络体系结构,其目标是将 Windows 上的网络接口“镜像”到 Linux 中,以添加新的网络功能并提高兼容性。

以下是启用此模式的当前优势:

如何启用镜像模式网络?我们在 配置子系统 小节了解过如何配置 WSL,具体做法是在 C:\Users\<UserName>\.wslconfig 文件中 (如果不存在就手动创建一个) 加入以下内容:

[wsl2]
networkingMode=mirrored

经过这样设置之后,WSL 管理的子系统的 IP 跟主机一样了,也就是说,两个机器共用一套 IP 和端口,在子系统中部署的服务,在 Windows 中直接可以通过 localhost 来访问,比如我在 wsl 中部署了一个 nginx 服务,代理了 80 端口,那么在 Windows 下的浏览器中,直接输入 localhost:8000 就能访问,再加上 WSL 管理的子系统和 Windows 的文件本来就是互通的,修改和调试 Linux 下的应用就更加简单了。

docker run -d --name nginx  -p 8000:80  -v /opt/nginx/config/nginx.conf:/etc/nginx/nginx.conf  -v /opt/nginx/logs:/var/log/nginx -v /opt/nginx/www:/usr/share/nginx/html  nginx

我宣布,Windows11 是最好的 Linux 管理软件

注意,这个时候,子系统中的服务,Windows 中想要访问只能通过 localhost 来访问,因此此时只是开启了 Hyper-V 的回环地址访问(WSL 基于 Hyper-V,localhost 基于回环地址),在其他机器上通过 Windows 子系统的具体 ip 访问子系统中的服务是访问不了的,必须在 wsl 的虚拟机的防火墙上开端口。也就是配置 WSL 虚拟机的入站规则。

配置 WSL 虚拟机的入站规则

官方文档:
使用 WSL 访问网络应用程序 | Microsoft Learn
Hyper-V 防火墙 - Windows Security | Microsoft Learn

首先,我们要知道 WSL 基于 Hyper-V,而 Hyper-V 的防火墙规则按 VMCreatorId 启用。若要获取 WSL 虚拟机的 VMCreatorId,则可以这样查询:

Get-NetFirewallHyperVVMCreator

VMCreatorId  : {40E0AC32-46A5-438A-A0B2-2B479E8F2E90}
FriendlyName : WSL

此时我们查看 WSL 虚拟机的配置,我们会发现入站是被阻断(Block)的,也就是说,通过 IP 从 WSL 虚拟机的外部,是无法 ping 通 WSL 虚拟机中的任何端口的

Get-NetFirewallHyperVVMSetting -PolicyStore ActiveStore -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}'

Name                  : {40E0AC32-46A5-438A-A0B2-2B479E8F2E90}
Enabled               : True
DefaultInboundAction  : Block
DefaultOutboundAction : Allow
LoopbackEnabled       : True
AllowHostPolicyMerge  : True

我们可以开启这个入站规则,开启之后,虚拟机外部访问虚拟机内部的所有端口,都会放行。此命令需要以管理员权限运行终端

Set-NetFirewallHyperVVMSetting -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -DefaultInboundAction Allow

再次查看

Get-NetFirewallHyperVVMSetting -PolicyStore ActiveStore -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}'

Name                  : {40E0AC32-46A5-438A-A0B2-2B479E8F2E90}
Enabled               : True
DefaultInboundAction  : Allow
DefaultOutboundAction : Allow
LoopbackEnabled       : True
AllowHostPolicyMerge  : True

那我们如何关闭 WSL 虚拟机入站的命令呢?

Set-NetFirewallHyperVVMSetting -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -DefaultInboundAction Block

此外,你也可以不开启所有端口,也可以只开启特定端口,方法是创建一个入站规则

New-NetFirewallHyperVRule -Name MyWebServer -DisplayName “My Web Server” -Direction Inbound -VMCreatorId “{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}” -Protocol TCP -LocalPorts 80

然后通过以下命令查看手动添加的入站规则,

应该会有两个默认的 PolicyStoreSourceType : Local 的规则。

Get-NetFirewallHyperVRule -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}'

删除此特定的入站规则

Remove-NetFirewallHyperVRule -Name 'MyWebServer'

相关 API 页面:New-NetFirewallHyperVRule (NetSecurity) | Microsoft LearnRemove-NetFirewallHyperVRule (NetSecurity) | Microsoft Learn

不需要再手动配置子系统的代理

注意,当我们已经配置了虚拟机的网络模式为镜像(networkingMode=mirrored)的时候,我们实际上不需要再手动在子系统中配置 http_proxy 或者 https_proxyall_proxy 这些环境变量,系统已经为我们配置好了,通过 echo $http_proxy 等可以会直接输出我们在 Windows 上配置的系统代理,还挺方便的。

为 Linux 配置代理的方法请参考 博客

测试是否通过了代理:

因为 ping 是 ICMP 报文,代理不转发所以测试时不要使用 ping 来测试,而是用 curl 来进行测试。

curl ip.gs  # 推荐,启用后返回代理 IP,否则是本地 IP(但是返回结果只有 IP)
curl cip.cc # 好像有点问题,能 curl 通 Google 但是它还是显示本地 IP(返回结果会显示地址)
curl https://www.google.com  # 最直接的方法,能返回 HTML 就没问题,否则会报错

然后我们再到 Windows 下的 Clash 中查看日志,日志级别调到 ALL,可以看到从子系统中发出的请求的日志

A localhost proxy configuration was detected but not mirrored into WSL. WSL in NAT mode does not support localhost proxies.

明明我的 .wslconfig 中开启了镜像模式

[wsl2] 
networkingMode=mirrored 
dnsTunneling=true 
autoProxy=true 
firewall=false 

[experimental] 
autoMemoryReclaim=gradual # gradual | dropcache | disabled

为什么 wsl 仍处在 NAT 模式。

说明你的 WSL 2 虽然在配置文件中开启了 networkingMode=mirrored,但由于某种原因,WSL 仍然运行在传统的 NAT 模式下,导致它无法兼容宿主机上的 localhost 代理配置。

这个时候,我们需要检查 Windows 宿主机是否安装了第三方代理软件(如 Clash、V2Ray 等)。这些软件如果开启了“系统代理”或“TUN 模式”,有时会与 WSL 的镜像模式抢占网络接口,导致 WSL 启动时无法成功镜像网络。此时我们退出 TUN 模式 然后重启 wsl,一般都可以回复正常。

如果不行,则需要执行

netsh winsock reset
netsh int ip reset

在开启 networkingMode=mirrored(镜像模式)时,WSL 不再是简单的虚拟机,它需要深度调用 Windows 的底层网络驱动(类似于直接“劫持”宿主机的网络堆栈)。

  1. netsh winsock reset:修复了被代理软件(如 Clash、V2Ray 等)或底层防火墙修改过的网络分层服务提供程序(LSP)。镜像模式对这些链路的纯净度要求极高,如果链路中存在异常的节点,WSL 就会报 0x8007054f 错误。
  2. netsh int ip reset:重新初始化了 TCP/IP 堆栈,清除了之前因配置失败而残留的虚拟网卡占位或错误的路由表。

谨慎开启 TUN 模式:镜像模式和代理软件的 TUN 模式 偶尔仍会有冲突(因为两者都想接管虚拟网卡)

验证 WSL 镜像效果: 你现在可以再次输入 ip addr,应该能看到 WSL 的 IP 地址已经变得和 Windows 宿主机一模一样了。

现在你的 WSL 应该可以完美享受宿主机代理自动同步本地端口直接访问等镜像模式带来的便利了。

NAT 模式 更稳定实用

.wslconfig

推荐配置

[wsl2]
networkingMode=nat
dnsTunneling=true
autoProxy=false
firewall=false

[experimental]
autoMemoryReclaim=gradual

wsl 启动之后,手动配置代理

cat >> ~/.bashrc <<'EOF'

# proxy
export http_proxy=http://172.17.208.1:1080
export https_proxy=http://172.17.208.1:1080
export ALL_PROXY=socks5://172.17.208.1:1080

EOF

对于 bash 来说,登录 shell 会按顺序找:

~/.bash_profile
~/.bash_login
~/.profile

只要找到了 ~/.bash_profile,就不会再执行 ~/.profile

所以如果有 ~/.bash_profile,执行

echo '

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi
' >> ~/.bash_profile

否则执行

echo '

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi
' >> ~/.profile


这样:

都能加载。

为什么使用 NAT 模式

大部分人的开发机,上面会装很多网络软件:

WSL mirrored

实际上需要:

Hyper-V Extensible Switch

而很多工具都会占着桥接层,比如 VMware 和 Windows Bridge。WSL 根本抢不到。

尤其是 VMware Bridge Protocol

这个:

vmware_bridge

已经绑定:

甚至:

vEthernet (FSE HostVnic)

都被 VMware bridge 了。

这非常不正常。

VMware 正在桥接 Hyper-V 虚拟网卡。

这特别容易把:

WSL / Docker / Hyper-V

搞炸。

因为你机器同时有:

这类机器:

networkingMode=nat

反而是最稳定的。

很多开发者最后都会退回 NAT。

一般来说:

VMware / VPN不会轻易干扰 WSL NAT

这正是:

NAT 比 mirrored 稳定

的核心原因。

如何获取 WSL 的 IP

启动 wsl 之后执行 ifconfig

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.209.121  netmask 255.255.240.0  broadcast 172.17.223.255
        inet6 fe80::215:5dff:fec8:2afa  prefixlen 64  scopeid 0x20<link>
        ether 00:15:5d:c8:2a:fa  txqueuelen 1000  (Ethernet)
        RX packets 42  bytes 2636 (2.6 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 56  bytes 3704 (3.7 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以看到当前 IP 是 172.17.209.121

回到 Windows 主机,执行 ipconfig

Ethernet adapter vEthernet (WSL):

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::a8cd:1dab:74f1:2a3e%71
   IPv4 Address. . . . . . . . . . . : 172.17.208.1
   Subnet Mask . . . . . . . . . . . : 255.255.240.0
   Default Gateway . . . . . . . . . :

可以看到宿主机的 IP 是 172.17.208.1,我们在 wsl 中可以通过这个 ip 访问宿主机中的服务,比如 socket 代理

Windows 如何访问 NAT 中的端口

在 Windows 中直接通过 localhost 就可以访问到 wsl(172.17.209.121) 中的服务,直接通过 172.17.209.121 反而访问不到

因为 WSL NAT 模式下:

localhost 能通
≠
WSL IP 能通

这是微软专门做的:

localhost forwarding

机制。

而:

172.17.209.121

只是:

WSL 内部虚拟网卡 IP

并不是真正对 Windows 暴露的“局域网 IP”。

你现在的网络结构

其实是:

Windows
   |
Hyper-V NAT
   |
WSL eth0 (172.17.209.121)

WSL 在:

Hyper-V NAT 后面

为什么 localhost 能访问

微软后来做了一个特殊机制:

localhost forwarding

当:

WSL 内监听:

0.0.0.0:8000

或者:

127.0.0.1:8000

Windows 会自动创建:

localhost:8000

到 WSL 的转发。

所以:

Windows localhost

实际上不是直接访问:

172.17.209.121

而是:

Windows 内部做了端口代理
为什么直接访问 172.17.209.121 不通

因为:

172.17.x.x

是:

Hyper-V NAT 内部网段

Windows 主机:

通常不会直接路由过去。

甚至:

Windows 防火墙可能直接拦了。

这是 NAT 的典型行为

就像:

VirtualBox NAT
VMware NAT
Docker bridge

一样。

虚拟机 IP:

不一定能从宿主机直接访问

但:

端口映射后的 localhost 可以。

微软为什么这样设计

因为:

localhost 开发体验最好

微软的目标是:

用户在 WSL:

npm run dev

Windows:

localhost:3000

直接打开。

不用关心:

装 Docker

在 Ubuntu 中安装 Docker,参考 官方文档

最后执行 sudo docker run hello-world 的时候发现提示超时:

root@LAPTOP-LK:/mnt/c/Users/wwwli# sudo docker run hello-world                                                                                
Unable to find image 'hello-world:latest' locally                                                                                             
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Ti
meout exceeded while awaiting headers).

后面查资料之后才发现原因:

解决方案很简单,首先为 docker 命令行工具配置代理,创建 /etc/docker/daemon.json,内容如下:参考 官方文档

  {
    "proxies": {
      "http-proxy": "http://127.0.0.1:1080",
      "https-proxy": "http://127.0.0.1:1080",
      "no-proxy": "*.test.example.com,.example.org"
    }
  }

然后重启 docker 服务

systemctl restart docker

然后我们在 Clash 中为域名 registry-1.docker.io 添加映射规则

再执行 login -u <username> 输入密码,如果输出 Login Succeeded 则表示登录成功。

然后就可以正常拉取镜像了

注意:

宿主机的 Clash 添加了新的规则之后,最好重启 wsl,这样 wsl 才能刷新新的规则。

wsl 子系统网络问题定位

当我发现子系统中 docker login 访问 registry-1.docker.io 老是失败,但是在子系统中通过 curl 访问此域名可以成功的时候(而且也可以在 Windows 的 Clash 中看到相应日志),我大概就可以判断出,这个时候基本确定就是因为 docker login 没有走代理