跳过正文

512MB 小内存云主机整机失联:自动更新触发 OOM,打死了网络栈

·4615 字·10 分钟
目录

一台 512MB 的 Ubuntu 云主机(跑 sing-box 代理)某天突然"整机失联":云控制台显示实例正在运行、静态 IP 没变,SSH、代理端口、云厂商的浏览器终端却全都不通,只有重启才恢复。一天之内,两台同配置的机器先后中招。根因不是网卡、不是运营商、也不是云厂商,而是无人值守自动更新(unattended-upgrades)把内存打满,全局 OOM 连带把负责网络配置的 systemd-networkd 拖进 Failed,默认路由与 DNS 随后消失——机器"活着但没网"。适用环境:Ubuntu 22.04 LTS(内核 6.8.0-1063-aws)、systemd-networkd、512MB 云主机,时间 2026-09-09 至 09-10。

一、症状与第一手数据
#

两台机在不到一天里先后病倒。本文主角(下称 A 机)于 2026-09-10 早晨 08:00(CST)前后失联;另一台同配置机器(下称 B 机)在约 19 小时前——09-09 06:18 UTC——以同样的链条先倒下,失联约 18 小时才被发现。

A 机失联时的观测数据:

  • 云控制台:实例状态"正在运行",静态 IP 未变;实例历史无任何停止/回收动作 → 云侧没动过它。
  • 外部探测:SSH(非标准端口)与代理入站端口全部 TCP 超时、无 SYN-ACK(不是拒绝),ICMP 不通。
  • 浏览器终端:报 UPSTREAM_ERROR [515]——云厂商会话管理 agent(SSM)连不上元数据服务 169.254.169.254,控制台通道本身也断了。
  • 自建监控 agent 主动出站上报心跳,最后一次心跳 23:55:24 UTC。入站与出站同时中断 → 故障在整机/网络层,不在 sing-box 进程层。
  • 对照:其余地域节点全部正常,云厂商状态页无对应事件 → 排除全局网络故障与区域事故。

排查期间还踩了个坑:本机经代理/TUN 测端口会"乐观握手"——本地应用立刻拿到 SYN-ACK,实际转发到上游才算成功,第一轮误报"已恢复"。改用两台异地干净观测点直连复测,才确认 09:13 控制台重启后 sing-box 09:13:52 自动拉起、监控 09:13:55 恢复上报,失联时长约 74 分钟。

重启后从前一次启动的 journal 里挖出完整的故障链日志:

# A 机(最后一段崩溃现场)
Sep 10 07:07:33 systemd-networkd[342]: ens5: Could not set route: Connection timed out
Sep 10 07:07:33 systemd-networkd[342]: ens5: Failed
Sep 10 07:08:13 kernel: oom-kill: ... global_oom, task_memcg=/system.slice/apt-daily-upgrade.service, task=unattended-upgr
Sep 10 07:08:13 kernel: Out of memory: Killed process 14467 (unattended-upgr) total-vm:469680kB, anon-rss:150328kB ... oom_score_adj:0
Sep 10 07:08:38 kernel: Out of memory: Killed process 14650 (unattended-upgr) anon-rss:163744kB
Sep 10 07:12:15 kernel: agent invoked oom-killer ... Out of memory: Killed process (unattended-upgr)
Sep 10 08:00:09 sing-box: ERROR router: missing default interface
Sep 10 08:00:23 agent: lookup <监控域名> on 127.0.0.53:53: server misbehaving
Sep 10 08:04:51 ssm-agent: 169.254.169.254 connect: network is unreachable
Sep 10 09:13:36 systemd[1]: systemd-networkd.service: Deactivated successfully.  ← 重启
# B 机(早 19 小时的同一套日志)
Sep 09 06:18:11 systemd-networkd[352]: eth0: Could not set route: Connection timed out
Sep 09 06:18:11 systemd-networkd[352]: eth0: Failed
Sep 09 06:18:11 kernel: Out of memory: Killed process 51149 (localedef) total-vm:127980kB, anon-rss:115752kB

两机同源:都是 512MB 内存、Swap: 0、日常 apt-daily-upgrade(unattended-upgrades)触发全局 OOM。B 机先倒、无人察觉;A 机随后复现,才把问题翻出来。

时间(UTC) 日志/事件 含义
06:52 apt-daily-upgrade.timer 启动 每日升级开始了
07:07:33 networkd Could not set route: Connection timed outFailed 内存耗尽、内核极端迟滞,设置 DHCP 路由超时,网络配置服务死亡
07:08:13 / 07:08:38 / 07:12:15 Out of memory: Killed process (unattended-upgr) 全局 OOM,三次杀掉升级进程;apt 被杀后又被拉起、再次被杀
08:00:09 sing-box missing default interface networkd 死后无人续租约,默认路由消失
08:00:23 监控 agent DNS 查询失败 DNS 失效
08:04:51 SSM agent 连元数据服务失败 控制台终端 515 的直接原因
09:13 控制台重启 sing-box 09:13:52 拉起、监控 09:13:55 恢复上报

