← 返回博客
架构2026-08-31 13:20:096 分钟 · 1,670 0

工业数字孪生实战:前后端分离架构与 CI/CD 落地

一个工业数字孪生 Demo 从本地跑通到生产部署的全过程:2G 小服务器如何同时扛住前端与 Go 实时后端,前后端拆子域、CI/CD 双路、nginx 反代 WebSocket 的实战踩坑。

今年做了一个工业数字孪生 Demo(wxbuluo-twin):前端 Next.js + R3F 做 3D 实时大屏,后端 Go + WebSocket 广播设备遥测。功能本身不复杂,真正磨人的是怎么把它稳稳地部署到生产——尤其是手里只有一台 2G 内存的小服务器,前端还想用 Vercel 白嫖。这篇文章讲清楚从架构决策到 CI/CD 落地的全过程,所有坑都是真撞过的。

一、为什么必须前后端拆开

一开始的想法很朴素:一台服务器、一个域名、前后端一起跑。但两个硬约束把这条路堵死了:

1. 2G 内存跑不动双进程。 前端 Next.js(Node 运行时)常驻就要吃掉几百 MB,加上 Go 后端、加 nginx,2G 内存频繁 swap,部署一次卡到怀疑人生。所以前端必须外迁——Vercel 的 Edge/Serverless 不占我服务器资源,刚好。

2. WebSocket 长连接穿不过 Vercel。 Vercel 的 Serverless Function 不适合长连接,而且一个域名 DNS 只能指一处:wxbuluo-twin.vercel.app 的流量全进 Vercel,/ws/ 无法回源到我服务器。数字孪生的实时遥测恰恰是 WS 长连接,必须给后端单独开个子域。

结论很清晰:前端上 Vercel,后端留服务器,WS 走独立子域 twin-api.wxbuluo.com

二、子域与端口规划

