服务器:阿里云 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 被占满,所有需要文件访问或新内存分配的进程全部卡死。
服务器卡死无法 SSH,在控制台强制重启后重新登录。首先获取当前系统状态:
bashuptime # 确认刚重启(5分钟),无异常负载
free -h # 1.6G内存/997M已用/74M空闲,Swap 2G/0B使用
df -h # 40G磁盘/15G已用/39%,空间正常
当前状态一切正常——这排除了磁盘满、硬件故障等持久性问题。问题必定和重启前运行的某个进程或状态有关。
bashps aux --sort=-%cpu | head -20 # 无异常高CPU进程
ps aux --sort=-%mem | head -15 # Mongo 7%, Node 7%, Waline 7% 正常
为什么这样想:阿里云后台监控显示连接数 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 条失败记录(整个生命周期)
验证结果不是攻击的排查:
bashsudo tail -300 /home/alibaba/blog/vanblog/log/vanblog-access.log
访问日志分析:
/.env、/.git/config、/wp-login.php,全部返回404)X-Nextjs-Cache: HIT),响应时间在 5-30ms结论:不是攻击。公网带宽经阿里云后台确认只有约 100kbps,不符合 DDoS 特征。访问日志是典型的个人博客流量模式,没有任何恶意流量模式。关键证据:屏蔽所有公网 IP 后服务器依旧没恢复——如果攻击流量来自公网,安全组拦截是即时生效的,不恢复反向证明负载来源不是外部入站流量。
为什么这样想: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(客户端主日志)
查看备份计划详情:
bashsudo 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,为增量备份)。备份走阿里云内网上传,不走公网流量。
查看备份实际结束时间:
bashsudo 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 参数——两者独立存在。
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 的操作全部无限期排队。
为什么这样想:VanBlog 的 ISR (Incremental Static Regeneration) 每小时触发一次全量渲染(读取所有文章数据、生成静态页面)。如果 ISR 和 dnf makecache 叠加,可能导致内存和 I/O 阶段性峰值。
验证方法:
bashsudo 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 附近停止。无异常错误,无无限循环。
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
bashcat /proc/sys/vm/swappiness
# 输出:0
swappiness=0 的含义:告诉内核"绝对不要使用 swap,优先丢弃文件缓存页来腾内存"。
这个值对桌面或低延迟服务或许合理,但对运行 Docker+MongoDB 的内存机器来说是灾难性的,因为:
为什么 Swap 始终 0%:swappiness=0 让内核无论内存多紧张都不敢用 swap。2G swap 全程闲置。
bashgrep -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:56 | ECS 文件备份基础版首次全量备份(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:28 | hbrclient 备份进程完全退出 |
| 08-02 13:00:01 | VanBlog ISR 全量渲染完成 |
| 08-02 13:01:01 | cron.hourly 正常执行 |
| 08-02 13:12 | hbrclient 诊断日志最后一次写入 |
| 08-02 13:17:54 | 博客最后一条访问日志(ClaudeBot) |
| 08-02 13:29:39 | dnf-makecache.service 开始。内存压力已在临界点(重启后经半天的容器运行恢复到约 83%) |
| 08-02 13:29:43 | dnf 下载 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 的热页 → 进程立刻缺页中断 → 读磁盘 → 读回来又占满内存 → 又丢 → 死循环
swappiness=0 不是 "优先用物理内存",而是 "禁止使用 swap"。2G swap 分配了但内核不会往里面写任何东西。如果能用上 swap:dnf 需要内存 → 内核把 MongoDB 等进程的一些不活跃匿名页换到 swap → 腾出物理内存给 dnf → dnf 结束后那些页再换回来 → 整个过程有开销但不会导致所有文件缓存被清空的死循环。
journald 写日志本身也需要文件系统操作(追加写入 /var/log/journal/),文件系统操作需要 I/O,而 I/O 队列已被 Thrashing 产生的读请求爆满,所有非 Thrashing 参与的写操作都在排队的最后面——永远等不到。
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 -l、sudo cat /root/.ssh/authorized_keys、systemctl 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 Killer | journal 无 OOM kill 记录 | journalctl -b -1 | grep -i oom |
| 内核 Panic | 监控始终上报,无 kernel panic 日志 | journalctl -b -1 | grep -i panic |
将 swappiness 从 0 改为 10:
bashecho 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl -w vm.swappiness=10
保留了 ECS 文件备份基础版(增量、低优先级、凌晨窗口,设计合理,与本次事件无关)
[P1] 给 MongoDB 加内存限制:
yaml# docker-compose.yaml
mongo:
command: --wiredTigerCacheSizeGB 0.15
mem_limit: 192M
[P1] 给 VanBlog 容器加内存上限:同文件添加 mem_limit: 384M
[P2] 关闭 22 端口:安全组删除 22 入站规则,仅保留 ***
[P3] 验证 swappiness 改动已生效:
bashsysctl vm.swappiness # 应输出 vm.swappiness = 10
vm.swappiness=0 在生产环境中是危险的:它看起来像"优先用物理内存",但实际效果是"内存紧张时优先把文件缓存全部丢弃"。对数据库和 Node.js 应用,文件缓存就是它们的运行时内存。阿里云镜像默认设 0 在内存充裕的机器上没问题,在 1.6G 小机器上是定时炸弹。
排查系统假死要从内存+内核参数入手:假死(无崩溃日志、但不可交互)不是某个进程卡死,而是所有用户态进程被 I/O 饿死。常见的原因就是 swap/缓存配置不当导致的 Thrashing。
Swap 不是"内存不够用的补丁",是"内存压力时的安全阀":swappiness=0 不代表"我不需要 swap",而是"把 swap 的安全阀关掉"。在小内存机器上,swap 是防止 Thrashing 的最后一道防线。
阿里云控制台的监控数据是重要的排查线索:磁盘 100MB/s 读 + 低公网带宽 = 负载来自内部,不是外部攻击。这个规律可以推广。
排除法效率高:对"攻击"、"备份扫描"、"数据库异常"逐个用日志验证或排除。最危险的是在排除假设备份后就停止——真正的根因在更底层的 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.zst | SSH 审计日志 | 全生命周期仅 1 条 Failed password |
/var/log/messages | 系统消息日志 | 无 OOM、内核告警、I/O 错误 |
vanblog-access.log | 博客访问日志 | 流量正常,攻击模式不存在 |
docker logs vanblog-mongo-1 | MongoDB 容器日志 | WiredTiger checkpoint 正常,无异常 |
docker logs vanblog-vanblog-1 | VanBlog 容器日志 | ISR 渲染正常,无异常 |
本文作者:yan7
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!