在 2 核 2G 的云主机上部署 new-api,如果这台机器还跑着别的服务,裸二进制方案通常是唯一可行的选择。原因不是 new-api 本身重——它是个 Go 单体,编译出来就一个二进制——而是 Docker 守护进程加上容器化的数据库和缓存,会在系统底噪之上再叠几百 MB 常驻开销,而 2G 机器的可用余量往往只剩 1G 出头。
这篇文章不谈"Docker 好还是不好",只回答一个具体问题:在小内存机器上,这几百 MB 的差价花得值不值。
先看一台真实 2G 机器的内存都被谁吃了
大多数部署教程会告诉你"new-api 占用很小",这话没错,但它回答的不是你真正要问的问题。下面是我手上一台腾讯云 2 核 2G / CentOS Stream 9 机器的实测数据,已连续运行 34 天,跑着 nginx、MariaDB、Redis 和两个自己写的 Go 服务:
total used free shared buff/cache available
Mem: 1.9Gi 613Mi 74Mi 7.0Mi 1.2Gi 1.1Gi
Swap: 0B 0B 0B
按进程 RSS 排出来的大户:
| 进程 | RSS | 是什么 |
|---|---|---|
mariadbd | 285 MB | 数据库,单进程最大户 |
systemd-journald | 200 MB | 系统日志,默认无上限增长 |
YDService | 93 MB | 腾讯云主机安全 agent |
rsyslogd | 60 MB | 又一份日志 |
barad_agent | 27 MB | 腾讯云监控 agent |
petserver | 26 MB | 自己写的 Go 服务 |
这里有个细节要说清楚,否则数字看着不自洽:上面各进程 RSS 加起来(约 691 MB)大于 free 报的 used(613 MB),这不是我算错了。RSS 会把进程间共享的内存页(共享库、写时复制的父子页)在每个进程里各算一遍,所以 RSS 求和天然偏高。判断"还剩多少能用"要看 available,判断"谁是大户"才看 RSS。
从这张图能得出两个结论,它们直接决定选型:
- 系统底噪比应用大得多。 journald 200 MB、两个云厂商 agent 合计 120 MB、rsyslogd 60 MB —— 这些你一行代码没写就已经存在了。
- 可用余量只有 1.1 G,且没有 swap。 这意味着任何一次内存尖峰都直接触发 OOM killer,没有降级缓冲。
裸二进制方案的实际构成
new-api 是 Go 编译的单体,部署形态非常简单:
systemd
├── new-api 单二进制,Go 运行时
├── mariadbd 本机数据库
└── caddy / nginx 反向代理 + TLS
一个够用的 systemd unit:
[Unit]
Description=new-api
After=network-online.target mariadb.service
Wants=network-online.target
[Service]
Type=simple
User=newapi
WorkingDirectory=/opt/new-api
ExecStart=/opt/new-api/new-api --port 3000 --log-dir /var/log/new-api
Restart=always
RestartSec=5
# 小内存机器上值得加的两道保险
MemoryMax=512M
OOMPolicy=continue
[Install]
WantedBy=multi-user.target
MemoryMax 这行是小内存机器的关键:给应用设一个上限,让它在自己超标时被 cgroup 限制住,而不是把整机推向 OOM、让内核随机挑一个进程杀——被杀的往往是内存占用最大的 MariaDB,那就是数据库直接下线。
代价要讲清楚,裸二进制不是没有成本的:
- 升级是手动的:下载新二进制、
systemctl stop、替换文件、systemctl start,回滚要自己留旧版本 - 没有进程隔离,应用和宿主机共享一切
- 依赖宿主机的 glibc 版本,跨发行版迁移可能编译不兼容(Go 静态编译能规避大部分,但 CGO 开启时不行)
- 数据库、缓存的版本管理全靠宿主机包管理器
Docker 方案多出来的常驻开销
dockerd 与 containerd 是纯粹的编排开销,跟你的业务一点关系没有。每个容器还各带一个 shim 进程。标注中只有 mariadbd 的 285 MB 是我实测值,其余是常见范围,具体数字随版本和配置浮动。把这 120–220 MB 放回前面那张图里看:可用内存 1126 MB,Docker 一上来先拿走两成,剩下的才是留给 new-api、Postgres 和 Redis 的。在 4G 机器上这笔账无所谓;在 2G 机器上它就是压垮骆驼的那根稻草。
Docker 换来的东西也是真的,别因为省内存就否定它:
- 升级是
docker compose pull && docker compose up -d,回滚是改个 tag - 环境完全一致,本地能跑线上就能跑
- 数据库、Redis 的版本与宿主机解耦,不用跟发行版的包管理器较劲
- 多实例、多环境并存很容易
决策建议
| 场景 | 建议 | 理由 |
|---|---|---|
| 内存 ≥ 4G,机器专用 | Docker | 省心值这个钱,运维成本的节省远超内存代价 |
| 2G,且已跑其他生产服务 | 裸二进制 | 编排开销挤占的是业务余量,这笔交换不划算 |
| 2G,机器专用于 new-api | 都行,倾向 Docker | 没有邻居要抢内存,但务必先加 swap |
| 需要多实例 / 多环境并存 | Docker | 裸二进制做这件事会很痛苦 |
两个几乎所有人都忽略的内存大户
journald 默认没有上限
前面实测里 systemd-journald 占了 200 MB,这不是异常,是默认行为——它会一直增长到磁盘配额上限。小内存机器上值得直接压掉:
# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=200M
RuntimeMaxUse=50M
改完 systemctl restart systemd-journald。如果你的服务日志已经写到文件并由 logrotate 管,journald 里那份基本是重复的。
云厂商 agent
实测里 YDService(主机安全)加 barad_agent(监控)合计约 120 MB。这些是装系统镜像时预置的,你未必需要全部。要不要关取决于你的取舍:关掉监控 agent 会失去云控制台的监控图表,关掉安全 agent 会失去入侵检测告警。在 2G 机器上这 120 MB 确实值钱,但别为了省内存把安全能力一起关掉——如果你的机器上有数据库对外暴露,那点内存远不如告警重要。
无 swap 是这类机器的隐藏地雷
前面实测数据里 Swap: 0B。云主机默认不带 swap,而 2G 机器最需要它——不是为了性能,是为了在内存尖峰时有个降级路径,而不是直接被 OOM killer 杀掉。
# 加 1G swapfile
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
# 小内存机器上把 swappiness 调低,只在真的紧张时才用
sysctl -w vm.swappiness=10
代价是数据库一旦被换出磁盘,性能会明显下降。但"慢"永远好过"被杀"。
相关阅读
部署完成后,反向代理的配置有两个坑几乎人人会踩,我单独写了:
- 反代把大模型 API 的 SSE 流式响应缓冲了:五个缓冲点与三层排查法 —— 流式输出变成一次性返回,问题通常不在应用
- API 中转站的限流为什么不能信 X-Forwarded-For —— 这个头客户端能自己塞,按它限流等于没限
而本文里那个占 285 MB 的 MariaDB,本身也是个可以重新选的决定:
- 给 API 中转站选数据库:SQLite / MySQL / PostgreSQL 在小机器上的取舍 —— 换成 SQLite 能把这 285 MB 整个省掉,代价是写并发上限