🐍 WSL2 + Docker + Python:打造跨平台一致开发环境

一、前言

在软件开发中,“它在我的机器上能跑!” 是一个广为流传的笑话,但它揭示了一个真实而痛苦的现实:开发环境与生产环境之间的不一致,是导致线上故障最常见的根源之一。

即使在团队内部,不同成员的开发环境也可能存在细微差异——操作系统版本不同、系统库版本不同、Python 版本不同、环境变量配置不同——这些差异在日常开发中可能不会暴露问题,但在关键时刻(紧急修复、上线部署)往往引发难以排查的诡异 bug。

解决这一问题的终极方案,是 WSL2 + Docker 的组合

  • WSL2(Windows Subsystem for Linux 2) 让 Windows 用户拥有完整的 Linux 内核,消除了操作系统层面的差异
  • Docker 通过容器化技术将应用及其依赖打包为不可变的镜像,确保开发、测试、生产环境的高度一致

本文将详细介绍如何在 Windows 上搭建 WSL2 + Docker 的 Python 开发环境,并探讨如何将这套工作流融入日常开发。


二、WSL2 原理与安装

2.1 WSL1 vs. WSL2:架构差异

WSL 经历了两个版本的演进,它们的架构有本质区别:

特性 WSL1 WSL2
架构 翻译层(将 Linux 系统调用转换为 Windows 系统调用) 完整的 Linux 虚拟机(运行在 Hyper-V 轻量级 VM 中)
Linux 内核 ❌ 无实际 Linux 内核 ✅ 包含完整 Linux 内核
系统调用兼容性 ⚠️ 部分不兼容(约 70% 系统调用) ✅ 完全兼容(100%)
I/O 性能 🚀 快(跨文件系统操作) ⚡ Windows → Linux 文件系统:较快;反之较慢
内存使用 💾 低(共享 Windows 内存) 💾 较高(有独立 VM 内存分配)
Docker 支持 ❌ 不支持 Docker 原生运行 ✅ 支持 Docker 原生运行

为何选择 WSL2? 对 Python 开发者而言,WSL2 带来的完整 Linux 内核意味着:

  • Docker 可以在 WSL2 中原生运行(无需 Docker Desktop 的 Hyper-V VM)
  • Linux 特有的系统调用(如 inotifyepollfork 等)全部可用
  • 科学计算库(numpy、scipy 等)的底层优化全面生效

2.2 WSL2 安装步骤

前提条件
  • Windows 10 版本 2004+(Build 19041+)或 Windows 11
  • 启用了 BIOS 中的虚拟化技术(VT-x/AMD-V)
安装命令
# 1. 以管理员身份打开 PowerShell,启用 WSL 功能
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

# 2. 启用虚拟机平台
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

# 3. 设置 WSL2 为默认版本
wsl --set-default-version 2

# 4. 安装 Linux 发行版(推荐 Ubuntu 22.04 LTS 或 24.04 LTS)
# 方法一:从 Microsoft Store 搜索 "Ubuntu" 安装
# 方法二:使用命令行
wsl --install -d Ubuntu-24.04

# 5. 查看已安装的 WSL 发行版及其版本
wsl --list --verbose
验证安装
# 进入 WSL2 环境
wsl

# 在 WSL2 中验证
uname -a
# 输出示例:Linux DESKTOP-XXXX 5.15.146.1-microsoft-standard-WSL2 #1 SMP ...

cat /etc/os-release
# 输出示例:Ubuntu 24.04 LTS

2.3 WSL2 优化配置

WSL2 的默认配置在内存和资源使用上可能不是最优的。在 Windows 用户目录下创建 .wslconfig 文件进行调优:

# %USERPROFILE%\.wslconfig

[wsl2]
# 限制最大内存使用(防止 WSL2 占用过多内存)
memory=8GB

# 限制最大 CPU 核心数
processors=4

# 交换空间大小
swap=2GB

# 开启 localhost 转发(WSL2 中的服务可从 Windows 访问)
localhostForwarding=true

# 启用 WSL2 的 systemd 支持(Ubuntu 22.04+ 需要)
systemd=true

⚠️ 注意: 修改 .wslconfig 后需要重启 WSL2 才能生效:

wsl --shutdown
wsl

三、在 WSL2 中配置 Python 开发环境

3.1 基础环境安装

进入 WSL2 后,配置与在原生 Linux 上完全一致:

# 更新包管理器
sudo apt update && sudo apt upgrade -y

# 安装基础开发工具
sudo apt install -y \
    build-essential \
    curl \
    wget \
    git \
    vim \
    htop \
    software-properties-common \
    libssl-dev \
    libffi-dev \
    zlib1g-dev

