HanyanOS 运维手记

1. 引言

当你的服务器只有 11GB 内存和 4 个 E-core,Prometheus + Grafana + Loki + AlertManager 这套标准观测栈在第一分钟就能吃掉 2GB。对于一个运行着邮件系统、反向代理、AI agent 网关、Docker 工作负载和定时任务的 N100 来说,这不是选择问题——是没有选择。

但这不意味着放弃监控。恰恰相反——资源受限反而迫使你思考一个更本质的问题:你真正需要看到什么?

这篇文章记录 HanyanOS 在 N100 上的轻量级监控方案:不装任何额外 daemon,不用 SaaS,不加学习曲线。只有 systemdjournalctlhtop、几行 shell 脚本和一个 Telegram 通知。成本为零,效果够用。

2. 监控哲学:三层告警

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────┐
│ 实时仪表盘(人主动看) │
│ htop / journalctl -f / pm2 monit │
│ 用途:debug 时打开,日常不看 │
├─────────────────────────────────────────────────────┤
│ 阈值告警(守护进程被动推) │
│ 温度 >70°C → 通知 │ CPU >80% 持续5分钟 → 通知 │
│ 内存 <15% → 通知 │ 关键进程挂 → 通知 │
│ 用途:出了问题第一时间知道 │
├─────────────────────────────────────────────────────┤
│ 健康巡检(定时跑) │
│ 每15分钟:端口可达性、进程活着、磁盘余量 │
│ 每日汇总:日志异常摘要、资源趋势 │
│ 用途:在用户发现之前发现问题 │
└─────────────────────────────────────────────────────┘

3. 实时仪表盘:知道该查什么

htop — 资源总管

N100 上的 htop 配置去掉了所有花哨的进度条,只留四行:CPU(4核)、内存、Swap、已运行时间。开 Tree view 能一眼看到哪个进程在吃资源,哪条 systemd 单元的子进程炸了。

pm2 进程的 CPU 占用率突然跳到 90%+ 且持续不回落——那就是 ai-portal 后端进入了无限循环(亲身经历,本篇后续会复盘)。

1
2
# 一行看关键指标
alias health='echo "🔸 CPU: $(top -bn1 | grep Cpu | awk "{print \$2}")% | MEM: $(free -m | awk '/^Mem:/{printf "%.0f%%", $3/$2*100}') | SWAP: $(free -m | awk '/^Swap:/{print $3}")M | T: $(sensors | grep Package | awk "{print \$4}")"'

journalctl — 事故日志的入口

