2026-08-03
技术
0

目录

服务器假死事件 —— 完整排查报告
一、事件概述
二、排查过程
阶段 1:恢复访问,建立基线
阶段 2:第一假设——外部攻击(DDoS/CC)
阶段 3:第二假设——阿里云备份客户端(hbrclient)全盘扫描
阶段 4:系统日志时间线拼接
阶段 5:第三假设——MongoDB 异常或 ISR 渲染
阶段 6:内存诊断
阶段 7:发现关键线索——swappiness
三、完整事件时间线
四、根因总结
直接触发
根本原因链
为什么 Swap 2G 完全没用
为什么所有 log 在 13:29:43 停止
为什么 SSH 能建立 TCP 连接但无法登录
为什么监控还能上报
五、排除的假设
六、修复措施
已执行
建议
七、经验教训
八、关键日志文件索引

服务器假死事件 —— 完整排查报告

服务器:阿里云 ECS (99元/年档)
配置:2 vCPU / 2G 内存(实际可用1.6G) / 40G 磁盘 / 2G Swap
系统:Alinux 4 (Linux 6.6.102-5.3.4.alnx4)
部署服务:VanBlog (Docker: Node/Next.js + MongoDB + Caddy + Waline 评论系统)
域名www.yan7.cn / yan7.cn
事件时间:2026-08-02 13:29 ~ 15:00 CST
排查日期:2026-08-02
排查工具:Claude Code + DeepSeek


一、事件概述

2026年8月2日下午,部署仅一天的 VanBlog 个人博客突然无法访问。SSH 连接卡在验证阶段无法登录(可以建立 TCP 连接但无法进入 shell),但阿里云控制台显示服务器仍在"运行中",各项监控指标(CPU、磁盘 I/O、连接数)持续更新。

用户在安全组中屏蔽所有公网 IP 后——服务器依旧没有恢复。最终通过阿里云控制台强制重启解决问题。

最终认为的根因:阿里云 Alinux 4 镜像默认配置 vm.swappiness=0,禁止内核使用 swap。在物理内存不足的情况下,dnf makecache 触发内存分配请求,内核被迫丢弃活跃进程的文件缓存页来腾空间,被丢弃的页面又立刻被进程需要而读回磁盘,形成"丢页 → 缺页中断 → 读盘 → 占内存 → 再丢页"的死循环(Thrashing),磁盘 I/O 被占满,所有需要文件访问或新内存分配的进程全部卡死。


二、排查过程

阶段 1:恢复访问,建立基线

服务器卡死无法 SSH,在控制台强制重启后重新登录。首先获取当前系统状态:

bash
uptime # 确认刚重启(5分钟),无异常负载 free -h # 1.6G内存/997M已用/74M空闲,Swap 2G/0B使用 df -h # 40G磁盘/15G已用/39%,空间正常

当前状态一切正常——这排除了磁盘满、硬件故障等持久性问题。问题必定和重启前运行的某个进程或状态有关。

bash
ps aux --sort=-%cpu | head -20 # 无异常高CPU进程 ps aux --sort=-%mem | head -15 # Mongo 7%, Node 7%, Waline 7% 正常

阶段 2:第一假设——外部攻击(DDoS/CC)

为什么这样想:阿里云后台监控显示连接数 20+,磁盘读取 100MB/s+、850次/秒。这个量级对个人博客来说极不正常。AI 助手也判断可能是攻击。

验证方法

bash
# 查看监听端口和当前连接——有没有异常监听、大量外部连接 ss -tlnp # 仅 22/80/443/***(SSH),无不认识端口 ss -tn state established # 当前仅8个连接,包括Claude会话和阿里云监控 # 检查当前网络流量 iostat -x 1 2 # vda盘 97r/s, 3.1MB/s(刚重启,正常回落)

验证 SSH 安全

bash
# 尝试读取secure日志 zcat /var/log/secure-20260802.zst # 权限不足(root文件) sudo zgrep -cE "Failed password|Invalid user" /var/log/secure* # 结果:仅 1 条失败记录(整个生命周期)

验证结果不是攻击的排查

