国内构建拉不动镜像:代理与镜像仓库 fallback 策略

在国内服务器或 CI 构建集群上跑构建,最耗时间的不是编译,是网络:docker pull 卡死、npm 安装超时、GitHub 克隆失败。这些问题的共性是目标服务在海外,而解决思路可以收敛成一套固定的 fallback 顺序,不用每次现想。

总原则:先直连,失败了才绕路

直连永远先试——能直连时延迟最低、链路最短。只有明确失败(超时、连接拒绝、DNS 解析失败)才走 fallback,而且要自动重试,不要让人手动判断这次该不该加代理。

对 git、curl、wget 这类命令行工具,fallback 就是加代理前缀重试:

git pull || https_proxy=http://127.0.0.1:7897 http_proxy=http://127.0.0.1:7897 git pull

注意代理是 per-command 的环境变量,不要 export 到全局——全局代理会让本来能直连的内网请求也绕一圈。

npm/pnpm:换源比代理更稳

包管理器不建议走代理,直接换国内镜像源,一次配置长期受益:

pnpm config set registry https://registry.npmmirror.com
# corepack 下载 pnpm 自身也要换源,否则卡在第一步
export COREPACK_NPM_REGISTRY=https://registry.npmmirror.com

CI 环境里还有一个细节:Node 本体未必预装,从 npmmirror 的二进制镜像下载 Node 压缩包比官方源可靠得多。

Docker 镜像:SWR 中转 + tag 回原名

docker pull 没有通用的代理环境变量可用(daemon 的网络不归 shell 管),直连 docker.io 超时时的可靠路径是借道华为云 SWR 的镜像加速:

docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/<源仓库>/<镜像名>
docker tag <SWR 地址> <原始镜像名>

<源仓库> 是原镜像的仓库前缀:ghcr.io、mcr.microsoft.com 照写,官方镜像(如 postgres:17-alpine)无前缀就填 docker.io。

tag 回原名是关键一步,不要省:compose 文件、k8s 清单里写的都是原始镜像名,本地 tag 成同名后这些配置一行都不用改。否则每换一个 pull 渠道就要改一遍编排文件,渠道就又变成了耦合点。

CI 里的特殊形态:改构建副本,不动仓库

构建集群和仓库代码是两回事。集群直连不了 docker.io,但不能把 SWR 地址提交进仓库的 Dockerfile(本地和其他环境还要用 docker.io)。做法是在构建步骤里 sed 替换构建集群上那份副本:

sed -i 's|node:22-alpine|swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/node:22-alpine|g' Dockerfile

每个 CI job 都是全新工作区,这个改动只存在于本次构建,仓库保持干净。同理,构建期参数组件不支持的注入(比如站点 URL),也用 sed 在构建步骤里完成。

结论

网络问题的工程化处理就三层:命令行工具加代理前缀重试、包管理器换国内源、Docker 镜像走 SWR 中转并 tag 回原名。共同原则是同一条:fallback 只改变「怎么拿到」,不改变「拿到之后叫什么」——所有下游配置面向原始名称写死,渠道可以随时换。