解决 WSL2 环境 Docker Pull 超时:基于 systemd 的代理动态注入方案


本文总阅读量次

wsl-docker-proxy-automation.png


每次重启 Windows,旧版 WSL2(NAT 模式)的宿主机网关 IP 都会重新分配。这就导致了一个典型的进程隔离问题:终端里的 curl 还能靠 .profile 动态拿到新 IP 正常请求,但后台 systemd 托管的 dockerd 根本吃不到这个环境变量,一执行 docker pull 就直接超时。

分享一套我目前在用的自动化注入方案,核心就是利用 systemd 依赖,让 Docker 守护进程每次启动前动态感知 Windows 新 IP,免去手动改配置的麻烦。

前置环境确认

在动手前,确认下你的环境是否匹配这套方案:

  1. 未使用有些臃肿的 Docker Desktop,而是在 WSL2 内部署了原生的 Docker Engine。
  2. WSL 已启用 systemd(执行 ps -p 1 -o comm= 确认输出为 systemd)。
  3. 宿主机的代理软件已开启“允许局域网连接”(监听地址包含 0.0.0.0)。

核心步骤 1:抓取宿主机动态 IP

首当其冲的是,我们需要计算 Windows 当前的网关 IP。

新建 /etc/profile.d/wsl-proxy.sh(假设你的代理端口是 7897,按需修改),这类似于为整个 WSL 系统提供一个基准配置:

sudo tee /etc/profile.d/wsl-proxy.sh >/dev/null <<'EOF'
# Configure WSL2 NAT to use the Windows-hosted proxy.
if [ -n "${WSL_INTEROP:-}" ] || grep -qi microsoft /proc/version 2>/dev/null; then
    _wsl_host_ip="$(ip -4 route show default 2>/dev/null | awk '{print $3; exit}')"

    if [ -n "$_wsl_host_ip" ]; then
        export http_proxy="http://${_wsl_host_ip}:7897"
        export https_proxy="$http_proxy"
        export all_proxy="$http_proxy"

        export HTTP_PROXY="$http_proxy"
        export HTTPS_PROXY="$http_proxy"
        export ALL_PROXY="$http_proxy"

        export no_proxy="localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
        export NO_PROXY="$no_proxy"
    fi

    unset _wsl_host_ip
fi
EOF

核心步骤 2:Systemd 自动化桥接机制

WSL 刚启动时,网卡和路由表初始化存在毫秒级延迟。在 Docker 里挂载代理,核心思路是写一个带重试机制的脚本,配合 systemd 在 Docker 服务拉起前强制拦截并注入环境变量。

新建 /usr/local/sbin/update-docker-proxy-env 并执行 sudo chmod 0755 /usr/local/sbin/update-docker-proxy-env 赋予执行权限:

sudo tee /usr/local/sbin/update-docker-proxy-env >/dev/null <<'EOF'
#!/bin/sh
set -eu

PROFILE=/etc/profile.d/wsl-proxy.sh
RUNTIME_DIR=/run/docker-proxy
ENV_FILE="$RUNTIME_DIR/environment"

if [ ! -r "$PROFILE" ]; then
    echo "Cannot read $PROFILE" >&2
    exit 0
fi

# 隔离执行环境
unset HTTP_PROXY HTTPS_PROXY NO_PROXY || true

# 轮询等待 IP 注入
i=0
while [ "$i" -lt 30 ]; do
    . "$PROFILE"
    if [ -n "${HTTP_PROXY:-}" ] && [ -n "${HTTPS_PROXY:-}" ]; then
        break
    fi
    i=$((i + 1))
    sleep 1
done

install -d -m 0755 "$RUNTIME_DIR"
tmp_file="$(mktemp "$RUNTIME_DIR/environment.XXXXXX")"
trap 'rm -f "$tmp_file"' EXIT HUP INT TERM

if [ -n "${HTTP_PROXY:-}" ]; then
    {
        printf 'HTTP_PROXY=%s\n' "$HTTP_PROXY"
        printf 'HTTPS_PROXY=%s\n' "$HTTPS_PROXY"
        printf 'NO_PROXY=%s\n' "${NO_PROXY:-}"
        printf 'http_proxy=%s\n' "$HTTP_PROXY"
        printf 'https_proxy=%s\n' "$HTTPS_PROXY"
        printf 'no_proxy=%s\n' "${NO_PROXY:-}"
    } > "$tmp_file"
    printf 'Docker proxy environment generated: %s\n' "$HTTP_PROXY"
else
    # 生成空文件,避免无代理时破坏 Docker 启动
    printf '' > "$tmp_file"
    echo "Warning: No proxy was generated by $PROFILE. Docker will start without proxy." >&2
fi

chmod 0600 "$tmp_file"
mv -f "$tmp_file" "$ENV_FILE"
trap - EXIT HUP INT TERM
EOF

接着,用 systemd 建立启动顺序,新建 /etc/systemd/system/docker-proxy-env.service:

sudo tee /etc/systemd/system/docker-proxy-env.service >/dev/null <<'EOF'
[Unit]
Description=Generate Docker proxy environment from WSL proxy profile
Wants=network-online.target
After=network-online.target
Before=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-docker-proxy-env
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

最后,通过 drop-in 文件挂载代理环境。

注意 EnvironmentFile=-/... 里的减号绝对不能漏,它的作用是即使环境配置文件缺失,系统也不会报错中断启动,保障 Docker 基础服务能继续跑:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf >/dev/null <<'EOF'
[Unit]
Wants=docker-proxy-env.service
After=docker-proxy-env.service

[Service]
EnvironmentFile=-/run/docker-proxy/environment
EOF

启动与避坑指北

全搞定后,重载 systemd 并启动服务:

sudo systemctl daemon-reload
sudo systemctl enable docker-proxy-env.service
sudo systemctl restart docker-proxy-env.service
sudo systemctl restart docker.service

执行 docker info | grep -i proxy。如果看到宿主机的新 IP 和你设置的端口号,直接 docker pull 即可。

顺带一提几个日常维护的踩坑点和操作细节:


本站总访问量次