bash
sudo tail -300 /home/alibaba/blog/vanblog/log/vanblog-access.log

访问日志分析:

  • 所有请求都是正常的页面访问+搜索引擎爬虫(ClaudeBot、bingbot、GPTBot)+常规漏洞扫描器探测(/.env/.git/config/wp-login.php,全部返回404)
  • 无高并发特征:请求间隔以秒到分钟计,同 IP 无密集访问
  • 所有正常请求均命中 CDN 缓存(X-Nextjs-Cache: HIT),响应时间在 5-30ms
  • 最后一条访问日志是 13:17:54(卡死前12分钟),此后完全没有新的访问记录

结论不是攻击。公网带宽经阿里云后台确认只有约 100kbps,不符合 DDoS 特征。访问日志是典型的个人博客流量模式,没有任何恶意流量模式。关键证据:屏蔽所有公网 IP 后服务器依旧没恢复——如果攻击流量来自公网,安全组拦截是即时生效的,不恢复反向证明负载来源不是外部入站流量。


阶段 3:第二假设——阿里云备份客户端(hbrclient)全盘扫描

为什么这样想ps aux 看到 /opt/alibabacloud/hbrclient/client/hbrclient run 正在运行。磁盘 I/O 异常高(850次/秒读、100MB/s),很像全盘扫描特征。屏蔽公网 IP 无效暗示负载来源在服务器内部或内网。

验证方法

bash
# 查看 hbrclient 是什么服务 systemctl list-units --no-pager | grep -iE "hbr|backup|hybrid" # → hbrclient.service (Alibaba Cloud Backup Service) # → hbrclientupdater.service # 查看备份日志 ls -lhS /opt/alibabacloud/hbrclient/logs/ # → backup_plan-*.log 1.8MB(备份计划日志) # → diagnosis.log 1.5MB(诊断日志,最后写入 13:12) # → hbrclient.log 2.1MB(客户端主日志)

查看备份计划详情

bash
sudo head -50 /opt/alibabacloud/hbrclient/logs/backup_plan-*.log

关键发现:

backup "/" # 备份整个根目录 --speedlimiter 0:24:10240 # 白天上传限速 10MB/s --scanspeedlimiter 0:24:1000 # 白天扫描 1000 文件/秒 --exclude /bin/ --exclude /boot/ ... # 排除系统目录但扫描范围仍很大 file_cache_max_size_hint=34359738368 # 文件缓存提示值 32GB(机器只有1.6G)

hbrclient 是新版 ECS 文件备份基础版(使用的是低优先级备份模式:最大占用不超过10MB/s、1vCPU,为增量备份)。备份走阿里云内网上传,不走公网流量。

查看备份实际结束时间

bash
sudo grep -iE "snapshot|completed|done" backup_plan-*.log | tail -20

结果:

2026-08-02T02:29:27 - INCR backup completed successfully 2026-08-02T02:29:27 - Save snapshot successful 2026-08-02T02:29:28 - stopped!

部分排除。8月2日凌晨的增量备份在 02:29 就正常完成并退出了,距 13:29 的崩溃足足 11 小时。备份本身不是直接元凶。备份完成时的系统内存状态是健康的:

memory usage at the end: available=577974272 (551MB), usedPercent=58.9% swap: used=0

但在后面的排查中发现,hbrclient 只是碰巧在那段时间完成备份,而真正的杀手是 swappiness=0 参数——两者独立存在。


阶段 4:系统日志时间线拼接

bash
# 查看上一轮启动(预重启)的系统日志 journalctl -b -1 --no-pager

从系统 journal 拼接出的时间线:

13:01 - cron.hourly 正常执行(0anacron) 13:29:39 - dnf-makecache.service 开始 13:29:40-42 - 陆续下载 alinux4-os/updates/plus 元数据(几KB小文件) 13:29:42 - EPEL 仓库元数据开始下载 13:29:43 - EPEL 下载中,21 MB/s, 21 MB ← 最后一条日志 ↓ 之后 journal 完全停止——系统进入假死 此时上阿里云后台监控,显示服务器正常运行,除了磁盘的读取保持一百多MB/s、850次/s,其余指标一切正常。服务器保持此状态继续运行了一个多小时,直到15:00左右在阿里云控制台进行重启。

