<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Systemd on Ambertu</title><link>https://ambertu.com/tags/systemd/</link><description>Recent content in Systemd on Ambertu</description><generator>Hugo</generator><language>zh-CN</language><copyright>2026 Ambertu · 粤ICP备18071259号</copyright><lastBuildDate>Thu, 03 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ambertu.com/tags/systemd/index.xml" rel="self" type="application/rss+xml"/><item><title>new-api 部署选型：2G 小内存机器上裸二进制比 Docker 省多少</title><link>https://ambertu.com/posts/new-api-binary-vs-docker/</link><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><guid>https://ambertu.com/posts/new-api-binary-vs-docker/</guid><description>在 2 核 2G 云主机上部署 new-api，裸二进制和 Docker 到底差多少内存？本文用一台真实 2G 机器的内存实测数据说明：系统底噪吃掉的比你以为的多，选型的关键根本不在应用本身。</description><content:encoded><![CDATA[<p>在 2 核 2G 的云主机上部署 new-api，如果这台机器还跑着别的服务，<strong>裸二进制方案通常是唯一可行的选择</strong>。原因不是 new-api 本身重——它是个 Go 单体，编译出来就一个二进制——而是 Docker 守护进程加上容器化的数据库和缓存，会在系统底噪之上再叠几百 MB 常驻开销，而 2G 机器的可用余量往往只剩 1G 出头。</p>
<p>这篇文章不谈&quot;Docker 好还是不好&quot;，只回答一个具体问题：<strong>在小内存机器上，这几百 MB 的差价花得值不值。</strong></p>
<h2 id="先看一台真实-2g-机器的内存都被谁吃了">先看一台真实 2G 机器的内存都被谁吃了</h2>
<p>大多数部署教程会告诉你&quot;new-api 占用很小&quot;，这话没错，但它回答的不是你真正要问的问题。下面是我手上一台腾讯云 2 核 2G / CentOS Stream 9 机器的实测数据，已连续运行 34 天，跑着 nginx、MariaDB、Redis 和两个自己写的 Go 服务：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">              total        used        free      shared  buff/cache   available
</span></span><span class="line"><span class="cl">Mem:          1.9Gi       613Mi        74Mi       7.0Mi       1.2Gi       1.1Gi
</span></span><span class="line"><span class="cl">Swap:            0B          0B          0B
</span></span></code></pre></div><p>按进程 RSS 排出来的大户：</p>
<table>
	<thead>
			<tr>
					<th>进程</th>
					<th>RSS</th>
					<th>是什么</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>mariadbd</code></td>
					<td>285 MB</td>
					<td>数据库，单进程最大户</td>
			</tr>
			<tr>
					<td><code>systemd-journald</code></td>
					<td>200 MB</td>
					<td><strong>系统日志，默认无上限增长</strong></td>
			</tr>
			<tr>
					<td><code>YDService</code></td>
					<td>93 MB</td>
					<td>腾讯云主机安全 agent</td>
			</tr>
			<tr>
					<td><code>rsyslogd</code></td>
					<td>60 MB</td>
					<td>又一份日志</td>
			</tr>
			<tr>
					<td><code>barad_agent</code></td>
					<td>27 MB</td>
					<td>腾讯云监控 agent</td>
			</tr>
			<tr>
					<td><code>petserver</code></td>
					<td>26 MB</td>
					<td>自己写的 Go 服务</td>
			</tr>
	</tbody>