地址说明
前端wxbuluo-twin.vercel.app(规划绑 twin.wxbuluo.comVercel 托管,不占服务器
后端 WStwin-api.wxbuluo.com:443nginx 反代到容器内后端
后端容器twin-server(docker 网络内 :8080不占宿主 8080(被 frps 占着)
镜像ghcr.io/ningbnii/wxbuluo-twin-server:latestGitHub 构建推送,服务器只拉

关键设计:twin-server 只在 docker_default 网络内暴露 8080,由 nginx 反代出去,不抢宿主端口。宿主 8080 已经被 frps 占用,这样两者互不干扰。

三、CI/CD 双路:同一个 main,按目录分流

整个仓库是单仓,但前端和后端走完全不同的两条部署链路,靠目录过滤分开:

前端 web/** → Vercel(GitHub webhook 自动部署)

  • 项目从 Git 导入时,Vercel 在 GitHub 建了 webhook;git push origin main 自动触发构建部署。
  • 本地不需要装 vercel CLI,所谓"本地授权一下"就是导入时 GitHub 给 Vercel 的 OAuth,它顺手建好了 webhook。
  • 前端连后端必须设环境变量 NEXT_PUBLIC_WS_URL=wss://twin-api.wxbuluo.com/ws/(Production/Preview/Development 三档全选)。这个变量是构建期内联的,改了变量不会自动部署,必须手动 Redeploy;但 push 代码一定自动部署。

后端 server/** → GitHub Actions(.github/workflows/deploy.yml

  • server job:go vet + 100% 覆盖率门禁 + go build ./...
  • deploy-server job:GitHub runner 上 docker/build-push-action 构建镜像推到 ghcr.io,然后 SSH 到服务器 docker pull + docker compose up -d,服务段统一纳管在服务器的 /docker/docker-compose.yml
  • 服务器 2G 内存绝不本地构建镜像,镜像统一在 runner 上造好,服务器只负责拉和起。

四、nginx 反代 WebSocket:slb 架构

服务器上对外 SSL 的是 nginx 容器 slb(映射宿主 0.0.0.0:80/443,挂在 docker_default 网络,和 twin-server 同网)。这点很关键:因为同网络,proxy_pass http://twin-server:8080 直接用容器名解析,不用写 IP。

nginx/twin-api.wxbuluo.com.conf(仓库里是唯一事实源)核心三段:

# 80 端口:配合 certbot 做 HTTP-01 验证
server {
    listen 80;
    server_name twin-api.wxbuluo.com;
    location /.well-known/acme-challenge/ { root /etc/nginx/conf.d/acme; }
    location / { return 301 https://$host$request_uri; }
}

# 443 端口:WS 升级头 + 长超时
server {
    listen 443 ssl;
    server_name twin-api.wxbuluo.com;
    ssl_certificate     /etc/letsencrypt/live/twin-api.wxbuluo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/twin-api.wxbuluo.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location /ws/ {
        proxy_pass http://twin-server:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
    location / { return 404; }   # 本子域只服务 WS
}

证书用 certbot 容器签发(certbot/certbot certonly --webroot),并在 cron 里加了续期行 renew --cert-name twin-api.wxbuluo.com——否则默认续期只管 analytics/www,90 天后会过期。

五、踩坑实录(每个都真撞过)

  1. build-push-actionfile: 相对仓库根解析file: Dockerfile.server 找不到,必须写 server/Dockerfile.server,和 context: server 不是一套相对基准。
  2. Go 版本三处必须一致go.modgo 1.27,Dockerfile 基础镜像却用 golang:1.22——Go 1.21+ 默认 GOTOOLCHAIN=auto,CI runner 能联网下 toolchain 门禁过,但 Docker 构建容器内拉不到 go.dev/dlgo mod download 直接 exit 1。修法:镜像和 setup-go 同步升 1.27。
  3. docker compose up --pull 参数错。新版 --pull 要的是 always/missing/never 值,不是服务名,--pull twin-server 会报 invalid --pull option。前面已单独 pull,这里直接去掉。
  4. nginx 反代架构认知web1 容器只在内网不对外,新子域 conf 必须放 slb/docker/nginx/slb/conf.d/;conf 必须含 acme-challenge location 和 options-ssl-nginx.conf/ssl_dhparam,否则签发被 80→443 跳转挡住、SSL 配置也不完整。
  5. curl 测 WS 必须 --http1.1。curl 默认走 HTTP/2,WS 升级只在 HTTP/1.1 上,否则返回 400 Bad Request——这是测试工具陷阱,浏览器真实客户端不受影响。
  6. git push 带二进制超 postBuffer。一次提交了 7.1MB 的 GLB/wasm/jpg,git push 报 HTTP 400 / RPC failed。git config http.postBuffer 1073741824(1G)后一次过。
  7. Vercel 设变量走错面板。不是 "Create Environment" 弹窗(那是 Pro 的自定义环境功能),而是左侧菜单 Environment Variables,表格三列 Key/Value/Environments。

六、验证结果(实锤)

  • twin-server 容器运行中,镜像 ghcr.io/ningbnii/wxbuluo-twin-server:latest
  • curl <容器IP>:8080/healthz{"status":"ok"}
  • 外部 wss://twin-api.wxbuluo.com/ws/(HTTP/1.1)→ 101 Switching Protocols,后端实时推送 assets/metrics/tick/alarm
  • twin-api 证书 Let's Encrypt 有效至 2026-11-28(续期 cron 已加)
  • 前端 Vercel 自动部署 Ready,WS 直连 twin-api 成功

七、小结与系列预告

这套前后端分离 + CI/CD 双路的本质,就是把"重"的交给平台(Vercel 扛前端、GitHub runner 扛镜像构建),只留"轻"的在服务器(拉镜像 + 起容器 + 反代)。2G 内存的小服务器也能稳稳跑起一个带实时 WS 的数字孪生。

这个系列后面两篇会深入具体技术点:

  • (二)前端 3D 实时渲染:R3F + GLB 模型加载,以及"浏览器里看不到模型纹理"的完整诊断与修复(envMap / metalness / 色彩空间 / UV repeat)。
  • (三)Go + WebSocket 实时遥测后端:从 gorilla Hub 广播、CheckOrigin 同源白名单,到 GitHub Actions 推镜像自动部署的逐行拆解。

如果哪块想先看,或者想让我把某个坑展开成独立短文,直接说。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/160