系统日志中没有 OOM Kill、没有 kernel panic、没有 I/O error、没有 hung task 告警。这些通常都被 journald 记录下来,但它们全部缺席。这正是 Thrashing 的典型特征:不是某条命令卡死,而是所有涉及磁盘 I/O 的操作全部无限期排队。


阶段 5:第三假设——MongoDB 异常或 ISR 渲染

为什么这样想:VanBlog 的 ISR (Incremental Static Regeneration) 每小时触发一次全量渲染(读取所有文章数据、生成静态页面)。如果 ISR 和 dnf makecache 叠加,可能导致内存和 I/O 阶段性峰值。

验证方法

bash
sudo docker logs vanblog-mongo-1 --since "2026-08-02T12:00" --until "2026-08-02T15:00" 2>&1 | tail -80 sudo docker logs vanblog-vanblog-1 --since "2026-08-02T12:00" --until "2026-08-02T15:00" 2>&1 | tail -80

MongoDB 日志:每分钟一条 WiredTiger checkpoint 日志,完全正常。从 12:00 延续到 13:29:43,之后停止——时间线和 journalctl 完全吻合。

VanBlog 日志

13:00:01 - ISR 全量渲染完成 13:01:01 - SiteMap 重新生成 13:03:01 - RSS 重新生成

ISR 在 13:00 就完成了,距 13:29 有半小时。之后只有零星的 CaddyController 日志(拦截了通过 IP 直连的 HTTPS 请求),最后一次记录也在 13:29:43 附近停止。无异常错误,无无限循环。


阶段 6:内存诊断

hbrclient 诊断日志 diagnosis.log 每小时采样一次,数据可追溯到 7月14日:

2026-07-14T01:56:30 MEM: [Total: 1.6G, Available: 134M, Used: 83.94%, Swap: 0%] TopTen: java=71%, hbrclient=2%, AliYunDunMonitor=1.1%, ... 2026-07-14 ~ 08-02 所有采样点: 内存使用率 83-85%,Swap 使用率始终 0% 可用内存长期在 120-150MB

阶段 7:发现关键线索——swappiness

bash
cat /proc/sys/vm/swappiness # 输出:0

swappiness=0 的含义:告诉内核"绝对不要使用 swap,优先丢弃文件缓存页来腾内存"。

这个值对桌面或低延迟服务或许合理,但对运行 Docker+MongoDB 的内存机器来说是灾难性的,因为:

  1. dummy cache 就是这些进程的命:MongoDB 的 WiredTiger 缓存、Node 的 V8 堆+代码段,都依赖文件缓存
  2. 当 dnf 需要内存时,内核执行 "丢弃文件缓存 → dnf 得到内存"
  3. 但 MongoDB/Node 的内存页也是文件缓存——被一同丢弃
  4. 丢弃的瞬间,那些进程的代码和数据就需要被重新读入内存
  5. 重新读入又需要内存,又触发新一轮丢弃……死循环

为什么 Swap 始终 0%:swappiness=0 让内核无论内存多紧张都不敢用 swap。2G swap 全程闲置。

bash
grep -r "swappiness" /etc/sysctl.conf /etc/sysctl.d/ /usr/lib/sysctl.d/ 2>/dev/null # → /etc/sysctl.d/50-aliyun.conf:vm.swappiness = 0 # → /etc/sysctl.d/99-apsara-sysctl.conf:vm.swappiness = 0

这是阿里云 Alinux 4 镜像的默认配置,两个系统文件都写死了 swappiness=0。两个文件可能是不同组件安装时分别生成的重复配置。


三、完整事件时间线

