23:47,一个朋友发来消息:你的博客打不开了。
我当时已经准备睡觉。第一反应是“可能是他网络问题”,第二反应才是打开浏览器。页面转了很久,最后返回 502。那一刻我清醒了。
第一轮排查
登录服务器,先看服务状态:
1 | systemctl status axis-api |
axis-api 显示 failed,内存只剩几十兆,磁盘用了 92%。Nginx 的错误日志里是一串 connect() failed (111: Connection refused),说明请求到了 Nginx,但后端没起来。
我重启了服务,页面恢复。从收到消息到恢复,大概用了十二分钟。我给自己倒了杯水,觉得事情结束了。
四十分钟后,朋友又发来消息:又挂了。
第二轮:这次不能只重启
第二次登录,我没有直接重启,先看日志:
1 | journalctl -u axis-api --since "1 hour ago" --no-pager | tail -n 100 |
dmesg 里有明显的 OOM 记录:内核在内存耗尽时杀掉了 axis-api。日志还显示,这个服务在处理一类请求时内存持续上涨,之前几天也有缓慢增长的迹象,只是没人看。
磁盘 92% 也不是小事。日志文件没有轮转,/var/log 下面几个文件加起来占了 3G。磁盘接近满的时候,写入失败会引发一连串看起来毫不相关的错误。
那晚我做了三件事:
- 给服务加了内存上限,超过就重启,避免拖垮整台机器;
- 配了
logrotate,按天切分并保留 14 天; - 临时加了 2G swap,争取排查时间,但不把它当长期方案。
服务稳定下来已经是凌晨两点。真正让我睡不着的不是这次故障,而是另一个问题:如果朋友没有告诉我,我多久才会发现?
我发现自己没有“健康”的定义
第二天我列了一下当时拥有的东西:一个能打开的网页,一个 systemctl status,一份 Nginx 日志。就这些。
我没有可用性检测,没有内存和磁盘的趋势,没有证书到期提醒,也没有“服务刚重启过”的告警。站点对我来说只有两种状态:能打开、打不开。中间那些“正在变坏”的过程,我完全看不见。
这意味着我所谓的“稳定”,其实是运气。只要没人告诉我,故障可以持续一整晚。
补上的东西
接下来一周,我补的东西都很基础:
- 可用性检测:每五分钟请求一次首页和接口,连续两次失败才告警,避免网络抖动误报;
- 资源告警:内存超过 80%、磁盘超过 85% 时通知;
- 日志轮转:所有自建服务的日志都纳入
logrotate; - 重启记录:服务重启时发一条消息,哪怕是在凌晨;
- 证书到期提醒:提前三十天提醒,不依赖记忆;
- 一页故障手册:写清楚先看什么、再做什么、什么情况下回滚。
这些东西加起来不到一个周末,但它们把“站点是否健康”从感觉变成了可以回答的问题。
稳定性的三个词
这次之后,我对稳定性的理解变成了三个词。
可观测。 不是装一个漂亮的监控面板,而是能在故障发生时回答:什么时候开始的、影响了什么、现在是什么状态。没有数据的“我觉得没事”不算可观测。
可恢复。 服务挂了能不能自动或手动拉起来,数据丢了能不能恢复,配置改错了能不能退回上一版。恢复时间比“不出故障”更现实,因为故障一定会发生。
变更纪律。 那次内存泄漏不是一夜之间出现的,是某次改动之后慢慢累积的。如果每次变更都记录改了什么、怎么验证、怎么回滚,问题会早很多暴露。
最后
我不觉得这次故障处理得多好。第一次重启只是碰巧有效,第二次才找到原因;如果没有朋友提醒,后果可能更糟。
但它确实改变了我做站点的方式。以前我更关心“怎么把功能做出来”,现在会先问“如果它坏了,我怎么知道、怎么修、怎么退”。这两个问题的答案,比多装一个服务重要得多。