</table>
<figure>
<svg viewBox="0 0 720 300" role="img" aria-label="一台 2 核 2G 云主机的进程内存占用条形图：MariaDB 285MB、journald 200MB、腾讯云安全 agent 93MB、rsyslogd 60MB、监控 agent 27MB、自研 Go 服务 26MB，可用内存约 1126MB" style="width:100%;height:auto;color:currentColor">
  <text x="0" y="14" font-size="11" font-family="monospace" fill="currentColor" opacity="0.6">进程 RSS（实测 · 2C2G CentOS Stream 9）</text>
  <g font-size="11" font-family="monospace" fill="currentColor">
    <text x="0" y="48">mariadbd</text>
    <rect x="150" y="38" width="285" height="14" fill="currentColor" opacity="0.75"/>
    <text x="443" y="48" opacity="0.7">285 MB</text>
    <text x="0" y="76">journald</text>
    <rect x="150" y="66" width="200" height="14" fill="currentColor" opacity="0.75"/>
    <text x="358" y="76" opacity="0.7">200 MB</text>
    <text x="0" y="104">YDService</text>
    <rect x="150" y="94" width="93" height="14" fill="currentColor" opacity="0.5"/>
    <text x="251" y="104" opacity="0.7">93 MB</text>
    <text x="0" y="132">rsyslogd</text>
    <rect x="150" y="122" width="60" height="14" fill="currentColor" opacity="0.5"/>
    <text x="218" y="132" opacity="0.7">60 MB</text>
    <text x="0" y="160">barad_agent</text>
    <rect x="150" y="150" width="27" height="14" fill="currentColor" opacity="0.5"/>
    <text x="185" y="160" opacity="0.7">27 MB</text>
    <text x="0" y="188">petserver</text>
    <rect x="150" y="178" width="26" height="14" fill="currentColor" opacity="0.5"/>
    <text x="184" y="188" opacity="0.7">26 MB（自研 Go 服务）</text>
  </g>
  <line x1="150" y1="208" x2="700" y2="208" stroke="currentColor" stroke-width="1" opacity="0.25"/>
  <g font-size="11" font-family="monospace" fill="currentColor">
    <text x="0" y="234">available</text>
    <rect x="150" y="224" width="422" height="14" fill="none" stroke="currentColor" stroke-width="1.2" stroke-dasharray="4 3"/>
    <text x="580" y="234" opacity="0.7">≈ 1126 MB</text>
  </g>
  <text x="0" y="268" font-size="10" font-family="monospace" fill="currentColor" opacity="0.6">Swap: 0B —— 没有兜底，OOM 直接杀进程</text>
  <text x="0" y="286" font-size="10" font-family="monospace" fill="currentColor" opacity="0.6">注：RSS 之和大于 free 的 used，因为共享页在每个进程里都被重复计入</text>
