适用场景
在境内机房的服务器上部署开源项目时,经常卡在三件事上:docker pull 拉不动镜像、pip/npm/go mod 装依赖反复超时、git clone 一个 GitHub 仓库要等十几分钟甚至直接断掉。这套方案适合需要在纯内网或弱网环境下把项目跑起来的开发者与运维,目标是把「看运气下载」变成「可预期、可复现」的部署流程。
环境与前置条件
- 操作系统:主流 Linux 发行版均可,建议使用各发行版当前仍在维护的 LTS 版本(具体支持周期以官方文档为准),带
systemd与sudo。 - 权限:能执行
sudo,或直接使用 root。修改/etc下的配置需要提权。 - 内存:2GB 起步可满足跑容器的需求;涉及 Go/Rust 编译、前端构建、Java 打包的机器建议 4GB 以上,否则容易在依赖解析阶段被 OOM Killer 干掉,报错看起来像网络问题。
- 磁盘:系统盘 20GB 起步。Docker 镜像层、pip/npm 缓存、Go module cache 建议放在单独的数据盘,避免写满根分区。
- 网络:能访问至少一个国内公共镜像站(阿里云、腾讯云、华为云、清华 TUNA、中科大等均有提供,地址以各镜像站官方文档当前说明为准)。若出口经过企业代理,提前拿到代理地址与认证方式。
- 工具:
curl、git、mtr(排查链路用),以及项目所需的运行时;Docker 的安装方式以官方文档当前版本为准。
分步骤部署
第 1 步:先做网络基线,定位慢在哪一段
不要一上来就改配置。先分清是 DNS 解析慢、TCP 握手失败,还是传输阶段慢:
```bash
curl -o /dev/null -s -w 'dns:%{time_namelookup} conn:%{time_connect} tls:%{time_appconnect} total:%{time_total}\n' https://github.com
mtr -rwc 20 github.com
```
如果 dns 数值明显偏高,问题在解析;conn 卡住,问题在链路或出口策略;total 远大于 tls,说明是带宽或对端限速。三种情况的处理方式完全不同。
第 2 步:优化 DNS
优先使用国内公共 DNS,并确认 /etc/resolv.conf 里没有残留的失效地址:
```bash
sudo tee /etc/resolv.conf <<'EOF'
nameserver 223.5.5.5
nameserver 119.29.29.29
options timeout:2 attempts:2
EOF
```
如果系统由 systemd-resolved 托管,直接改文件会被覆盖,应改 /etc/systemd/resolved.conf 后执行 sudo systemctl restart systemd-resolved,再用 resolvectl status 确认生效。改完用 getent hosts github.com 看是否秒回。
第 3 步:配置 Docker 镜像加速
编辑 /etc/docker/daemon.json(不存在则新建):
```json
{
"registry-mirrors": ["<镜像站提供的加速地址>"],
"max-concurrent-downloads": 5,
"dns": ["223.5.5.5", "119.29.29.29"]
}
```
```bash
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -A5 "Registry Mirrors"
```
注意两点:镜像加速通常只对 Docker Hub 生效;拉取 ghcr.io、quay.io 等其他仓库时,需要在镜像名前面拼上镜像站提供的前缀,或者走代理。另外,并发下载数不建议调得过高,带宽被多个大层抢占反而更慢。
第 4 步:配置 pip 源
在 /etc/pip.conf 写入全局配置(用户级可放 ~/.config/pip/pip.conf):
```ini
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = pypi.tuna.tsinghua.edu.cn
timeout = 60
retries = 5
```
timeout 适当调大能减少弱网下的重试失败,但不要设置成几分钟,否则排障时看不到错误。国内各云厂商也提供 PyPI 镜像,地址以对应镜像站官方文档当前说明为准。
第 5 步:配置 npm 源
```bash
npm config set registry https://registry.npmmirror.com --location=global
npm config get registry
```
如果项目里有 package-lock.json,注意锁文件里的 resolved 字段可能仍指向官方源,可以删除锁文件重新生成,或用 npm ci 前先确认网络可达。
第 6 步:配置 Go 模块代理
```bash
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOFLAGS=-mod=mod
go env GOPROXY
```
校验和数据库在弱网下也会拖慢首次构建,内网机器可以设置 GOSUMDB=off 并关闭 GOPRIVATE 之外的校验,代价是失去了供应链校验,仅在受控内网使用。
第 7 步:配置 Maven / Gradle
在 ~/.m2/settings.xml 的 <mirrors> 中加入国内镜像的 <mirror> 节点,<mirrorOf> 填 central 或 *。Gradle 则在 init.gradle 里替换 repositories。具体节点写法以各镜像站官方文档为准。
第 8 步:处理 apt / yum 源
云服务器通常自带内网软件源,速度优于公网源,不要盲目替换。确认当前源可用:
```bash
grep -rE '^(deb|baseurl)' /etc/apt/sources.list /etc/yum.repos.d/ 2>/dev/null | head
sudo apt update || sudo dnf makecache
```
第 9 步:GitHub 访问慢的替代思路
按成本从低到高排列,优先用上面的:
1. 能装包就别 clone 源码。多数依赖在 PyPI/npm 上都有发行包,装包比拉仓库快得多。
2. 浅克隆:git clone --depth=1 --single-branch <仓库地址>,能省掉绝大部分历史对象。
3. 走国内代码托管平台的镜像导入功能,把上游仓库导入后从国内地址克隆,适合长期跟随的项目。
4. SSH 走 443 端口。在 ~/.ssh/config 中:
```
Host github.com
Hostname ssh.github.com
Port 443
User git
```
然后用 ssh -T git@github.com 测试连通性。
5. 通过企业代理:git config --global http.proxy http://<代理地址>:<端口>,HTTPS 同理,用完记得 --unset。
6. 大文件与 Release 附件:在能正常访问的机器上下载,再 rsync 或 scp 传到目标机;大文件不要进 Git 仓库。
第 10 步:离线兜底与内网私有仓库
这是最稳的一条路。在联网机器上把依赖预先取下来:
```bash
pip download -d ./wheels -r requirements.txt
docker save -o app.tar <镜像名>:<标签>
mvn dependency:go-offline
```
传到目标机后离线安装:
```bash
pip install --no-index --find-links=./wheels -r requirements.txt
docker load -i app.tar
```
规模再大一些,就在内网搭私有仓库(Harbor、Nexus、Verdaccio、devpi 等,选型与部署以各自官方文档为准),让所有机器只认内网地址,外部网络抖动就影响不到发布流程。
验证部署是否成功
逐项确认配置真正生效,而不是只改不改:
```bash
docker info | grep -A5 "Registry Mirrors" # 能看到配置的加速地址
time docker pull hello-world # 秒级完成,说明镜像加速生效
pip config list # 输出 index-url 指向镜像站
npm config get registry # 输出镜像站地址
go env GOPROXY # 输出配置的代理
```
再做一个真实的小体量拉取测试:
```bash
time pip download --no-deps requests -d /tmp/wheeltest
time git clone --depth=1 https://github.com/octocat/Hello-World.git /tmp/htest
```
预期结果:pip download 在几秒内完成,日志中的下载地址是镜像站域名;浅克隆在十几秒内结束。如果耗时依旧以分钟计,回到第 1 步重新做基线,不要继续叠加配置。
常见报错与解决
报错 1: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded)
→ 原因:未配置镜像加速,或配置的加速地址已下线。
→ 解决:
```bash
sudo cat /etc/docker/daemon.json
sudo systemctl restart docker && docker info | grep -A5 "Registry Mirrors"
```
地址失效时更换为镜像站当前公布的地址,仍不通则考虑在企业代理下运行 Docker(需为 dockerd 单独配置代理环境变量)。
报错 2: WARNING: Retrying (Retry(total=4, ...)) ... ReadTimeoutError: HTTPSConnectionPool(host='pypi.org', port=443): Read timed out.
→ 原因:pip 仍在使用默认官方源,配置文件位置不对或未生效。
→ 解决:
```bash
pip config debug # 查看实际加载了哪些配置文件
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn
```
报错 3: fatal: unable to access 'https://github.com/xxx/yyy.git/': Failed to connect to github.com port 443: Connection timed out
→ 原因:出口对 443 的访问受限,或链路质量太差。
→ 解决:
```bash
git clone --depth=1 --single-branch https://github.com/xxx/yyy.git
git config --global http.proxy http://<代理地址>:<端口>
ssh -T git@github.com # 配合第 9 步的 SSH 443 配置
```
报错 4: npm ERR! code ETIMEDOUT / npm ERR! code ECONNRESET
→ 原因:registry 未切换,或被本地缓存/锁文件里的旧地址带偏。
→ 解决:
```bash
npm config get registry
npm config set registry https://registry.npmmirror.com --location=global
rm -rf node_modules package-lock.json && npm install
```
报错 5: go: github.com/xxx/yyy@vX.Y.Z: dial tcp 142.250.x.x:443: i/o timeout
→ 原因:GOPROXY 未设置或被项目内的环境变量覆盖。
→ 解决:
```bash
go env GOPROXY GONOSUMDB GOPRIVATE
go env -w GOPROXY=https://goproxy.cn,direct
go clean -modcache && go mod download
```
报错 6: Could not resolve host: xxx / Temporary failure in name resolution
→ 原因:DNS 不可达,或容器内没有继承宿主机的 DNS。
→ 解决:
```bash
getent hosts pypi.org
sudo resolvectl flush-caches 2>/dev/null || true
容器内则在 daemon.json 中配置 "dns" 字段后重启 Docker
```
后续维护
配置备份。 把 /etc/docker/daemon.json、/etc/pip.conf、~/.npmrc、~/.m2/settings.xml、/etc/resolv.conf 纳入配置管理(Ansible、SaltStack 均可),并记录每个镜像地址的来源与变更时间。镜像站地址是会变的,集中管理比靠记忆靠谱。
升级策略。 镜像加速地址、代理地址下线时,往往是批量替换。先在测试机验证新地址可用,再灰度到生产。运行时与依赖的小版本升级单独排期,不要和网络配置变更混在同一次发布里。
日志与监控。 关注这几处:journalctl -u docker 看拉取失败,docker events 看容器生命周期,构建流水线的日志中搜索 timeout、Retry、ECONNRESET 统计失败率。磁盘水位需要单独监控,镜像层和包缓存增长很快。
缓存清理。 定期执行 pip cache purge、npm cache clean --force、go clean -modcache,Docker 侧用 docker system prune 但要确认不会删掉正在使用的镜像,建议加 --filter 限定范围。内网私有仓库同样需要设置清理策略并做定期备份。
回滚预案。 每次调整源地址前,保留一份原配置;出问题时能一条命令切回去,比现场排查快得多。