systemd 的统一日志系统远比零散的 /var/log/* 可读。日常三句最常用:

1
2
3
4
5
6
7
8
# 看最近一次服务崩溃的上下文
journalctl -u openclaw-gateway -n 50 --no-pager

# 看过去5分钟的告警
journalctl --since "5 min ago" -p warning

# 跟踪特定单元的实时日志(debug 时一直开着)
journalctl -u nginx -f

当 FRP 隧道中断导致邮件服务不可达时,nginx 的错误日志里会出现成片的 502 Bad Gateway —— journalctl 配合 -p err 能在 3 秒内定位。

pm2 monit — Node 进程的专属监护

NodeJS 应用在 N100 上由 pm2 管理。pm2 monit 提供跟 htop 差不多但只针对 pm2 进程的视图:

1
2
● ai-portal       [fork_mode]  MEM: 245.3 MB     CPU: 2.5%
● hanyan-bridge [fork_mode] MEM: 183.1 MB CPU: 3.1%

这比 htop 有用——它会显示 restart count。当你看到 count 超过 10 且还在跳,说明进程在反复 crash-loop,根因多半是内存不足被 OOM killer 干掉了。

4. 阈值告警:让机器喊你

温度监控 — N100 最需要关心的事

N100 的被动散热设计意味着它对持续负载非常敏感。一个 Docker 容器跑编译,或者 FRP 隧道长时间高带宽,都能把温度从 44°C 推上 70°C+。

风扇脚本是 HanyanOS 最重要的自有服务。每次迭代的版本号体现了这个事情有多折腾——光是 2026-06-04 这一天就迭代到了 v6,因为边界条件翻车了两次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# fan-control v6 — 主动风扇控制,曲线激进
TEMP=$(sensors | grep Package | awk '{print $4}' | tr -d '+°C')

if [ "$TEMP" -ge 72 ]; then
# 危险区间:全速散热
echo 200 | sudo tee /sys/class/hwmon/hwmon1/pwm1
elif [ "$TEMP" -ge 65 ]; then
# 警告区间:提速
echo 150 | sudo tee /sys/class/hwmon/hwmon1/pwm1
elif [ "$TEMP" -ge 55 ]; then
# 温和上升
echo 100 | sudo tee /sys/class/hwmon/hwmon1/pwm1
else
# 静默运行
echo 80 | sudo tee /sys/class/hwmon/hwmon1/pwm1
fi

系统级的温度蜂巢通知通过 cron 每 5 分钟跑一个检测脚本实现:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# temp_alert.sh
TEMP=$(sensors | grep Package | awk '{print $4}' | tr -d '+°C')
LIMIT=75

if [ $(echo "$TEMP > $LIMIT" | bc) -eq 1 ]; then
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID" \
-d "text=🚨 N100 温度告警:${TEMP}°C(阈值 ${LIMIT}°C)"
fi

CPU 雪崩 — 切身的教训(2026-06-04 真实事故)

这是今天真正发生的事。下午发现 N100 CPU 占用率飙到 90%+,htop 显示 node /home/michael/www/ai.chenyun.org/server.js 占掉了 3.8 个核。

排查路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
htop → 发现 node 进程 90%+

pm2 list → 确认是 ai-portal

pm2 logs ai-portal --lines 50 → 看到无限循环的 API 请求日志

|── 客户端不断轮询 /api 端点
│ ↓ 每次超时(因为 server 过载)→ 重试
│ ↓ 重试请求堆积 → 更慢
│ ↓ 死循环

journalctl -u nginx -p err --since "1 hour ago"
→ 成片的 upstream timed out

解决方案简单粗暴:

1
2
pm2 stop ai-portal     # 立即止血
# CPU 从 90% 降到了 5.4%

后续根治需要加限流和熔断,那是漫歌的事,但监控在这里的价值是:第一时间发现,而不是等用户反馈说网站打不开

如果在生产环境考虑自愈,也可以加一个 watchdog:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# cpu_watchdog.sh — 5分钟运行一次
LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs)
LIMIT=6.0

if [ $(echo "$LOAD > $LIMIT" | bc) -eq 1 ]; then
# 找最吃 CPU 的进程
PID=$(ps aux --sort=-%cpu | awk 'NR==2{print $2}')
NAME=$(ps aux --sort=-%cpu | awk 'NR==2{print $11}')
MEM=$(ps aux --sort=-%cpu | awk 'NR==2{print $6}')
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID" \
-d "text=🚨 CPU 过载告警\n负载: ${LOAD} (阈值 ${LIMIT})\n最重进程: ${NAME} (PID ${PID}, MEM ${MEM}KB)\n建议: pm2 restart ${NAME}"
fi

5. 健康巡检:自动化日报

cron 每 15 分钟跑一次端口扫描 + 进程存活检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
# healthcheck.sh — 每15分钟由 cron 触发

# 关键端口检测
PORTS=(22 80 443 465 587 993 18789)
for PORT in "${PORTS[@]}"; do
if ! ss -tlnp | grep -q ":$PORT "; then
echo "🚫 端口 ${PORT} 异常" >> /tmp/healthcheck.log
fi
done

# 关键进程检测
PROCS=("nginx" "frpc" "stalwart" "openclaw")
for PROC in "${PROCS[@]}"; do
if ! pgrep -x "$PROC" > /dev/null; then
echo "🚫 进程 ${PROC} 挂了" >> /tmp/healthcheck.log
fi
done

# 磁盘余量
DISK=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
if [ "$DISK" -gt 85 ]; then
echo "🚫 磁盘使用 ${DISK}%" >> /tmp/healthcheck.log
fi

# 若有异常则通知
if [ -s /tmp/healthcheck.log ]; then
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID" \
-d "text=⚠️ N100 健康巡检异常\n$(cat /tmp/healthcheck.log)"
> /tmp/healthcheck.log
fi

每日凌晨汇总日志异常:

1
2
3
4
5
6
7
# daily_summary.sh — cron 每日 06:00
echo "=== 昨日日志异常摘要 ===" >> /tmp/daily-report
journalctl --since "24 hours ago" -p err >> /tmp/daily-report
echo "=== 磁盘使用率 ===" >> /tmp/daily-report
df -h / >> /tmp/daily-report
echo "=== 温度记录 ===" >> /tmp/daily-report
sensors >> /tmp/daily-report

6. N100 监控资源配置一览

组件 内存占用量 CPU 占用量 备注
htop ~4MB ~0.1% 仅交互时运行
journalctl 2-5MB (daemon) ~0.3% systemd 自带
风扇脚本 (cron) ~1MB ~0.1% 每5秒跑一次
健康巡检 (cron) ~1MB ~0.1% 每15分钟跑一次
CPU watchdog ~0.5MB ~0.05% 每5分钟跑
总量 ~8-12MB <1% vs Prometheus 栈 ~1.5-2GB

7. 权衡与取舍

这套方案不做什么

  • 不做历史趋势图 —— 没有 Grafana 仪表盘,你无法回看一周前的内存曲线。但你在 N100 上也不需要——你只需要知道”现在”和”刚发生了什么”
  • 不做分布式追踪 —— 单一服务器,没有微服务链路需要跟踪
  • 不整合告警平台 —— Telegram Bot 足够。PagerDuty 能省的都省了

什么时候该上重型方案

当 N100 上运行的工作负载膨胀到以下程度之一时,值得重新考虑:

  1. 超过 10 个独立服务需要监控
  2. 有多台服务器(加上 Lightsail VPS)
  3. 有用户可见的 SLA 需要保障

但在那之前——三行 htop 配置 + 一个 Telegram Bot + 一个 cron 脚本组成的监控系统,11MB 内存,零学习成本,效果够用。这就够了。


运维手记系列预告:

  • #2: Docker 容器生命周期管理——拉取、更新、回滚、清理
  • #3: 备份恢复管线——restic + cron 冷热分离
  • #4: 日志管理——旋转、留存、审计
  • #5: 性能调优——11GB 上的内存生存艺术