时间事件
07-13 15:34服务器启动,运行 3 周。
07-14 00:56ECS 文件备份基础版首次全量备份(64832文件/8.75GB),00:57 扫描完成(1116文件/秒),01:34 完成
08-01 16:11重启服务器。部署 VanBlog(Docker: VanBlog+MongoDB+Caddy+Waline×2)
08-01 20:00博客正常。VanBlog 有少量应用层 bug(sitemap URL、articles.map),不影响功能
08-02 00:56备份增量任务开始(331,332 文件)
08-02 02:29:27备份正常完成,输出 INCR backup completed successfully。完成后内存使用率 58.9%,系统健康
08-02 02:29:28hbrclient 备份进程完全退出
08-02 13:00:01VanBlog ISR 全量渲染完成
08-02 13:01:01cron.hourly 正常执行
08-02 13:12hbrclient 诊断日志最后一次写入
08-02 13:17:54博客最后一条访问日志(ClaudeBot)
08-02 13:29:39dnf-makecache.service 开始。内存压力已在临界点(重启后经半天的容器运行恢复到约 83%)
08-02 13:29:43dnf 下载 21MB EPEL 元数据。download 成功但在解析元数据时需要分配内存。内核因 swappiness=0 不能用 swap,丢弃活跃的文件缓存页来腾空间。被丢弃的页面立刻被 MongoDB/Node 需要而回读磁盘——Thrashing 开始
13:29~15:00死循环持续约 90 分钟:磁盘 100MB/s 读、850 IOPS。用户级进程全部 I/O 阻塞,SSH 能建 TCP 连接但无法完成认证。阿里云监控持续上报但服务器无响应。屏蔽全部公网 IP 无效——这不是外部流量问题
~15:00用户通过阿里云控制台强制重启
15:07服务器完成重启,所有服务自动恢复正常。hbrclient 随系统自启

四、根因总结

直接触发

dnf makecache 在 13:29 分配内存,在 vm.swappiness=0 约束下触发 Thrashing。

根本原因链

内核参数 vm.swappiness=0(阿里云 Alinux 4 镜像默认) × 1.6G 物理内存(跑 Docker + MongoDB + Node/VanBlog + Waline×2 + Caddy + aliyun-service + AliYunDun + AliHips + hbrclient 驻留) × dnf makecache 每小时自动执行(任何需要新内存分配的操作都是触发源) = 一旦触发 → 内核只能用丢文件缓存来腾内存 → 丢的是 MongoDB/Node 的热页 → 进程立刻缺页中断 → 读磁盘 → 读回来又占满内存 → 又丢 → 死循环

为什么 Swap 2G 完全没用

swappiness=0 不是 "优先用物理内存",而是 "禁止使用 swap"。2G swap 分配了但内核不会往里面写任何东西。如果能用上 swap:dnf 需要内存 → 内核把 MongoDB 等进程的一些不活跃匿名页换到 swap → 腾出物理内存给 dnf → dnf 结束后那些页再换回来 → 整个过程有开销但不会导致所有文件缓存被清空的死循环。

为什么所有 log 在 13:29:43 停止

journald 写日志本身也需要文件系统操作(追加写入 /var/log/journal/),文件系统操作需要 I/O,而 I/O 队列已被 Thrashing 产生的读请求爆满,所有非 Thrashing 参与的写操作都在排队的最后面——永远等不到。

为什么 SSH 能建立 TCP 连接但无法登录

TCP 三次握手在内核网络栈完成,不需要磁盘 I/O。sshd fork() 子进程、读取 /etc/passwd~/.ssh/authorized_keys、启动 shell 全部需要磁盘 I/O 或新内存分配 → 卡在队列里。

为什么监控还能上报

阿里云监控 agent 的代码已在物理内存中、不会被换出(它是内核线程),走内核网络栈直接发送——全程不需要新的磁盘 I/O 或用户态内存分配。


五、排除的假设

