服务器不能编译怎么办

接到迁移任务的时候,新服务器是 4 核 8G,装了 1Panel 面板。

旧机要下线,三端服务整体搬过去。

三端指的是一个 H5 静态站、一个 Go 后端、一个 Python 管理后台。

真正的麻烦是这台新机上,已经跑着 6 个别人的线上站点。

一开始我想的方案最省事:代码传上去,在服务器上跑一条 docker compose build,起容器,完事。

Docker 是容器运行时,compose 是它官方的多容器编排工具。

一份 yaml 里写清几个容器怎么起、怎么连。

三步走完,本地不用装任何东西,也不用镜像导出。

然后我看了下服务器的实测回显,这个方案就放弃了。

第一眼,同机不是空机。面板里列出来的站点目录有 6 个。

也就是说,任何一次把 CPU 打满的动作,都是同时在影响 6 个跟我毫不相干的业务。

第二眼,出网探测。服务器不是我的开发机,它能不能访问外网,得实测。

yaml 复制代码
goproxy.cn   : HTTP/2 200      √
pypi 清华源  : HTTP/2 200      √
github.com   : (空)           ×
docker hub   : (空)           × 直连不通(但配了云厂商内网加速兜底)

后注一条。我当时判的「github 不通」,后来被推翻过。

当时的探测命令带了 8 秒超时,空响应其实是超时,不是不通。

但「源码走本地导出」这个结论没有被推翻,原因在后面。

「最省事」这个想法是我站在「只管我这一个项目」的视角得出的。

一旦把同机的 6 个站点算进去,它立刻变成最麻烦的那个方案。

先说镜像怎么来的。三个 Dockerfile 的构建方式,代价完全不是一个量级。

app-h5 最轻。Dockerfile 只有一句 COPY dist,把静态产物拷进 nginx:1.27-alpine,纯拷贝。

app-gin 最重,走多阶段构建,在 golang:1.25-alpine 里跑 go build。

它要拉约 300MB 的基础镜像,还要拉依赖、编译。

app-admin 居中,基础镜像是 python:3.11-slim,再加 pip install 装 20 多个包。

它要拉约 130MB 的镜像,再装依赖。

三个镜像做完之后,实测体积是这样的。

app-admin:latest 是 305 MB,基础镜像加 pip 依赖占了大头。

app-h5:latest 是 110 MB,nginx 加静态产物。

app-gin:latest 最小,48.4 MB,Go 静态二进制。

三个加起来大约 463 MB,docker save 的输出量级大致也是这个数。

顺带说一句 :latest。它只是镜像的 tag,一个可以移动的名字。

真正标识一个镜像的,是它那串 sha256 摘要。这两者不是一回事,后面我会专门讲它。

于是有了三条路线的对比。它按的是「编译这件事发生在哪台机器上」,跟哪个更高级无关。

路线 A,全在服务器 build。服务器上要跑编译,本地不需要 Docker。

但它得让服务器出网拉基础镜像,约 270MB。首次构建估计要 8 到 15 分钟满载 CPU。

这条路对同机 6 个站点的影响是实打实的:CPU 打满,响应变慢甚至超时。

后续改代码发版,靠的是 git pull 加 build。

路线 B,只传镜像,源码不上机。服务器不跑编译,本地需要 Docker,服务器只拉数据库和缓存。

源码不在服务器上,只有配置。排障最差,因为是个黑盒。风险是镜像和源码容易脱节。

路线 C,源码上机,镜像在本地构建。服务器同样不跑编译,本地需要 Docker。

服务器只拉数据库和缓存,源码留在服务器上,排障方便。代价是依赖本地 Docker。

改代码之后本地重构建,重传的只是那一个镜像。

我选的是 C。三条理由,每一条都对着上面的一行。

编译的 CPU 花在我自己的电脑上,不花在别人正在访问的机器上,对同机业务零冲击。

首次部署和后续发版走的是同一条路,不会出现首次能上、之后没法发版的断层。

源码仍然留在服务器上,出了事能上去看配置、改 .env、翻日志,不是一个打不开的黑盒。

路线 A 被否掉的核心不是它慢。慢是自己扛的事,可它同时还是一次对共享资源的争抢,这部分是服务器上其他人的系统替我扛的。

决定不在这台服务器上编译之后,下一步是把「要传什么」拆开。

一拆就发现,代码仓库里其实混着三种命运完全不同的东西。

先说源码。

git archive 把某个提交的已跟踪文件直接导成一个压缩包。

它不产出 .git 目录,也不需要任何凭证。

它和 git clone 的差别,在服务器上尤其明显。

clone 必须让服务器出网到代码托管平台,archive 不需要。

clone 带着 .git 目录,几十 MB;archive 不带。

node_modules 那边,clone 视情况带,archive 一定不带,因为它只导已跟踪文件。

仓库是私有的,clone 要配 PAT 或者 SSH;archive 不需要任何凭证。

上传体积上,clone 是仓库全量,archive 大约只有 45MB。

bash 复制代码
git archive --format=tar.gz -o app-src.tar.gz HEAD
ls -lh app-src.tar.gz      # 预期约 45MB

这一条同时解决三件事。服务器出不出网,已经不重要了。

不用在服务器上留一份 PAT。