二、根因
#

链条是这样走的:

  1. 内存被打满。512MB 实例、无 swap,日常占用已接近一半(journald 约 28MB、multipathd 约 27MB、sing-box 约 25MB、snapd 约 21MB,加 buff/cache 可用只剩约 200MB)。unattended-upgrades 升级时再吃 150MB+,内存耗尽。
  2. 负责网络的服务先被拖死。内存耗尽后内核迟滞,systemd-networkd 执行设置路由的操作超时,报 Could not set route: Connection timed out 后进入 Failed 状态——它一死,DHCP 租约不再续期。
  3. OOM killer 动手,但为时已晚。全局 OOM 触发,被杀的是 unattended-upgr(150MB)和 localedef(115MB)这类升级进程;此时 networkd 已死,路由/DNS 只是时间问题。约 53 分钟后默认路由消失(A 机 08:00:09),DNS 随后失效。
  4. 机器"活着但没网"。内核还在跑、进程还在、云面板显示 running,但没有任何网络路径:出站不行、入站更不行,SSH、代理、控制台终端全部不可达。没有 panic、没有 oops、连 hung task 都没有——journal 里除了内核那几行 OOM 和 networkd 两行 Failed,再无痕迹。常规搜 panic/error 什么也扫不出来。

两个值得注意的点。其一,OOM killer 实际杀掉的是升级进程,networkd 是"超时自杀";但加固前它的 OOMScoreAdjust=0Restart=on-failure,在全局 OOM 里一样是零保护的候选,这次没被挑中纯属运气——这决定了下面的加固方向。其二,这种故障不留明显崩溃痕迹,B 机失联 18 小时没有任何人发现,正是因为面板显示正常、日志"看起来很干净"。

三、处置过程(含走过的弯路)
#

弯路一:本机测端口误报恢复。 本机经 TUN/代理测目标端口,本地应用立刻拿到 SYN-ACK,第一轮三个端口全部"OPEN";干净外部源直连才暴露真相:全部超时。结论必须以非本机的观测点为准。

弯路二:自动重启被误关又恢复。 处置中一度把 50unattended-upgradesAutomatic-Reboottrue 改成 false,理由"去掉多余的重启"。随后确认一个名实问题:机器每周日凌晨的重启不是 cron——手动重启 cron 早在几天前加固时已删除(末次触发 09-06);真正在做凌晨自动重启的是 unattended-upgrades 的 Automatic-Reboot-Time "02:00"(另一台机 08-20、08-22 两次 02:00 重启即它所致)。而站主平时不会定期登录机器,若关掉这个开关,装了内核更新也永不生效——最终改回 true 并以 apt-config dump 验证生效。这里想对读者说明取舍:关掉 Automatic-Reboot 的代价,就是内核安全更新要等下次人工重启才生效;无定期登录习惯的机器不建议关。

上机复核:另一套并行加固已生效。 出手前先复核现状,发现当天早些时候已经有另一批加固配置落在两台机器上(不是本次操作加的):apt 服务的内存 cgroup 上限(MemoryMax=250M / MemoryHigh=200M)、networkd 的 OOMScoreAdjust=-1000 + Restart=alwaysmultipathd 停用、以及一个 net-watchdog 路由看门狗。逐项验证确实生效,且与本次新增的 swap 不冲突——fstab 里 /swapfile 只有一条、无重复挂载,findmnt --verify 通过。这套思路比"只加 swap"更对:卡住 OOM 源头(apt 内存升到 250M 就在自己的 cgroup 内被杀,整机不受影响),而不是等它打死网络栈再救。

本次补做的处置

  1. 2GB swapfilefallocate -l 2Gmkswapswapon,写进 /etc/fstab/swapfile none swap sw 0 0)。作用:吸收突发内存压力,让 OOM 尽量不发生。注意 swap 只是缓冲不是解药——OOM 一旦真的发生,netfilter 之外的任何进程都可能被随机挑中,网络栈仍可能中招。
  2. 清理纯代理机用不上的定时器:禁用 motd-news.timerupdate-notifier-download.timerupdate-notifier-motd.timer(每日拉资讯/弹更新通知,代理机上无用途,省网络与内存);保留 apt-dailyapt-daily-upgrade(安全更新)、fstrime2scrub_alllogrotatedpkg-db-backupnet-watchdog。顺手停用 multipathd(无多路径存储,白占约 27MB)。
  3. 补上离线告警:自建监控的离线告警此前未开,B 机失联 18 小时无人知晓。开启后同类失联约 3 分钟(grace 180 秒)内推送到消息通道。
  4. 规格决策:站主否决升配(加内存每月约 +2 美元,对纯代理机不划算),以"swap + 内存 cgroup 上限 + OOM 保护 + 路由看门狗"组合替代。