</svg>
<figcaption>实测数据。真正值得注意的不是哪个进程最大，而是<strong>你的应用还没启动，系统底噪加数据库已经吃掉了大约 580 MB</strong>。</figcaption>
</figure>
<p>这里有个细节要说清楚，否则数字看着不自洽：<strong>上面各进程 RSS 加起来（约 691 MB）大于 <code>free</code> 报的 <code>used</code>（613 MB）</strong>，这不是我算错了。RSS 会把进程间共享的内存页（共享库、写时复制的父子页）在每个进程里各算一遍，所以 RSS 求和天然偏高。判断&quot;还剩多少能用&quot;要看 <code>available</code>，判断&quot;谁是大户&quot;才看 RSS。</p>
<p>从这张图能得出两个结论，它们直接决定选型：</p>
<ol>
<li><strong>系统底噪比应用大得多。</strong> journald 200 MB、两个云厂商 agent 合计 120 MB、rsyslogd 60 MB —— 这些你一行代码没写就已经存在了。</li>
<li><strong>可用余量只有 1.1 G，且没有 swap。</strong> 这意味着任何一次内存尖峰都直接触发 OOM killer，没有降级缓冲。</li>
</ol>
<h2 id="裸二进制方案的实际构成">裸二进制方案的实际构成</h2>
<p>new-api 是 Go 编译的单体，部署形态非常简单：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">systemd
</span></span><span class="line"><span class="cl">├── new-api            单二进制，Go 运行时
</span></span><span class="line"><span class="cl">├── mariadbd           本机数据库
</span></span><span class="line"><span class="cl">└── caddy / nginx      反向代理 + TLS
</span></span></code></pre></div><p>一个够用的 systemd unit：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">new-api</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target mariadb.service</span>
</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">simple</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">newapi</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/opt/new-api</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/opt/new-api/new-api --port 3000 --log-dir /var/log/new-api</span>
</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">always</span>
</span></span><span class="line"><span class="cl"><span class="na">RestartSec</span><span class="o">=</span><span class="s">5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 小内存机器上值得加的两道保险</span>
</span></span><span class="line"><span class="cl"><span class="na">MemoryMax</span><span class="o">=</span><span class="s">512M</span>
</span></span><span class="line"><span class="cl"><span class="na">OOMPolicy</span><span class="o">=</span><span class="s">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p><code>MemoryMax</code> 这行是小内存机器的关键：给应用设一个上限，让它在自己超标时被 cgroup 限制住，而不是把整机推向 OOM、让内核随机挑一个进程杀——被杀的往往是内存占用最大的 MariaDB，那就是数据库直接下线。</p>
<p><strong>代价要讲清楚</strong>，裸二进制不是没有成本的：</p>
<ul>
<li>升级是手动的：下载新二进制、<code>systemctl stop</code>、替换文件、<code>systemctl start</code>，回滚要自己留旧版本</li>
<li>没有进程隔离，应用和宿主机共享一切</li>
<li>依赖宿主机的 glibc 版本，跨发行版迁移可能编译不兼容（Go 静态编译能规避大部分，但 CGO 开启时不行）</li>
<li>数据库、缓存的版本管理全靠宿主机包管理器</li>
</ul>
<h2 id="docker-方案多出来的常驻开销">Docker 方案多出来的常驻开销</h2>
<figure>
<svg viewBox="0 0 760 340" role="img" aria-label="裸二进制与 Docker 两种部署方式的进程树对比：裸二进制下 systemd 直接管理 new-api、mariadbd 和反向代理；Docker 下额外增加 dockerd、containerd 以及每个容器的 shim 进程" style="width:100%;height:auto;color:currentColor">
  <text x="0" y="14" font-size="11" font-family="monospace" fill="currentColor" opacity="0.6">裸二进制</text>
  <text x="400" y="14" font-size="11" font-family="monospace" fill="currentColor" opacity="0.6">Docker</text>
  <line x1="378" y1="26" x2="378" y2="320" stroke="currentColor" stroke-width="1" opacity="0.25" stroke-dasharray="3 5"/>
  <!-- left -->
  <g font-size="11" font-family="monospace" fill="currentColor">
    <rect x="0" y="34" width="110" height="26" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="12" y="51">systemd</text>
    <path d="M 20 60 L 20 90 L 40 90" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="40" y="78" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="50" y="94">new-api</text>
    <text x="200" y="94" opacity="0.6">40–80 MB</text>
    <path d="M 20 90 L 20 128 L 40 128" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="40" y="116" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="50" y="132">mariadbd</text>
    <text x="200" y="132" opacity="0.6">285 MB 实测</text>
    <path d="M 20 128 L 20 166 L 40 166" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="40" y="154" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="50" y="170">caddy</text>
    <text x="200" y="170" opacity="0.6">10–25 MB</text>
  </g>
  <text x="0" y="228" font-size="11" font-family="monospace" fill="currentColor">额外常驻开销</text>
  <text x="0" y="250" font-size="15" font-family="monospace" fill="currentColor" font-weight="bold">0 MB</text>
  <!-- right -->
  <g font-size="11" font-family="monospace" fill="currentColor">
    <rect x="400" y="34" width="110" height="26" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="412" y="51">systemd</text>
    <path d="M 420 60 L 420 90 L 440 90" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="440" y="78" width="140" height="24" fill="currentColor" fill-opacity="0.1" stroke="currentColor" stroke-width="1.2"/>
    <text x="450" y="94">dockerd</text>
    <text x="590" y="94" opacity="0.6">80–150 MB</text>
    <path d="M 460 102 L 460 128 L 480 128" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="480" y="116" width="140" height="24" fill="currentColor" fill-opacity="0.1" stroke="currentColor" stroke-width="1.2"/>
    <text x="490" y="132">containerd</text>
    <text x="630" y="132" opacity="0.6">40–70 MB</text>
    <path d="M 500 140 L 500 166 L 520 166" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="520" y="154" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="530" y="170">shim → new-api</text>
    <path d="M 500 166 L 500 198 L 520 198" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="520" y="186" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="530" y="202">shim → postgres</text>
    <path d="M 500 198 L 500 230 L 520 230" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
    <rect x="520" y="218" width="150" height="24" fill="none" stroke="currentColor" stroke-width="1.2"/>
    <text x="530" y="234">shim → redis</text>
  </g>
  <text x="400" y="278" font-size="11" font-family="monospace" fill="currentColor">额外常驻开销</text>
  <text x="400" y="300" font-size="15" font-family="monospace" fill="currentColor" font-weight="bold">120–220 MB</text>
  <text x="400" y="322" font-size="10" font-family="monospace" fill="currentColor" opacity="0.6">灰底两项是应用之外白交的</text>
</svg>
<figcaption>右侧灰底的 <code>dockerd</code> 与 <code>containerd</code> 是纯粹的编排开销，跟你的业务一点关系没有。每个容器还各带一个 shim 进程。<strong>标注中只有 mariadbd 的 285 MB 是我实测值，其余是常见范围</strong>，具体数字随版本和配置浮动。</figcaption>
</figure>
<p>把这 120–220 MB 放回前面那张图里看：可用内存 1126 MB，Docker 一上来先拿走两成，剩下的才是留给 new-api、Postgres 和 Redis 的。在 4G 机器上这笔账无所谓；在 2G 机器上它就是压垮骆驼的那根稻草。</p>
<p>Docker 换来的东西也是真的，别因为省内存就否定它：</p>
<ul>
<li>升级是 <code>docker compose pull &amp;&amp; docker compose up -d</code>，回滚是改个 tag</li>
<li>环境完全一致，本地能跑线上就能跑</li>
<li>数据库、Redis 的版本与宿主机解耦，不用跟发行版的包管理器较劲</li>
<li>多实例、多环境并存很容易</li>
</ul>
<h2 id="决策建议">决策建议</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>建议</th>
					<th>理由</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>内存 ≥ 4G，机器专用</td>
					<td><strong>Docker</strong></td>
					<td>省心值这个钱，运维成本的节省远超内存代价</td>
			</tr>
			<tr>
					<td>2G，且已跑其他生产服务</td>
					<td><strong>裸二进制</strong></td>
					<td>编排开销挤占的是业务余量，这笔交换不划算</td>
			</tr>
			<tr>
					<td>2G，机器专用于 new-api</td>
					<td>都行，倾向 Docker</td>
					<td>没有邻居要抢内存，但务必先加 swap</td>
			</tr>
			<tr>
					<td>需要多实例 / 多环境并存</td>
					<td><strong>Docker</strong></td>
					<td>裸二进制做这件事会很痛苦</td>
			</tr>
	</tbody>
