技术笔记

2 核 2G 的服务器能跑什么

先说结论:能跑,但前提是把重活挪到构建期。

这台机器是 2 vCPU / 1.8 GiB 内存 / 40G 磁盘,已经跑着一套容器化的 AI 网关和一组无头浏览器。 实测可用内存只剩 1.0 GiB 左右。这个数字直接决定了架构选择。

内存预算是唯一的约束

常见的"个人网站"方案是 Next.js SSR + PostgreSQL + Redis。先算一下这套在 1.8 GiB 上的账:

组件典型 RSS
Node SSR 应用150–300 MB
PostgreSQL100–200 MB
Redis30–50 MB
合计300–550 MB

再加上已有的 AI 网关(204 MB)和浏览器进程(~180 MB),直接 OOM。 而且 swap 只有 4G,一旦开始换页,响应会卡到不可用。

所以换一条路:

组件内存
静态站点(Caddy 直接吐文件)0
SQLite~5 MB
反向代理~20 MB
新增合计≈ 5 MB

差了两个数量级。

关键在于"构建期做重活"

站点内容用 Markdown 写、存在 git 里,在本地构建成静态 HTML,再推到服务器。 服务器只负责发文件,一个 PHP 进程都不需要。

这不是为了省内存而省内存。真正的收益是:

  1. 故障面极小 —— 站点挂了只可能是磁盘满或 Caddy 挂了,不会有数据库连接池耗尽这类玄学问题。
  2. 迁移成本几乎为零 —— 换个 ECS,拷一个目录就完事。
  3. 版本管理天然内建 —— 内容变更就是 git 提交,有 diff 有回滚。

代价是"发一篇要跑一次构建"。对个人博客来说,这个代价比反向代理的复杂度小得多。

反向代理的结构

vhiccup.cn {
    handle /chat* {
        reverse_proxy host.docker.internal:18789
    }
    handle /life* {
        basic_auth {
            shengyuan $2a$14$...
        }
        root * /srv/site/current/private
        file_server
    }
    handle {
        root * /srv/site/current/public
        file_server
    }
}

用 handle 而不是 route,因为 handle 是互斥的 —— 第一个匹配的块赢,顺序即优先级。 认证、路径、文件服务三件事在各自的块里各自完成,不会互相干扰。

部署用符号链接原子切换:

/srv/site/releases/20261020-213000/
/srv/site/current -> releases/20261020-213000

推新的 release 目录,然后 ln -sfn 一下就切换了。没有"删掉旧文件再复制新文件"的窗口期, 也不需要清理逻辑 —— 旧 release 攒着,想删的时候再删。

一个差点踩进去的坑

AI 网关的 Web UI 是 SPA,静态资源用的是相对路径(./assets/xxx.js), 但 API 和 WebSocket 用的是绝对路径(/api/...、/ws)。

如果直接用反向代理把它挂到 /chat 下,页面能打开,但所有 API 请求会打到主站上, 聊天直接哑掉。

好在它原生支持配置前缀:

{
  "gateway": {
    "controlUi": { "enabled": true, "basePath": "/chat" }
  }
}

设成 /chat 之后,网关自己接管这个前缀下的资源、API 和 WS,反向代理原样透传路径、不做剥离就行。

教训:挂 SPA 到子路径之前,先确认它的 API 用的是相对还是绝对路径。 这个差别决定了配置里要不要写 handle_path。

  • 服务器
  • 运维

← 返回技术笔记