# 安装 Python(Ubuntu 24.04 默认包含 Python 3.12)
sudo apt install -y python3 python3-pip python3-venv python3-dev

# 验证
python3 --version
pip3 --version

# 安装常用 Python 工具
pip3 install --user --upgrade pip
pip3 install --user poetry ruff mypy pre-commit

3.2 安装 pyenv 管理多版本 Python

# 安装 pyenv 依赖
sudo apt install -y \
    libbz2-dev \
    libreadline-dev \
    libsqlite3-dev \
    libncursesw5-dev \
    xz-utils \
    tk-dev \
    libxml2-dev \
    libxmlsec1-dev

# 安装 pyenv
curl https://pyenv.run | bash

# 将 pyenv 添加到 shell 配置文件(~/.bashrc 或 ~/.zshrc)
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc

# 重新加载配置
source ~/.bashrc

# 安装多版本 Python
pyenv install 3.10.15
pyenv install 3.11.9
pyenv install 3.12.5

# 设置全局版本
pyenv global 3.12.5

3.3 配置 Poetry

# 安装 Poetry
curl -sSL https://install.python-poetry.org | python3 -

# 配置 Poetry 在项目目录内创建虚拟环境(而非统一管理)
poetry config virtualenvs.in-project true

# 配置 Poetry 使用的 Python 版本
poetry config virtualenvs.prefer-active-python true

# 验证
poetry --version

3.4 WSL2 中的项目结构建议

WSL2 的文件系统性能访问 Windows 文件(/mnt/c/...)时存在显著的性能损失。建议将项目代码放在 WSL2 内部文件系统,然后在 Windows 中使用 \\wsl$ 网络路径访问:

# ❌ 不推荐:在 Windows 文件系统中开发(跨文件系统操作很慢)
/mnt/c/Users/Administrator/Desktop/my-project/

# ✅ 推荐:在 WSL2 文件系统中开发
/home/username/projects/my-project/

# ✅ 推荐:将项目链接到 Windows 资源管理器(方便访问)
# 在 WSL2 中创建符号链接
ln -s /home/username/projects /mnt/c/Users/Administrator/projects

性能测试对比:

# Windows 文件系统中的项目
time python -c "import numpy; numpy.zeros((1000, 1000))"
# real    0m0.350s  ← I/O 路径经过 9p 协议,有额外开销

# WSL2 文件系统中的项目
time python -c "import numpy; numpy.zeros((1000, 1000))"
# real    0m0.125s  ← 原生 Linux 文件系统性能

四、Docker 基础与安装

4.1 Docker 的核心概念

在进入安装步骤之前,有必要建立对 Docker 核心概念的清晰理解:

┌──────────────────────────────────────────────────┐
│                    Docker 架构                      │
│                                                    │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐       │
│  │  Client  │   │  Client  │   │  Client  │       │
│  │  (CLI)   │   │  (IDE)   │   │  (API)   │       │
│  └─────┬────┘   └─────┬────┘   └─────┬────┘       │
│        │              │              │             │
│        └──────────────┼──────────────┘             │
│                       │                            │
│                 ┌─────▼──────┐                     │
│                 │  Docker    │                     │
│                 │  Daemon    │                     │
│                 │  (dockerd) │                     │
│                 └─────┬──────┘                     │
│                       │                            │
│          ┌────────────┼────────────┐               │
│          ▼            ▼            ▼               │
│   ┌──────────┐ ┌──────────┐ ┌──────────┐          │
│   │  Image   │ │  Image   │ │  Image   │          │
│   │(只读模板) │ │(只读模板) │ │(只读模板) │          │
│   └─────┬────┘ └─────┬────┘ └─────┬────┘          │
│         │             │             │               │
│         ▼             ▼             ▼               │
│   ┌──────────┐ ┌──────────┐ ┌──────────┐          │
│   │Container │ │Container │ │Container │          │
│   │(运行实例) │ │(运行实例) │ │(运行实例) │          │
│   └──────────┘ └──────────┘ └──────────┘          │
└──────────────────────────────────────────────────┘

核心概念:

概念 类比 描述
Image(镜像) 类的定义 只读的模板,包含运行应用所需的代码、运行时、库、环境变量和配置文件
Container(容器) 类的实例 镜像的运行实例,可被创建、启动、停止、删除
Dockerfile 类的源码 用于构建镜像的自动化脚本
Registry(仓库) PyPI / npm 存储和分发镜像的中央仓库(如 Docker Hub)
Volume(卷) 外接硬盘 持久化容器数据的存储机制

