日志爆量是 CC 攻击最容易被忽略的连带伤害:带宽没满、CPU 不高,磁盘先到 100%。本文讲清日志为什么涨得这么快、写满之后会连环坏什么、止血的先后顺序,以及切割、限流、留存三层怎么配。
Q:CC 攻击为什么能把日志撑到几十 GB?
- Nginx 每处理一个请求就写一行 access log。combined 格式一行大约 200 到 300 字节;如果带上完整 UA 和 Referer,或者加了
$upstream_response_time、$request_body这类字段,单行能到 500 字节以上。 - 算术很直接:1 万 QPS 跑一小时就是 3600 万行,按 300 字节算超过 10 GB。10 万 QPS 一小时接近 100 GB,比多数云服务器的系统盘还大。
- 正常站点一天可能只有几十万行、几百兆。攻击把这个数字放大两三个数量级,而磁盘容量不会有任何变化。
- 更麻烦的是 error log 同步在涨:请求被限流、被 WAF 拦、上游超时,几乎每个请求都会写一条错误记录。两本账一起涨,容量估算就彻底失准。
Q:磁盘写满之后会连环坏掉哪些东西?
- MySQL 先出事:binlog 写不进去,InnoDB 报错,严重时实例只读或崩溃。用户看到的直接现象是首页 500、下单失败。
- 会话跟着挂:PHP 的 session 文件落在磁盘上,写不进就是用户登不上、购物车清空。
- 日志自己也坏:磁盘满时写入失败,文件尾部可能出现截断,事后想复盘发现缺的恰好是最关键那一段。
- 缓存落盘失败:开了
stop-writes-on-bgsave-error的 Redis 会直接拒绝写入,缓存层等于报废。 - inode 比容量更阴:
df -h显示还有空间,df -i已经 100%。切割后的日志、session、临时文件都是小文件,先把 inode 吃光是常事。
Q:怎么在三分钟内判断是日志把磁盘吃满的?
# 容量与 inode 一起看
df -h /
df -i /
# /var 下按目录排序,找吃盘大户
du -xhd1 /var 2>/dev/null | sort -h | tail -10
# 日志目录单独看
du -sh /var/log/* | sort -h | tail -10
# 已删除但仍被进程占用的文件(删了空间不释放的典型)
lsof +L1 2>/dev/null | head -20
# 看增速:两次对比
ls -l /var/log/nginx/access.log; sleep 10; ls -l /var/log/nginx/access.log
- 增速是最关键的判断依据。10 秒涨几十兆说明攻击还在继续,这时候光清理没用,必须同步上限流,否则清完马上再满。
lsof +L1里出现 nginx 或 mysqld 的大文件,说明有人用rm删了日志但进程还握着句柄,空间不会回来。处理办法是对进程发 USR1 重开文件,或者重启对应服务。
Q:日志格式能不能瘦身,能省多少?
- 能省一半以上,而且几乎不影响排查。真正有用的字段是 IP、时间、方法、URI、状态码、响应时间、字节数;UA 与 Referer 只在查爬虫和来源时用得上。
- 静态资源最该砍:图片、CSS、JS 的请求量平时占七成以上,攻击时能到九成。
# http 块
map $uri $loggable {
~*\.(js|css|png|jpg|jpeg|gif|ico|woff2?|svg|mp4)$ 0;
default 1;
}
log_format slim '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent rt=$request_time';
server {
access_log /var/log/nginx/access.log slim buffer=64k flush=5s if=$loggable;
}
buffer=64k flush=5s让 Nginx 先在内存里攒着,实际写盘次数大减,磁盘 IO 压力能降一个量级。- 较新的 Nginx 版本
access_log支持gzip参数,边写边压,体积通常只剩十分之一,代价是不能直接 grep,得用 zcat。
Q:logrotate 怎么配才不会丢日志?
- 核心两件事:切割后通知 Nginx 重新打开文件,以及别用
copytruncate。
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
size 500M
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
endscript
}
- 不要用 copytruncate。它一边复制一边把原文件截断,切割瞬间写进去的日志会永久丢失。攻击期间正好是日志最密的时候,丢的往往就是证据。
size和daily同时写,哪个条件先满足就切,突发流量下不用等一整天。rotate 14乘单份体积就是预留空间。攻击时单份能到几个 GB,14 份就是几十 GB,容量要按这个算,不能按平时的几百兆算。- 配完先
logrotate -d干跑一次,确认路径、属主、权限都对,别等出事才发现权限错了。
Q:磁盘已经 100% 了,止血的先后顺序是什么?
- 第一件事不是删日志,是停止增长。先上限流,否则清一次满一次。
- 第二件事压缩,不要直接删。攻击窗口的原始日志是复盘和申诉的唯一现场。
- 第三件才是清理,而且从最没价值的地方开始。
# 1. 先压最老的日志,压缩比通常 8 到 12 倍
gzip -9 /var/log/nginx/access.log.1 2>/dev/null
# 2. 强制切割一次,让 Nginx 换到新文件
logrotate -f /etc/logrotate.d/nginx
kill -USR1 $(cat /run/nginx.pid)
# 3. 系统日志瘦身
journalctl --vacuum-size=200M
# 4. 清掉过期的压缩日志(确认保留策略后再执行)
find /var/log -name "*.gz" -mtime +7 -delete
# 5. binlog 单独占空间时
mysql -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY)"
- 直接
rm正在写的文件等于把空间锁死不还,因为进程还握着那个 inode。必须删完对进程发 USR1,或者干脆只切割不删除。 - binlog、慢查询日志、错误日志最常被忘。一个跑了几年的库,光 binlog 就能占掉上百 GB。
Q:源头限流怎么配,才能让日志量降下来?
- 限流是唯一能真正压住日志量的手段,切割和清理都只是补救。
# http 块
limit_req_zone $binary_remote_addr zone=perip:20m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=connip:10m;
limit_req_log_level warn;
server {
location / {
limit_req zone=perip burst=20 nodelay;
limit_conn connip 10;
limit_req_status 429;
}
}
limit_req_log_level warn这一行很关键。默认级别是 error,被限流的每个请求都会往 error log 写一条,日记量直接翻倍。限流本身反而制造了日志风暴,这个坑几乎人人都踩过。- 再把被拒请求从 access log 里排掉,又能省一大截。
map $status $log_ok {
429 0;
444 0;
default 1;
}
access_log /var/log/nginx/access.log slim buffer=64k flush=5s if=$log_ok;
if=只接受一个变量,需要多个条件时用 map 组合成一个。
Q:日志被裁掉了,事后还能复盘和取证吗?
- 能,前提是提前把摘要和原文分开存。
- 摘要日志只留 IP、时间、URI、状态码、响应时间,单行不到 100 字节,体积只有原文的三到五分之一,留 30 天也不心疼。
- 原文按需落盘:只对可疑请求保留完整 UA 和 Referer,比如命中 WAF 规则的、UA 长度小于 15 的、请求体异常的。用 map 判断后写到另一个文件即可。
- 聚合到外部:rsyslog 或 Filebeat 把日志送到另一台机器或对象存储,源站只留几小时。攻击时源站磁盘压力大降,分析反而更方便。
- 定期跑一遍 top IP、top URI、状态码分布存成表。原文被裁之后,这张表就是唯一能还原攻击形态的东西。
Q:还有哪些隐性写盘会跟着一起爆?
- error.log:见上面那条,限流超限、上游超时、SSL 握手失败都会写,攻击时涨得比 access log 还猛。
- PHP 错误日志:磁盘满导致的 No space left on device 会再产生一批错误,形成正反馈,越满越报、越报越满。
- session 目录:大并发下小文件数量暴涨,先吃 inode。
- WAF 与安全设备日志:本机部署的 WAF 日志量往往比 Nginx 还大,而且默认全量记录。
- journald:默认几乎不限制容量,一个 systemd 单元刷日志就能把 journal 目录撑上去,建议设
SystemMaxUse=500M兜底。
Q:还有什么补充建议?
- 把
/var/log单独挂一块盘,至少给根分区留保留块(tune2fs -m 5),避免日志把系统盘写死导致 SSH 都登不上。 - 监控要同时盯三个数:磁盘使用率、inode 使用率、日志增速。80% 就该告警,别等 95%。
- 保留策略写进预案:原始日志 7 天、摘要日志 30 天、统计表 180 天。攻击来的时候没空讨论这个。
- 半年演练一次:人为把日志盘压到 90%,看整个处置流程要多久。真出事你会发现最耗时的不是技术动作,而是找不到谁有权限删。
- 最后一句:日志是攻击期间唯一的现场,但现场不是越多越好。该压的压、该限的限、该留的一条不能少。