</table>
<h2 id="两个几乎所有人都忽略的内存大户">两个几乎所有人都忽略的内存大户</h2>
<h3 id="journald-默认没有上限">journald 默认没有上限</h3>
<p>前面实测里 <code>systemd-journald</code> 占了 200 MB，这不是异常，是默认行为——它会一直增长到磁盘配额上限。小内存机器上值得直接压掉：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/journald.conf</span>
</span></span><span class="line"><span class="cl"><span class="k">[Journal]</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemMaxUse</span><span class="o">=</span><span class="s">200M</span>
</span></span><span class="line"><span class="cl"><span class="na">RuntimeMaxUse</span><span class="o">=</span><span class="s">50M</span>
</span></span></code></pre></div><p>改完 <code>systemctl restart systemd-journald</code>。如果你的服务日志已经写到文件并由 logrotate 管，journald 里那份基本是重复的。</p>
<h3 id="云厂商-agent">云厂商 agent</h3>
<p>实测里 <code>YDService</code>（主机安全）加 <code>barad_agent</code>（监控）合计约 120 MB。这些是装系统镜像时预置的，你未必需要全部。<strong>要不要关取决于你的取舍</strong>：关掉监控 agent 会失去云控制台的监控图表，关掉安全 agent 会失去入侵检测告警。在 2G 机器上这 120 MB 确实值钱，但别为了省内存把安全能力一起关掉——如果你的机器上有数据库对外暴露，那点内存远不如告警重要。</p>
<h2 id="无-swap-是这类机器的隐藏地雷">无 swap 是这类机器的隐藏地雷</h2>
<p>前面实测数据里 <code>Swap: 0B</code>。云主机默认不带 swap，而 2G 机器最需要它——不是为了性能，是为了<strong>在内存尖峰时有个降级路径,而不是直接被 OOM killer 杀掉</strong>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 加 1G swapfile</span>
</span></span><span class="line"><span class="cl">fallocate -l 1G /swapfile
</span></span><span class="line"><span class="cl">chmod <span class="m">600</span> /swapfile
</span></span><span class="line"><span class="cl">mkswap /swapfile
</span></span><span class="line"><span class="cl">swapon /swapfile
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;/swapfile none swap sw 0 0&#39;</span> &gt;&gt; /etc/fstab
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 小内存机器上把 swappiness 调低，只在真的紧张时才用</span>
</span></span><span class="line"><span class="cl">sysctl -w vm.swappiness<span class="o">=</span><span class="m">10</span>
</span></span></code></pre></div><p>代价是数据库一旦被换出磁盘，性能会明显下降。但&quot;慢&quot;永远好过&quot;被杀&quot;。</p>
<h2 id="相关阅读">相关阅读</h2>
<p>部署完成后，反向代理的配置有两个坑几乎人人会踩，我单独写了：</p>
<ul>
<li><a href="/posts/llm-api-sse-proxy-buffering/">反代把大模型 API 的 SSE 流式响应缓冲了：五个缓冲点与三层排查法</a> —— 流式输出变成一次性返回，问题通常不在应用</li>
<li><a href="/posts/api-gateway-real-ip-ratelimit/">API 中转站的限流为什么不能信 X-Forwarded-For</a> —— 这个头客户端能自己塞，按它限流等于没限</li>
</ul>
<p>而本文里那个占 285 MB 的 MariaDB，本身也是个可以重新选的决定：</p>
<ul>
<li><a href="/posts/api-gateway-database-choice-small-vps/">给 API 中转站选数据库：SQLite / MySQL / PostgreSQL 在小机器上的取舍</a> —— 换成 SQLite 能把这 285 MB 整个省掉，代价是写并发上限</li>
</ul>
]]></content:encoded></item></channel></rss>