4.2 在 WSL2 中安装 Docker

由于 WSL2 包含完整的 Linux 内核,可以在其中直接安装 Docker Engine(而非 Docker Desktop),这是资源占用最小的方式:

# 更新包索引
sudo apt update

# 安装依赖
sudo apt install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# 添加 Docker 官方 GPG 密钥
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 添加 Docker 软件源
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 将当前用户添加到 docker 组(避免每次使用 sudo)
sudo usermod -aG docker $USER

# 使组变更生效
newgrp docker

# 验证安装
docker --version
docker compose version

4.3 启动 Docker 守护进程

WSL2 默认不自动启动 Docker 守护进程(dockerd),需要手动启动:

# 手动启动 Docker(每次进入 WSL2 需要执行一次)
sudo service docker start

# 验证 Docker 是否正常运行
docker info

# 运行 hello-world 验证
docker run hello-world

自动化启动配置:

如果 WSL2 启用了 systemd(在 .wslconfig 中设置 systemd=true),Docker 会自动随系统启动:

# 启用 Docker 开机自启
sudo systemctl enable docker

# 手动启动
sudo systemctl start docker

五、Python 项目的 Docker 化实践

5.1 编写 Dockerfile

以一个典型的 FastAPI 项目为例:

# ===== 第一阶段:依赖安装 =====
FROM python:3.12-slim AS builder

# 设置工作目录
WORKDIR /app

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

# 安装系统依赖(仅在需要编译 C 扩展时)
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装 Poetry 并安装依赖
RUN pip install poetry && \
    poetry config virtualenvs.create false && \
    poetry install --no-dev --no-interaction --no-ansi

# ===== 第二阶段:运行镜像 =====
FROM python:3.12-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# 从 builder 阶段复制已安装的包
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# 复制应用代码
COPY . .

# 创建非 root 用户运行
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Dockerfile 最佳实践说明:

  1. 多阶段构建:将依赖安装与运行分离,大幅减小最终镜像体积
  2. 依赖层利用 Docker 缓存COPY pyproject.toml poetry.lock ./ 放在代码之前,利用 Docker 的层缓存机制——只有当依赖文件变化时才重新安装
  3. 最小化基础镜像python:3.12-slimpython:3.12 小约 800MB,只包含运行 Python 所需的最小系统库
  4. 非 root 运行USER appuser 提升容器安全性

5.2 使用 docker-compose 编排多服务

在实际项目中,一个应用往往需要多个服务配合运行(应用 + 数据库 + 缓存 + 消息队列)。docker-compose 可以一键启动所有服务:

# docker-compose.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: myproject-app
    ports:
      - "8000:8000"
    volumes:
      - .:/app          # 挂载代码目录,实现热重载
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:16-alpine
    container_name: myproject-db
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: myproject
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: myproject-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

volumes:
  postgres_data:
  redis_data:

5.3 开发工作流

# 1. 克隆项目并进入目录
git clone git@github.com:user/myproject.git
cd myproject

# 2. 启动所有服务
docker compose up -d

# 3. 查看服务状态
docker compose ps

# 4. 查看日志
docker compose logs -f app

# 5. 进入运行中的容器执行命令
docker compose exec app bash

# 6. 运行测试
docker compose exec app pytest

# 7. 停止所有服务
docker compose down

# 8. 停止并删除数据卷
docker compose down -v

5.4 开发 vs 生产:使用不同 Dockerfile

为开发和生产环境分别配置 Dockerfile,可以兼顾开发体验和生产效率:

# Dockerfile.dev(开发环境)
FROM python:3.12-slim

WORKDIR /app

# 安装开发依赖
RUN pip install poetry
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-interaction  # 包含 dev 依赖

# 代码通过 volume 挂载,无需 COPY
COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# docker-compose.override.yml(开发环境覆盖配置)
# 使用方式:docker compose -f docker-compose.yml -f docker-compose.override.yml up
services:
  app:
    build:
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app          # 挂载代码实现热重载
      - /app/__pycache__  # 排除缓存目录
    environment:
      - DEBUG=1
      - LOG_LEVEL=debug

六、进阶:Dev Containers(开发容器)

VSCode 的 Dev Containers 扩展将 Docker 容器的应用推向了新的高度——整个开发环境(包括编辑器配置、扩展、终端、调试器)都可以运行在容器中。

6.1 配置 Dev Container

在项目根目录创建 .devcontainer/devcontainer.json

{
    "name": "Python 开发环境",
    "build": {
        "dockerfile": "Dockerfile",
        "context": ".."
    },
    "customizations": {
        "vscode": {
            "extensions": [
                "ms-python.python",
                "ms-python.vscode-pylance",
                "charliermarsh.ruff",
                "ms-python.debugpy",
                "tamasfe.even-better-toml",
                "GitHub.copilot"
            ],
            "settings": {
                "python.defaultInterpreterPath": "/usr/local/bin/python",
                "python.analysis.typeCheckingMode": "basic",
                "[python]": {
                    "editor.formatOnSave": true,
                    "editor.defaultFormatter": "charliermarsh.ruff"
                }
            }
        }
    },
    "postCreateCommand": "pip install poetry && poetry install",
    "forwardPorts": [8000],
    "remoteUser": "vscode",
    "mounts": [
        "source=${env:HOME}${env:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind"
    ]
}

6.2 使用 Dev Container 的优势

优势 说明
环境即代码 开发环境配置纳入版本控制,新成员一键复现
隔离性 每个项目有独立的开发环境,互不干扰
一致性 开发环境和 CI/CD / 生产环境使用相同的 Docker 镜像
零配置上手指南 新开发者只需安装 Docker + VSCode + Dev Containers 扩展即可开始编码
# 在 VSCode 中打开项目
code /home/username/projects/myproject

# 按 F1 → "Dev Containers: Reopen in Container"
# 等待镜像构建完成,即可在容器化的开发环境中编码

七、生产环境:从开发到部署

7.1 CI/CD 流水线中的 Docker

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build Docker image
        run: |
          docker build \
            --target runtime \
            -t registry.example.com/myapp:${{ github.sha }} \
            -t registry.example.com/myapp:latest \
            .
      
      - name: Push to registry
        run: |
          docker push registry.example.com/myapp:${{ github.sha }}
          docker push registry.example.com/myapp:latest

7.2 生产部署建议

# docker-compose.prod.yml(生产配置)
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: registry.example.com/myapp:latest
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      - PYTHONOPTIMIZE=1    # 启用 Python 优化模式
      - PYTHONHASHSEED=random
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: "2GB"
        reservations:
          cpus: "0.5"
          memory: "512MB"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

八、常见问题与排查

8.1 WSL2 相关

# 问题:WSL2 占用内存过高
# 解决:配置 .wslconfig 限制内存(见 2.3 节),或手动清理
wsl --shutdown

# 问题:跨文件系统访问(/mnt/c/)性能差
# 解决:将项目移至 WSL2 内部文件系统 /home/username/projects/

# 问题:WSL2 网络代理配置
# 在 ~/.bashrc 中添加:
export host_ip=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
export http_proxy="http://$host_ip:7890"
export https_proxy="http://$host_ip:7890"

8.2 Docker 相关

# 问题:docker: permission denied
# 解决:将用户添加到 docker 组
sudo usermod -aG docker $USER && newgrp docker

# 问题:端口已被占用
# 解决:修改映射端口
docker run -p 8001:8000 myapp

# 问题:磁盘空间被 Docker 镜像占满
# 解决:清理未使用的 Docker 资源
docker system prune -a --volumes

# 查看各资源占用
docker system df

# 问题:查看容器日志
docker logs -f container_name

# 问题:进入容器调试
docker exec -it container_name bash

九、总结

WSL2 + Docker 的组合为 Python 开发者提供了一种"环境即代码"的现代工作流:

层级 技术 解决的问题
操作系统 WSL2 Windows 上获得完整 Linux 内核,消除 OS 差异
语言级 pyenv / Poetry 多版本 Python 管理、依赖隔离
应用级 Docker 应用及其依赖打包为不可变镜像,确保环境一致性
开发级 Dev Containers 开发环境容器化,团队一键复现

这套工作流的核心思想是:从开发到部署,软件运行的环境始终是同一个 Docker 镜像。不再有"开发环境能跑、线上跑不了"的问题,不再有"明明按文档配置了,就是启动不起来"的挫败感。

当你配置好了 WSL2 上的 Docker 开发环境,编写好 Dockerfile 和 docker-compose 配置,你就拥有了一个可以随身携带、在任何机器上完全一致的 Python 开发环境——这才是现代软件工程的正确打开方式。


📌 系列文章目录:

  1. ✅ 从零搭建Python开发环境:Python安装 + VSCode + PyCharm配置
  2. ✅ Conda vs. venv vs. Poetry:Python虚拟环境方案对比与最佳实践
  3. ✅ 告别编码乱码:Python开发中编码问题的终极解决方案
  4. ✅ WSL2 + Docker + Python:打造跨平台一致开发环境(本文)
  5. 🔜 终端美化与效率工具:Oh My Zsh + iTerm2 + IPython 配置指南

如果本文对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