一个几百 MB 的仓库,被压成了 45MB。

再说构建产物。

这是最容易被忽略的一刀。实测。

bash 复制代码
git ls-files 2_Code/2_UniApp_Vue3/dist | wc -l   →  0        ← dist 未被跟踪

dist 是前端打包的输出目录,本来就不该进版本库。

但 app-h5 的 Dockerfile 里写的是 COPY dist /usr/share/nginx/html。

dist 不存在或者为空,构建出来的就是一个空站点。

表现是页面全白,没有任何报错。因为 nginx 确实起来了,只是没内容可给。

所以 dist 必须作为独立产物,单独打包,单独传。

bash 复制代码
cd 2_Code/2_UniApp_Vue3
npm run build
ls -la dist/index.html          # 确认时间戳是刚刚生成的
tar -czf app-dist.tar.gz dist

顺带一条。dist 是带过期时间的产物。

仓库里躺着的那个,可能是几周前构建的。

部署前必须重跑一次 npm run build,否则你上的是旧代码。

剩下的是镜像。

docker save 把本地构建好的镜像导出成一个 tar 文件,服务器侧用 docker load 读回来。

整个过程里,服务器不编译、不拉基础镜像、不装依赖。

bash 复制代码
docker save app-gin:latest app-admin:latest app-h5:latest -o app-images.tar
sha256sum app-images.tar | tee app-images.tar.sha256
gzip -9 app-images.tar

顺带一句。我最初按基础镜像的经验值估的是 gzip 后约 200MB,真打出来是 121MB。

所以体积这件事一律以 ls -lh 的实测为准,别拿估算值去规划带宽。

真正花时间的地方不在打包命令,而在决定什么不该进包。

一共有三处打包处置。

Python 的 venv 目录被排除了。它是 Windows 虚拟环境,虽然被 git 跟踪了,但传上 Linux 完全没用。

这一条让包从 74MB 降到 36.8MB。

5 个 Go 的 .exe 编译产物也被排除了。它们全是 Windows 二进制,.dockerignore 本来就排除它们。

这一条省掉 78MB。

第三处是改用 tar,不再用 git archive。

git archive HEAD 只导已跟踪文件,会漏掉还没 commit 的新文件。

比如服务器版编排文件、导入脚本、env 模板,这些就漏了。

换成 tar 之后,这些新文件才进得了包。

第三处最值得记住。git archive 的「只导已跟踪文件」,既是优点也是边界。

它天然排除了 node_modules 和 .git,但也天然会漏掉昨天刚写、还没提交的配置。

一旦包里必须包含未提交文件,就得换成 tar 加一份显式排除清单。

这时候「排除什么」变成了要负责的事。

然后是那个反直觉的发现。

复制代码
app-images.tar      121 MB
app-images.tar.gz   121 MB        ← gzip 之后,体积没变

为什么没变?因为镜像是由若干只读的层叠加而成的。

而 Docker 的每一层,本身就是一个 gzip 压缩过的 tar。

外层再压一遍,等于拿 gzip 去压 gzip,几乎零收益。

这件事有两个推论。

别为镜像包花时间等 gzip 了。

但要注意,sha256sum 算的是未压缩的那个 tar。

要么先 gunzip -k 解出原包,再 -c 校验;要么干脆跳过校验,直接 docker load。

load 成功本身,就已经证明包是完整的。

当时还算运气的一点是,docker load 能直接读 .tar.gz,不必先解压。

一次「服务器零编译」的迁移,最终交付的就是四样东西。

app-src.tar.gz 是源码包,437 个文件,36.8 MB。它由本地打包生成,打包时排除了 venv 和 .exe。

app-dist.tar.gz 是 H5 静态产物,137 个文件,17.1 MB。它是本地跑 npm run build 生成的。

app-images.tar.gz 里是三个业务镜像,121 MB,本地 docker save 生成。

app-images.tar.sha256 是完整性校验值,82 B,sha256sum 算出来的。

在服务器上跑的命令里,没有一条是 docker build,收尾的是 docker load。

有一点我到现在也没完全搞明白:镜像包 gzip 之后体积为什么一点没变

相关推荐
god_cvz1 小时前
第三章:1、Tool Calling(工具调用)
后端·ai编程
会编程的吕洞宾1 小时前
Spring AI 2.0 自校正结构化输出实战:让大模型稳定吐出合格 JSON
java·人工智能·后端·spring
深蓝AI2 小时前
Future.result 超时了,Python 线程却没停:线程池取消任务的三个陷阱
后端
suhan2 小时前
【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)
数据结构·后端·算法
鸿蒙开发2 小时前
我把自己的应用上线了:从买服务器、域名和 DNS,到备案与 HTTPS
后端·github
neoooo2 小时前
🚀Claude Code 小技巧(二):找回会话,撤销修改
前端·人工智能·后端
数据库小学妹2 小时前
向量检索最小可运行实现:HNSW索引参数与召回率/延迟权衡
数据库·后端
姜鱼问生2 小时前
Docker Volume 备份与恢复实战:不停机迁移数据
后端·算法·架构
suhan2 小时前
【数据结构】三、栈和队列:5.顺序队列(循环队列)(初始化,判空判满,入队,出队,实例)
数据结构·后端·算法