假设排除依据排除时的命令/日志
DDoS/CC 攻击公网带宽 100kbps、访问日志流量正常、屏蔽公网 IP 无效tail -300 vanblog-access.log、阿里云监控带宽图
SSH 暴力破解secure 日志仅 1 条失败记录sudo zgrep "Failed password" /var/log/secure*
恶意持久化/入侵无异常 crontab、无异常 systemd 服务、SSH 密钥均为合法crontab -lsudo cat /root/.ssh/authorized_keyssystemctl list-unit-files
hbrclient 全盘备份备份任务在 02:29 正常完成退出,距崩溃约 11 小时sudo grep "completed" backup_plan-*.log
MongoDB 异常操作日志全部为正常 WiredTiger checkpoint,无慢查询或异常sudo docker logs vanblog-mongo-1
VanBlog ISR 渲染循环ISR 在 13:00 完成,距崩溃 30 分钟,无异常日志sudo docker logs vanblog-vanblog-1
磁盘空间不足磁盘使用率 39%df -h
OOM Killerjournal 无 OOM kill 记录journalctl -b -1 | grep -i oom
内核 Panic监控始终上报,无 kernel panic 日志journalctl -b -1 | grep -i panic

六、修复措施

已执行

  1. 将 swappiness 从 0 改为 10:

    bash
    echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf sudo sysctl -w vm.swappiness=10
  2. 保留了 ECS 文件备份基础版(增量、低优先级、凌晨窗口,设计合理,与本次事件无关)

建议

  1. [P1] 给 MongoDB 加内存限制

    yaml
    # docker-compose.yaml mongo: command: --wiredTigerCacheSizeGB 0.15 mem_limit: 192M
  2. [P1] 给 VanBlog 容器加内存上限:同文件添加 mem_limit: 384M

  3. [P2] 关闭 22 端口:安全组删除 22 入站规则,仅保留 ***

  4. [P3] 验证 swappiness 改动已生效

    bash
    sysctl vm.swappiness # 应输出 vm.swappiness = 10

七、经验教训

  1. vm.swappiness=0 在生产环境中是危险的:它看起来像"优先用物理内存",但实际效果是"内存紧张时优先把文件缓存全部丢弃"。对数据库和 Node.js 应用,文件缓存就是它们的运行时内存。阿里云镜像默认设 0 在内存充裕的机器上没问题,在 1.6G 小机器上是定时炸弹。

  2. 排查系统假死要从内存+内核参数入手:假死(无崩溃日志、但不可交互)不是某个进程卡死,而是所有用户态进程被 I/O 饿死。常见的原因就是 swap/缓存配置不当导致的 Thrashing。

  3. Swap 不是"内存不够用的补丁",是"内存压力时的安全阀":swappiness=0 不代表"我不需要 swap",而是"把 swap 的安全阀关掉"。在小内存机器上,swap 是防止 Thrashing 的最后一道防线。

  4. 阿里云控制台的监控数据是重要的排查线索:磁盘 100MB/s 读 + 低公网带宽 = 负载来自内部,不是外部攻击。这个规律可以推广。

  5. 排除法效率高:对"攻击"、"备份扫描"、"数据库异常"逐个用日志验证或排除。最危险的是在排除假设备份后就停止——真正的根因在更底层的 swappiness 参数上。


八、关键日志文件索引

文件用途关键内容
journalctl -b -1上一轮启动的系统日志最后一条记录:13:29:43 dnf 下载 EPEL 元数据
/proc/sys/vm/swappiness当前 swappiness 值0
/etc/sysctl.d/50-aliyun.conf阿里云 sysctl 配置vm.swappiness = 0
/etc/sysctl.d/99-apsara-sysctl.conf阿里云 apsara sysctl 配置vm.swappiness = 0
/opt/.../hbrclient/logs/backup_plan-*.log备份计划日志备份 02:29 正常结束,与事件无关
/opt/.../hbrclient/logs/diagnosis.log备份诊断日志系统长期 83-85% 内存占用。备份完成后 58.9% 健康。Swap 始终 0%
/var/log/secure-20260802.zstSSH 审计日志全生命周期仅 1 条 Failed password
/var/log/messages系统消息日志无 OOM、内核告警、I/O 错误
vanblog-access.log博客访问日志流量正常,攻击模式不存在
docker logs vanblog-mongo-1MongoDB 容器日志WiredTiger checkpoint 正常,无异常
docker logs vanblog-vanblog-1VanBlog 容器日志ISR 渲染正常,无异常

本文作者:yan7

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!