看门狗逻辑(每 2 分钟,OnBootSec=3minOnUnitActiveSec=2minAccuracySec=15s):

#!/bin/bash
LOG=/var/log/net-watchdog.log
if ! ip -4 route show default | grep -q .; then
  echo "$(date -Is) NO-DEFAULT-ROUTE -> restarting systemd-networkd" >> "$LOG"
  systemctl restart systemd-networkd
  sleep 10
  if ip -4 route show default | grep -q .; then
    echo "$(date -Is) recovered" >> "$LOG"
  else
    echo "$(date -Is) STILL-DOWN" >> "$LOG"
  fi
fi

它只治网络栈这一层,机器彻底卡死时它自己也跑不起来——所以它是兜底,不是主力;主力仍是掐住 OOM 源头。

四、验证
#

加固后用可观测指标逐项复核(两台机同批);另外注意 OOMScoreAdjust 对已运行的进程要下次启动才生效,核实取值即可,不必为此强行重启 networkd。

检查项 方法 结果
内存上限生效 systemctl show apt-daily-upgrade.service -p MemoryMax 262144000(250M)✅
networkd 保护 systemctl show systemd-networkd -p OOMScoreAdjust -p Restart -1000 / always
看门狗运行且未误触发 systemctl list-timers net-watchdog.timer/var/log/net-watchdog.log enabled + active,每 2 分钟一次;日志为空 = 默认路由从未丢过 ✅
swap 持久化 swapon --showgrep swap /etc/fstabfindmnt --verify /swapfile 2G 生效,fstab 单条目无重复,校验通过;已在吸收压力(一台 8MB、一台 35MB 在用)✅
服务端口 异地干净源 TCP 探测 + openssl s_client REALITY 握手 全通;TLS1.3、Verify return code: 0 (ok)
监控心跳 监控面板各节点最后上报 全部 5 分钟内(含故障过的两台)✅
自动重启配置 apt-config dump | grep Automatic-Reboot true,与站主决策一致 ✅
保留的定时器 systemctl list-timers apt-daily*/fstrim/logrotate/dpkg-db-backup/net-watchdog 全部 enabled ✅

实机后的观察:swap 建立后已开始承接压力(一台 8MB、一台 35MB),说明 512MB 上日常内存就是紧的。遗留的边界:apt 被限制在 250M 后,个别大升级可能因内存不足失败但机器没事——失败目前没有独立告警(监控只报在线/离线),需要的话可加一个"升级任务失败"探针。

五、留给后来者的清单
#

  • 第一步永远是看内核日志里有没有 OOMjournalctl -k --since "1 day ago" | grep -iE "Out of memory|Killed process"。整机"活着但没网"时,这是最快的定位动作。
  • 面板 Running + 静态 IP 未变 + 入站出站同时断(监控 agent 的主动上报也停)= 先怀疑整机资源/网络栈层,别急着找运营商。
  • 小内存机器给 apt 体系套内存 cgroup 上限:apt-dailyapt-daily-upgrade 各建 drop-in,MemoryMax=250MMemoryHigh=200M
  • 给网络服务保命:systemd-networkd drop-in 设 OOMScoreAdjust=-1000Restart=alwaysRestartSec=10s
  • 路由看门狗(上文的 shell 脚本)作为兜底;记住它只治网络栈,机器彻底卡死时无效。
  • 加 swap 只解决"尽量不发生 OOM",不解决"OOM 发生时谁被挑中";两者定位不同,别混为一谈。
  • 自动重启开关的取舍:无定期登录习惯就保留 Automatic-Reboot "true",否则内核更新永不生效。
  • 本机经代理/TUN 测端口会乐观握手,端口存活判断一律用异地干净源。
  • 这类故障不留崩溃痕迹,离线告警必须开——监控不告警等于没监控。

六、碎碎念
#

B 机先倒下 19 小时无人发现,A 机再倒才被重视——两台的差别只是先后,不是运气。回头想,“机器活着但没网"大概是运维里最容易被误判为运营商/云厂商问题的一类故障:面板一切正常,探测全部超时,谁都会先怀疑链路。实际上只要先花十秒钟看一眼内核日志里有没有 Out of memory,这整场排查就结束了。小内存机器上,自动更新这种"每天都在跑、从来不看结果"的任务,往往是隐藏的定时炸弹;给它套上 cgroup 上限、给网络服务戴上护具,再留一个看门狗兜底,这套组合拳对 512MB 级别的云主机都适用。