最近在 Windows 上做了一次 WSL 容器压力测试。原计划只想验证新版本 WSL 能否稳定批量创建容器,结果累计创建了 3729 个容器,全部成功,零失败。更意外的是,测试过程中顺手定位到了 WSL 2 在日常开发中“卡顿”的几个真正原因。
这篇文章把整个实验过程、压测脚本、监控方法以及排查思路完整整理出来。无论你是在 Windows 上跑 Docker、用 WSL 做后端开发,还是被 WSL 2 的内存占用和磁盘抖动折磨过,都可以对照本文做一次系统检查。文章不堆概念,以可复现的实操为主。
1. 背景:WSL 为什么值得关注
WSL 的全称是 Windows Subsystem for Linux,也就是 Windows 下的 Linux 子系统。它允许开发者在 Windows 里直接运行 Linux 发行版、使用 Linux 命令行工具、编译 Linux 服务、运行 Docker 容器,不需要单独装虚拟机或者双系统。
从架构上看,WSL 经历了两个重要阶段。
WSL 1 早期是“内核接口翻译”方案。它通过把 Linux 系统调用翻译成 Windows 系统调用,来实现 Linux 程序的运行。这种方式启动快、文件访问走 Windows 文件系统,效率高,但很多 Linux 底层特性支持不完整,比如 Docker 就无法在 WSL 1 中稳定运行。
WSL 2 则是“轻量虚拟机”方案。它运行在一个由 Hyper-V 虚拟化平台支撑的轻量虚拟机中,内部使用完整 Linux 内核,兼容性大幅提升。Docker Desktop 可以基于 WSL 2 后端运行,很多 Linux 软件也能原样跑起来。代价是它拥有独立的虚拟磁盘文件(ext4.vhdx),存在跨文件系统访问的性能损耗,内存占用也不再完全透明。
至于标题里提到的“WSL 3.0”,目前微软官方并没有以这个命名正式发布一个稳定大版本。更准确的表述是:微软持续在 WSL 2 的基础上迭代预览版和新特性,比如改进网络模式、内存回收、系统调用兼容等。本文所说的“WSL 3.0”压测,本质上是针对新版 WSL 实验性构建的稳定性验证。为了避免版本误用,我会在实验环境部分说明如何确认当前实际 WSL 版本。
容器压测的意义也在这里。WSL 2 最常见的生产场景之一就是跑 Docker 容器。批量创建容器、运行容器、销毁容器,能够综合考察 Linux 内核稳定性、进程调度、内存管理、文件系统 IO 和系统调用兼容性。这比单纯跑 CPU 压力测试更能反映“日常开发是否会卡”。
所以,本文的标题“3729 个容器零失败”,重点不是数字本身,而是数字背后的稳定性表现,以及压测过程中暴露出的 WSL 2 性能瓶颈。
2. 环境准备与版本说明
在进行压测之前,先准备一个可复现的实验环境。下面所有命令均以 Windows 10/11 + WSL 为基准。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
2.1 安装 WSL 并确认版本
如果你的电脑没有安装 WSL,可以打开管理员 Power Shell,执行:
wsl --install默认情况下,这条命令会安装 Ubuntu 发行版,并启用必要的 Windows 功能。安装完成后重启电脑,按提示设置 Linux 用户名和密码。
确认当前 WSL 版本信息:
wsl --version wsl --statuswsl --status会输出当前默认发行版和内核版本,wsl --version会输出 WSL 自身组件版本。不同时间点安装的 WSL 版本差异较大,不要执着于“最新版本号”,重点是确认你启用了 WSL 2 架构。
如果需要切换发行版,可以列出并设置:
wsl --list --verbose wsl --set-version Ubuntu 2 wsl --set-default Ubuntu设置完成后,在 Ubuntu 内部查看系统信息:
cat /proc/version uname -a free -h2.2 启用 systemd
新版 WSL 支持 systemd,这对 Docker 等服务的运行很重要。在/etc/wsl.conf中开启:
# 文件路径:/etc/wsl.conf [boot] systemd=true [automount] enabled=true options="metadata,umask=022" [network] generateResolvConf=true修改完配置文件后,在 Windows PowerShell 中重启 WSL:
wsl --shutdown再重新进入 WSL,检查 systemd 是否启动:
systemctl list-units --type=service --state=running2.3 安装 Docker
本文压测使用的是 Docker 容器,因此需要在 WSL 内部安装 Docker Engine。如果使用 Docker Desktop,也可以依赖其 WSL 后端,但为了更贴近 Linux 原生环境,建议直接在 Ubuntu 内安装 Docker。
安装后的关键步骤是把当前用户加入 docker 组,避免每次执行 docker 命令都加 sudo:
sudo usermod -aG docker $USER退出 WSL 再重新进入,使配置生效。验证 Docker 是否正常:
docker --version docker run --rm hello-world2.4 压测工具清单
整个压测不需要复杂工具,使用 bash 脚本和系统自带命令即可:
| 工具 | 用途 |
|---|---|
| docker | 创建和销毁容器 |
| free | 查看内存使用 |
| vmstat | 查看 CPU、内存、IO 等待 |
| dmesg | 查看内核日志,定位 OOM 等问题 |
| watch | 周期刷新监控数据 |
| seq / xargs | 批量操作辅助 |
实验环境描述如下:Windows 11 系统,WSL 内运行 Ubuntu 发行版,安装 Docker Engine,容器镜像使用基础镜像 alpine,容器启动后执行sleep 60保持运行状态。至于具体宿主机 CPU 和内存配置,不同的机器会得到不同数据,因此不固定写死。压测方法比具体数值更值得保存。
3. 压测方案设计
3.1 为什么分批创建容器
3729 个容器如果一次性创建,任何物理机都扛不住。原因很简单:每个容器都会占用文件系统层、进程表、网络命名空间和内存。一次性创建数千个容器,大概率会触发系统 OOM 或 Docker 守护进程资源耗尽。
更合理的做法是分批创建、分批销毁,模拟“持续高频创建容器”的开发场景。比如每次创建 100 个容器,运行几秒后清理掉,再创建下一批。最终累计创建量达到 3729 个,关注的是整个过程中是否出现失败,以及资源曲线如何变化。
这样设计有两个优势:
- 更接近 CI/CD 场景:项目反复构建、启动依赖容器、销毁容器。
- 更容易观察 WSL 2 的稳定性:连续创建 3729 个容器,如果出现内核崩溃、docker 命令卡死、内存无法释放,就能很快定位。
3.2 核心压测脚本
下面是一段完整的 bash 压测脚本,按批次创建容器,累计目标 3729 个:
#!/bin/bash # 文件路径:~/wsl-container-stress.sh set -u TOTAL=0 FAIL=0 # 每个批次的创建数量,累计 3729 BATCH_LIST=(100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 129) echo "==== WSL container stress test start ====" date for batch in "${BATCH_LIST[@]}"; do echo "Creating batch of ${batch} containers..." for ((j = 0; j < batch; j++)); do if docker run -d --rm alpine sleep 60 >/dev/null 2>&1; then TOTAL=$((TOTAL + 1)) else FAIL=$((FAIL + 1)) echo "Create failed at progress: $TOTAL" fi done # 清理当前批次容器,避免占用过多资源 docker ps -aq | xargs -r docker rm -f >/dev/null 2>&1 || true echo "Progress: total=$TOTAL fail=$FAIL" # 输出当前资源状态 free -h | head -3 done echo "==== RESULT ====" echo "total containers tried: $TOTAL" echo "failed containers: $FAIL" date脚本原理如下:
docker run -d --rm alpine sleep 60:以后台模式启动一个 alpine 容器,运行 60 秒后随容器退出由--rm自动清理。这里不绑定端口,避免端口冲突。docker ps -aq | xargs -r docker rm -f:删除当前所有容器,释放资源。TOTAL累加成功创建的容器数,FAIL累加失败次数。- 每批次结束后打印内存状态,方便关联资源变化。
实际执行时,没必要显得太过“极限”,可以适当放慢。比如让容器 sleep 时间稍长,或者在每批之间加 3 秒停顿:
sleep 3这样做更贴近真实业务,不至于因为创建速度过快导致 Docker 守护进程排队,反而干扰对 WSL 本身稳定性的判断。
3.3 实时监控命令
压测过程中,另开一个 WSL 终端实时监控资源。推荐使用组合监控:
watch -n 3 'docker ps -q | wc -l && free -h'这个命令每 3 秒刷新一次当前容器数量和内存占用。
如果需要更细粒度的 IO 等待数据,可以使用 vmstat:
vmstat 1 60关注wa列(IO 等待)和si、so列(换入换出)。如果wa长期很高,说明磁盘 IO 是瓶颈。如果si、so持续波动,说明内存不够用,正在频繁换页。
内核日志也要盯一下:
dmesg --time-format iso | grep -E "oom|killed process|Out of memory"保险起见,压测开始前先记录一次基准状态:
free -m df -h /tmp /var/lib/docker这样压测结束后可以前后对比,看内存和磁盘是否有异常增长。
4. 压测执行与结果分析
4.1 执行过程
在 WSL 终端中运行脚本:
chmod +x ~/wsl-container-stress.sh ~/wsl-container-stress.sh脚本会在每个批次结束后输出一次进度。如果你看到类似下面的输出,说明流程正常:
Creating batch of 100 containers... Progress: total=100 fail=0 total used free shared buff/cache available Mem: 15G 2.1G 10G 120M 3.1G 13G Creating batch of 100 containers... Progress: total=200 fail=0 ...随着总创建数量增长,你可能会观察到几个典型现象:
- 前半段(前 500 个容器):Docker 创建速度稳定,内存释放及时,无延迟感。
- 中段(1000 至 2500 个容器):
buff/cache明显升高,偶尔出现短暂卡顿。 - 后段(2500 个以后):如果机器内存有限,可能出现 swap 增加,容器创建速度下降。
这里要特别注意:单个批次的容器创建成功后,脚本会立刻清理,所以同一时刻并存的容器数量并不多,不存在“真实同时运行 3729 个容器”的情况。3729 属于累计创建量,验证的是 WSL 2 在反复创建/销毁容器过程中的稳定性。
4.2 结果数据
本次实验的最终输出为:
==== RESULT ==== total containers tried: 3729 failed containers: 0所有容器创建成功,零失败。这个结果说明,如果配置合理,新版 WSL 构建环境可以支撑高频率的 Docker 容器操作,并没有出现系统调用不兼容、容器批量崩溃或 Docker 守护进程无法恢复等问题。
需要说明,不同机器硬件差异会导致性能曲线不同,但“零失败”这个稳定性结果是可复现方法验证的。关键是压测过程中不能人工干预,不能反复手工清理异常进程,让每一步都由脚本自动执行。
4.3 压测之后还需要清理
压测结束后,执行以下命令清理残留:
docker ps -aq | xargs -r docker rm -f docker system prune -fdocker system prune会清理无用的镜像、网络和构建缓存。注意,这条命令会删除未被容器使用的资源,在正式服务器上执行前要先确认,避免误删业务镜像。
5. WSL 2 卡顿的真正原因排查
在压测过程中,我收集到系统资源变化数据,同时也复现了几次明显卡顿。下面把 WSL 2 “卡顿”这个模糊感受拆成可定位的具体原因。
5.1 文件系统跨盘访问
WSL 2 的 Linux 文件系统存储在 ext4.vhdx 虚拟磁盘中,当你在 WSL 内部访问/root、/home等路径时,性能接近原生 Linux。但如果你通过/mnt/c、/mnt/d访问 Windows 盘符下的代码目录,性能下降非常明显。
原因是,跨文件系统访问需要经过 9P 协议(Plan 9 文件系统协议)在 Windows 和 Linux 内核之间传输文件元数据。每次 stat 操作、目录遍历、文件打开都有可能产生额外协议开销。如果项目依赖很多小文件,比如 node_modules,在 WSL 2 里运行 npm install 经常表现出极致的卡顿。
压测中我也观察到一个现象:当 Docker 镜像层数量增加到一定程度,容器启动时要从虚拟磁盘中读取大量镜像层文件,IO 等待明显上升。这说明 WSL 2 卡顿的第一元凶,通常不是 CPU 而是文件系统 IO。
解决方法很简单:项目代码尽量放在 WSL 内部文件系统中,而不是放在 C 盘然后用/mnt/c访问。如果需要共享代码,可以用\\wsl$\路径从 Windows 侧访问 WSL 内部文件,而不是反向操作。
5.2 内存回收与 buff/cache 异常
WSL 2 使用虚拟化方式管理内存,Windows 侧默认会动态分配内存给 WSL。但旧版本 WSL 存在一个常见问题:WSL 内 Linux 缓存(buff/cache)增长后,内存不会及时归还给 Windows。结果就是 Windows 任务管理器显示“内存占用很高”,而 WSL 内free -h显示大量内存被缓存占用。
压测中,批量创建容器会让页缓存快速膨胀。正常情况下,Linux 内存压力增大时应该自动回收缓存;但某些版本的 WSL 里,回收不及时,甚至出现内存持续增长不释放的现象。
可以通过下面的方式观察:
watch -n 1 'cat /proc/meminfo | grep -E "MemFree|MemAvailable|Buffers|Cached"'如果 Cache 持续增长且 MemAvailable 急剧下降,同时你还能观察到swap使用量上升,就说明 WSL 2 内存在回收问题。
缓解方案是配置.wslconfig,限制 WSL 使用的内存和 swap 大小。在 Windows 用户目录下创建.wslconfig:
# 文件路径:C:\Users\<你的用户名>\.wslconfig [wsl2] memory=8GB processors=4 swap=2GB swapfile=C:\\Users\\<你的用户名>\\wsl-swap.vhdx localhostForwarding=true配置完成后执行wsl --shutdown使其生效。限制内存的意义在于,让 WSL 在内存不足时主动回收,而不是无限向 Windows 申请物理内存。对于 8GB 内存的电脑,推荐设置 memory 为 4GB 左右;对于 32GB 内存的电脑,可以设置 16GB 或更高,具体需要结合实际负载调整。
5.3 杀毒软件与安全扫描
Windows Defender 默认会实时扫描文件,这对 WSL 虚拟磁盘的读写也会产生干扰。尤其是体积较大的 ext4.vhdx 文件被反复扫描时,磁盘占用率会显著上升,WSL 内的文件读写速度随之变慢。
如果你发现 WSL 内docker pull一直卡在某一步,关闭 Windows Defender 实时保护后速度骤升,就要考虑排除项配置。建议把 WSL 相关目录加入 Defender 排除名单:
C:\Users\<你的用户名>\AppData\Local\Packages\*C:\Users\<你的用户名>\.wslconfig- 以及 WSL 发行版所在的 vhdx 文件目录
不过,在生产环境或公司电脑上修改杀毒软件策略需要谨慎,应先了解安全部门的合规要求。
5.4 嵌套虚拟化与 CPU 调度
如果 Windows 本身运行在虚拟机里面,或者 CPU 不支持完整的虚拟化特性,WSL 2 的性能会进一步打折。嵌套虚拟化场景下,WSL 2 的轻量虚拟机内部再运行 Docker 容器,等于两层虚拟化叠加。容器创建速度、网络转发效率都会受影响。
压测中如果发现容器启动时间明显偏长,可以用以下命令检查 WSL 2 的虚拟化状态:
lscpu | grep -i "hyper"如果输出显示被 hypervisor 占用,说明可能运行在虚拟化环境中。没有特殊的原因,不建议在生产服务器上使用 WSL 2 作为 Docker 运行环境,服务器场景用原生 Linux、Docker Engine 或 Kubernetes 更合适。
5.5 高频率 IO 导致的隔离不足
压测中另一个常见卡顿源是 Docker 数据目录本身位于 WSL 虚拟磁盘中,大量容器层读写会产生较高的磁盘 IO。如果 Windows 系统盘是机械硬盘,这种卡顿会非常明显;如果是 SSD,则相对平滑。
在本次压测中,我们能从vmstat 1中看到wa列在容器批量创建时显著波动。这说明瓶颈集中在磁盘层,而不是 CPU 或内存。新版 WSL 虽然改进了虚拟磁盘格式和 IO 路径,但物理硬件的上限始终存在。
6. 常见问题与排查思路
压测过程中最容易遇到的是下面几类问题。整理成一个排查表,方便大家对照。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| WSL 启动后内存占用极高 | 缓存未回收或 .wslconfig 未限制 | 配置 .wslconfig 并 wsl --shutdown |
| 项目目录在 /mnt/c 下运行很慢 | 9P 协议跨盘访问开销大 | 代码移动到 WSL 内部文件系统 |
| docker build 时频繁卡住 | Windows Defender 扫描 vhdx 文件 | 添加 Defender 排除目录 |
| 容器数量多了之后创建变慢 | Docker 层缓存膨胀、内存紧张 | 执行 docker system prune,限制 WSL 内存 |
| dmesg 中出现 oom 字样 | 物理内存不足 | 减少单批容器数量,增加 swapfile |
| WSL 配置 .wslconfig 不生效 | 文件位置或文件名错误 | 确认用户目录下名为 .wslconfig,并执行 wsl --shutdown |
| systemd 相关服务启动不了 | 未启用 systemd | 在 /etc/wsl.conf 中配置 systemd=true |
| Windows 侧访问 \wsl$ 慢 | 文件数量多或网络路径初始化慢 | 优先考虑在 WSL 内操作文件 |
| docker ps 命令异常卡死 | Docker daemon 资源耗尽 | 使用 systemctl restart docker 重启 daemon |
当你遇到某个现象时,排查顺序建议是:
- 先看资源:
free -h、vmstat 1、df -h /var/lib/docker。 - 再看日志:
dmesg | tail -100定位内核级别问题。 - 再看 Docker 状态:
systemctl status docker、journalctl -u docker。 - 若进程状态异常,检查是否存在大量僵尸容器:
docker ps -aq。
7. 最佳实践与工程建议
7.1 把代码放到 WSL 内部文件系统
无论是在 WSL 中写代码、编译项目还是跑测试,尽量使用 Linux 侧路径。比如在 Ubuntu 内使用/home/username/project/,然后通过 Windows 侧的\\wsl$\Ubuntu\home\username\project\用 IDE 访问。这样既能获得较好性能,又方便 Windows 工具浏览文件。
7.2 使用 .wslconfig 限制资源
不要放任 WSL 2 无限制使用内存和 CPU。尤其在公司电脑或低配开发机上,合理限制反而能减少卡顿。配置 include 如下:
[wsl2] memory=8GB processors=4 swap=2GB设置完之后重启 WSL:
wsl --shutdown再次进入后查看资源是否生效:
free -h nproc7.3 批量容器操作采用自动清理
批量创建容器后,记得及时清理。Docker 本身有--rm参数,可以设置容器退出时自动删除:
docker run -d --rm --name temp-container alpine sleep 30但在压测等批量场景里,容器可能不会正常退出,所以脚本里需要准备清理动作:
docker ps -aq | xargs -r docker rm -f这里-r参数是 GNU xargs 的选项,表示当输入为空时不执行命令。如果环境不支持,可以改成:
docker rm -f $(docker ps -aq) 2>/dev/null || true7.4 使用 docker compose scale 模拟批量容器
如果想模拟相同镜像的多个容器,可以使用 docker-compose:
# 文件路径:docker-compose.yml services: worker: image: alpine command: sleep 60然后执行:
docker compose up -d --scale worker=100结束后清理:
docker compose down这种方式比手动循环更简洁,适合日常功能验证。压力测试极限场景还是使用 bash 脚本更可控。
7.5 关注安全边界
WSL 是开发工具,不应直接作为生产环境容器托管平台。如果业务容器需要对外提供服务,应部署到原生 Linux 服务器或 Kubernetes。原因很简单:
- WSL 依赖 Windows 内核调度,稳定性受宿主机影响;
- Windows 更新、驱动更新可能影响 WSL 运行;
- 容器网络、存储能力相比原生 Linux 有限;
- 生产环境需要严格的监控、备份、审计能力,WSL 在这方面的体系化能力不足。
压测得出的“零失败”只能证明开发机环境稳定,不能证明生产环境可靠,这一点要分清楚。
7.6 监控体系建设
不要只靠感觉判断卡顿。建议长期做好以下监控:
- 记录
free -h,跟踪内存走势。 - 记录
vmstat 1,观察 IO 等待。 - 使用
docker stats --no-stream查看容器资源占用。 - 定期执行
dmesg检查内核异常。 - 关注 WSL 更新日志,新版 WSL 对内存回收、网络性能改进明显。
7.7 版本迭代要留有余地
WSL 的迭代速度比较快,新版本可能优化旧问题,也可能引入新问题。升级 WSL 或更新预览版之前,建议先备份发行版数据:
wsl --export Ubuntu D:\backup\ubuntu-backup.tar当需要恢复时:
wsl --import Ubuntu D:\backup\ubuntu-backup.tar备份机制简单有效,实验新功能前养成备份习惯,可以避免环境损坏后重装系统的窘境。
8. 总结
这次 3729 个容器压测给我最大的启发是:WSL 2 的“卡顿”并不是某个单一特性造成的,而是文件系统协议、内存回收策略、防病毒扫描、资源限制和物理硬件共同作用的结果。压测中的零失败说明,只要方案设计合理,新版 WSL 构建环境完全能够承担高频率容器操作。
本文从环境搭建、压测方案、脚本实现、运行结果到卡顿排查和工程建议,给出了完整闭环。你可以直接复制文中的脚本,在 WSL 内跑一轮压测,亲手看一下容器批量创建时的内存和 IO 数据。
后续如果想进一步深入,可以学习 Linux 的 cgroup 资源隔离、Docker 存储驱动 overlay2 的原理,以及 WSL 内核源码中的内存回收机制。也可以把压测脚本改造成集成测试脚本,在 CI 流水线里自动验证 WSL 开发环境健康状态。
实践出真知,建议在测试环境验证后再调整自己的 WSL 配置。