在 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是什么
mariadbd285 MB数据库,单进程最大户
systemd-journald200 MB系统日志,默认无上限增长
YDService93 MB腾讯云主机安全 agent
rsyslogd60 MB又一份日志
barad_agent27 MB腾讯云监控 agent
petserver26 MB自己写的 Go 服务
进程 RSS(实测 · 2C2G CentOS Stream 9)mariadbd285 MBjournald200 MBYDService93 MBrsyslogd60 MBbarad_agent27 MBpetserver26 MB(自研 Go 服务)available≈ 1126 MBSwap: 0B —— 没有兜底,OOM 直接杀进程注:RSS 之和大于 free 的 used,因为共享页在每个进程里都被重复计入
实测数据。真正值得注意的不是哪个进程最大,而是你的应用还没启动,系统底噪加数据库已经吃掉了大约 580 MB。

这里有个细节要说清楚,否则数字看着不自洽:上面各进程 RSS 加起来(约 691 MB)大于 free 报的 used(613 MB),这不是我算错了。RSS 会把进程间共享的内存页(共享库、写时复制的父子页)在每个进程里各算一遍,所以 RSS 求和天然偏高。判断"还剩多少能用"要看 available,判断"谁是大户"才看 RSS。

从这张图能得出两个结论,它们直接决定选型:

  1. 系统底噪比应用大得多。 journald 200 MB、两个云厂商 agent 合计 120 MB、rsyslogd 60 MB —— 这些你一行代码没写就已经存在了。
  2. 可用余量只有 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 方案多出来的常驻开销

裸二进制Dockersystemdnew-api40–80 MBmariadbd285 MB 实测caddy10–25 MB额外常驻开销0 MBsystemddockerd80–150 MBcontainerd40–70 MBshim → new-apishim → postgresshim → redis额外常驻开销120–220 MB灰底两项是应用之外白交的
右侧灰底的 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

代价是数据库一旦被换出磁盘,性能会明显下降。但"慢"永远好过"被杀"。

相关阅读

部署完成后,反向代理的配置有两个坑几乎人人会踩,我单独写了:

而本文里那个占 285 MB 的 MariaDB,本身也是个可以重新选的决定: