CC 攻击期间最怕的不是攻击变强,而是临时加的规则把自己人拦在门外。本文用 10 个问答讲清 CC防护规则的变更管理:上线前准备什么、怎么灰度、观察窗盯哪些指标、出现哪些信号必须立刻回滚,以及回滚怎么做到一分钟以内。
Q:为什么 CC 防护规则的变更风险特别高?
- 限流的本质是按统计特征判断请求像不像人,天然有误判空间。规则越狠,误判越多。
- 规则作用在全部请求路径上,一条
deny或一个过低的rate,影响的是所有用户,包括正在付款的那批。 - 变更多半发生在攻击进行中:人在紧张状态、验证不充分、只想着「先加上再说」。这是二次故障最典型的成因。
- 经验判断:CC 期间的服务中断,相当一部分不是攻击打穿的,而是应急规则配错造成的。这类故障本可以完全避免。
Q:上线前必须准备哪三样东西?
- 基线:当前时段的 QPS、403/429 占比、5xx 占比、P95。没有基线,改完无法判断好坏。
- 回滚包:当前配置的完整备份、一键回滚脚本、
nginx -t已通过。三样缺一样都不许改线上。 - 观察责任:谁盯、盯多久、看到什么数字就回滚。写清楚,别靠临场商量。
- 再加一条纪律:变更窗口避开业务高峰和大促。攻击期间只调参数,不做结构性变更。
Q:灰度按什么维度切最安全?
- 优先级从高到低:路径 → 用户群 → 地域或 IP 段 → 流量百分比。
- 能按路径就不要按百分比。先把严格规则压在被攻击的那几个接口上,比如
/search、/api/list,首页和支付链路暂不动。 - 能先拿内部账号试,就不要先动真实用户。加一个内部标记 Cookie 或固定 IP 段先跑通,成本几乎为零。
- 百分比灰度是最后手段,因为它把风险平摊到了所有用户身上,谁被拦是随机的。
Q:百分比灰度用 Nginx 怎么落地?
- 分流用
split_clients,限流用两套独立 zone 叠加。关键技巧是:limit_req的键为空时该请求不计入,所以非灰度组不会受新规则影响。
# 1) 用 IP 与 UA 的哈希把 10% 的客户端划入灰度组
split_clients "${remote_addr}${http_user_agent}" $cc_gray {
10% 1;
* 0;
}
# 2) 老规则对所有人生效
limit_req_zone $binary_remote_addr zone=cc_old:20m rate=30r/s;
# 3) 新规则的键:只有灰度组非空,其余为空即不计入
map $cc_gray $cc_new_key {
1 $binary_remote_addr;
0 "";
}
limit_req_zone $cc_new_key zone=cc_new:20m rate=8r/s;
server {
location / {
limit_req zone=cc_old burst=40 nodelay;
limit_req zone=cc_new burst=15 nodelay; # 仅灰度组受更严限制
limit_req_status 429;
}
}
limit_req_status一定要显式设成 429。默认返回 503,会被监控当成服务故障,也会被 CDN 当作可缓存的错误页。- 如果用的是云 WAF,直接在控制台按比例或按 IP 段开灰度更省事,别在 Nginx 层硬拼。
Q:观察窗怎么定,盯哪几个指标?
- 节奏建议:5 分钟初判、30 分钟定论、2 小时收尾。2 小时是为了覆盖一个完整业务波峰,短窗口看不到长尾误杀。
- 四个指标足够:429 与 403 的占比、5xx 占比、P95 延迟、核心业务成功率。前两个看误杀,后两个看效果。
- 实时统计不要靠肉眼翻日志,用一条命令每分钟打一次:
# 每分钟统计一次状态码分布与占比
tail -n 20000 /var/log/nginx/access.log \
| awk '{n++; c[$9]++} END{for (k in c) printf "%s %.2f%%\n", k, c[k]*100/n}' \
| sort -k2 -rn
- 观察期内不要同时改第二个变量。一次只动一个参数,出问题才知道是谁的责任。
Q:出现哪些信号必须立刻回滚?
- 正常 UA 出现 403:说明 UA 规则写宽了,真实浏览器被误判。
- 支付回调或登录接口出现 429:这两条链路一次都不能拦,拦了就是直接掉单。
- 搜索引擎蜘蛛被限流:短期看不出问题,两周后收录掉一片。
- 错误率翻倍但源站 CPU 没降:说明限流没生效,改的不是瓶颈所在。
- 429 占比超过全部请求的 1%:这已经不是「拦异常」而是「拦正常」了。
- 出现任一条,回滚优先于排查。继续观察五分钟的代价,通常远高于回滚一次的代价。
Q:回滚怎么做才压得进一分钟?
- 配置按版本目录存放,用符号链接指向当前生效版本。回滚就是换链接加 reload,不用改文件内容。
# 目录结构
# /etc/nginx/releases/20261021-a/ (本次变更)
# /etc/nginx/releases/20261020-c/ (上一个稳定版)
# /etc/nginx/conf.d -> 当前生效版本
ln -sfn /etc/nginx/releases/20261020-c /etc/nginx/conf.d
# 预检通过才 reload(reload 不是 restart,连接不中断)
nginx -t && kill -USR1 $(cat /run/nginx.pid)
- 回滚脚本要做失败自保护:先
nginx -t,不通过就直接退出并保留原链接,绝不能让一次失败的 reload 把线上带下去。 - reload 后旧 worker 会等现有连接结束才退出,长连接场景下要设
worker_shutdown_timeout 10s,否则老规则还会在旧进程里多活很久。 - 回滚完立刻复测:
curl -sI看状态码,再看一眼 429 占比是否回落。
Q:Nginx 侧最容易踩的坑有哪些?
- 在
if里写limit_req:该指令不支持在 if 块内使用,写了不报错但行为不可预期。 - 多处叠加导致阈值失控:server 与 location 各写一条
limit_req,实际是同时生效,等于把速率乘了一次,很容易把正常用户限死。 - zone 开太小:10m 大约只能放 16 万个 IP 状态,被打量大时状态被淘汰,限流形同虚设。按业务量给到 50m 以上更稳。
- 用 XFF 做限流键却没配
set_real_ip_from:所有人被识别成同一个回源 IP,一次限流全站一起 429。 limit_req_status用默认值:503 会被监控判成宕机,也会被 CDN 缓存,形成「缓存了的错误页」。必须改 429 并加Cache-Control: no-store。
Q:多节点、多 CDN 怎么保持一致,变更要不要通知?
- 分批推:1 台 → 10% 节点 → 全量,每批验证通过再进下一批。多节点同时改,出问题连对照组都没有。
- 用 git 或配置中心管配置,推完做一次一致性校验,别靠人肉记忆:
# 逐台比对生效配置的哈希
for h in 10.0.0.11 10.0.0.12 10.0.0.13; do
printf "%s " $h; ssh $h 'md5sum /etc/nginx/nginx.conf | cut -c1-12'
done
- CDN 与 WAF 的开关要和源站同步。限流页面被 CDN 缓存,等于给所有人发了 429 页面,这是最隐蔽的一种事故。
- 变更前在值班群发一条:改什么、影响谁、谁盯、回滚口令。四句话说清,比开会快。
Q:还有什么补充建议?
- 新规则先跑观察模式:只记录不拦截,一周后看命中了哪些请求,确认没有正常流量再真正上线。
- 把回滚当默认动作,不是失败。敢回滚的团队才敢改配置,改得快的前提是退得快。
- 变更留五要素记录:时间、内容、影响面、观察结果、是否回滚。这份记录是下一次变更最省时间的参考。
- 最后一句:CC 防护的上限不是规则写得多狠,而是你敢不敢改、改错了能不能立刻退回来。