私有部署服务挂了没人管?最低成本告警与自愈实践

服务挂了几个小时才发现,是很多个人/小团队私有部署的常态。

尤其是学生项目、个人工具、内部系统,往往没有专人 7×24 盯着。挂了就挂了,直到用户(或者自己)来用才发现。

我给自己的私有服务做了一套最低成本的告警 + 自愈方案,核心原则就三条:

  1. 推送通道稳定可用(微信推送、飞书/钉钉、自建工具)
  2. 成本尽可能低(免费额度 + 现成组件,不堆 Prometheus/Grafana)
  3. 能自愈就先自愈,告警只是兜底

整套东西跑下来,每月几乎零额外费用,维护成本也很低。

整体思路

Uptime Kuma 监控仪表盘示例

简单来说分三层:

  • 健康检查:本地脚本 / systemd / Uptime Kuma
  • 自愈:失败就重启(带冷却,防止抖动)
  • 告警:微信(Server酱 / PushPlus)+ 飞书/钉钉机器人 + 邮件兜底

监控和被监控尽量不要完全跑在同一台机器上(否则整机挂了就一起静音)。如果只有一台机器,至少把「外部可达性检查」放在别处,或者用更轻量的本地方案。

1. 最简本地自愈:systemd + 健康检查脚本

很多服务本身用 systemd 管理就够了。

# /etc/systemd/system/my-app.service
[Unit]
Description=My Private App
After=network.target

[Service]
Type=simple
User=app
WorkingDirectory=/opt/my-app
ExecStart=/opt/my-app/bin/start.sh
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=60
StartLimitBurst=5

# 可选:资源限制,防止失控
# MemoryMax=512M
# CPUQuota=50%

[Install]
WantedBy=multi-user.target

Restart=on-failure + RestartSec 已经能解决大部分「进程直接挂掉」的情况。

如果服务是「假活」(进程在,但接口已经 5xx 或卡死),就需要主动健康检查。

写一个简单的检查脚本:

#!/bin/bash
# /opt/scripts/healthcheck-my-app.sh

URL="http://127.0.0.1:8080/health"
TIMEOUT=5
MAX_FAIL=3
STATE_FILE="/tmp/my-app-health.failcount"

fail_count=$(cat "$STATE_FILE" 2>/dev/null || echo 0)

if curl -sf --max-time $TIMEOUT "$URL" > /dev/null; then
    echo 0 > "$STATE_FILE"
    exit 0
fi

fail_count=$((fail_count + 1))
echo $fail_count > "$STATE_FILE"

if [ "$fail_count" -ge "$MAX_FAIL" ]; then
    echo "$(date) healthcheck failed $fail_count times, restarting..."
    systemctl restart my-app.service
    echo 0 > "$STATE_FILE"
    # 这里可以顺便发一次告警
    /opt/scripts/notify.sh "my-app 健康检查连续失败,已自动重启"
fi

用 systemd timer 定期跑:

# /etc/systemd/system/my-app-health.timer
[Unit]
Description=My App Health Check

[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
AccuracySec=10s

[Install]
WantedBy=timers.target

对应的 service 只负责执行脚本即可。

防抖很重要:连续失败 N 次才重启,并清零计数,避免短暂网络抖动导致反复重启。

2. Docker 场景

Docker 自带重启策略已经很好用:

services:
  my-app:
    image: my-app:latest
    restart: unless-stopped   # 或 always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 40s

配合 restart: unless-stopped,健康检查失败后 Docker 会自动重启容器。

如果想更主动一点,可以再加一个 sidecar 或外部脚本去 docker restart

3. 告警通道

微信推送(推荐)

目前最省事的两个:

示例(Server酱):

#!/bin/bash
# /opt/scripts/notify.sh
TITLE="$1"
CONTENT="${2:-}"

SENDKEY="你的SCT_KEY"
curl -s -X POST "https://sctapi.ftqq.com/${SENDKEY}.send" \
  -d "title=${TITLE}" \
  -d "desp=${CONTENT}"

飞书 / 钉钉机器人

免费、稳定、支持 Markdown,适合团队或自己多端接收。

飞书自定义机器人 webhook 直接 POST JSON 即可。

邮件兜底

用 163 / QQ 邮箱 + 授权码,走 SMTP,几乎永远能通。当作最后兜底。

4. 想更省心:上 Uptime Kuma

如果服务不止一两个,或者希望有漂亮的状态页和历史记录,直接上 Uptime Kuma

它是目前个人/小团队最推荐的自托管监控工具之一:

  • Docker 一行部署
  • 支持 HTTP、TCP、Ping、DNS、Docker 容器、关键字、证书过期等
  • 通知渠道非常多(包括 webhook,可对接 Server酱、飞书、钉钉)
  • 资源占用极低

Uptime Kuma 深色模式仪表盘

docker run -d \
  --restart=always \
  -p 3001:3001 \
  -v uptime-kuma:/app/data \
  --name uptime-kuma \
  louislam/uptime-kuma:1

关键建议:尽量不要和被监控的核心服务跑在同一台机器上。如果只有一台机器,至少把「公网可达性检查」放到另一台便宜的机器(或者朋友的机器)上。

Uptime Kuma 本身也支持通知到 Server酱 / 飞书等,配置一次就行。

5. 实际使用中的几个注意点

  1. 冷却与防抖
    不要「一失败就立刻重启 + 立刻告警」。连续失败、时间窗口、重启冷却都要有,否则半夜会被刷屏,或者服务陷入重启循环。

  2. 告警分级

    • 自动重启成功 → 发一条 info
    • 重启多次仍失败 → 发 critical
    • 磁盘/内存打满 → 单独告警
  3. 日志别丢
    自愈之后,最好把最近的日志截一段一起推过来,方便事后排查。

  4. 不要过度设计
    个人项目真的不需要完整 Prometheus + Grafana + Alertmanager 那一套。能用脚本 + 几个 webhook 解决的,就别上重型方案。

  5. 备份与配置也要管
    告警和自愈脚本本身也要纳入版本管理,或者至少定期备份。机器重装后能快速恢复。

成本与效果

我目前这套方案:

  • 额外费用:基本为 0(免费推送额度足够)
  • 维护时间:最初配置大概半天,之后几乎不用管
  • 效果:大部分「进程挂了 / 接口假死」的情况能在 1–2 分钟内自愈,并收到通知

当然它解决不了「整机断电、机房网络全挂、磁盘物理损坏」这种问题,那需要更高层级的容灾。但对个人私有部署来说,已经足够把「挂了几个小时才发现」变成「挂了马上知道,并且大概率自己起来了」。

总结

最低成本并不等于简陋。

用好 systemd 的重启策略、加一层轻量健康检查、对接稳定可用的推送通道,再视需要上一个 Uptime Kuma,就已经能覆盖绝大多数个人/小团队场景。

真正重要的不是工具有多花哨,而是:

服务挂了,你能不能在用户发现之前知道,并且尽量让它自己先起来。

如果你也有类似需求,可以从「给最核心的一个服务加 systemd Restart + 一个简单的健康检查脚本」开始,先跑起来再说。

有问题欢迎在评论区交流。