Kubernetes 限流 配置避坑指南
起因:本来想防刷,结果把自己刷趴了
前阵子有个对外的接口被人拿脚本刷,半夜告警响个没完,寻思着在 Ingress 上配个 nginx.ingress.kubernetes.io/limit-rps 就完事了,阈值还特意设得挺宽裕。结果上线十来分钟,服务大面积 503,pod 接连重启,用户骂声一片。限流明明是用来保护服务的,怎么先把自家服务干趴了?
排查下来发现这玩意儿的坑是真不少,下面几条都是拿线上流量换来的,配置之前建议对着过一遍,没准儿能帮你省一个晚上的觉。
坑一:拿不到真实 IP,所有请求被当成同一个人
最大的坑,排第一。limit-rps 默认拿 $binary_remote_addr 当限流键,说白了就是 TCP 源 IP。但流量经过云 LB,或者 Service 的 externalTrafficPolicy 是默认的 Cluster 时,源地址会被 SNAT 掉,到了 Ingress Controller 手里,所有人的 IP 都长一个样。这时候开限流,等于把整个入口当成一个人在限,一个人触发阈值,全站陪葬。
先改 Service,保留客户端源 IP:
# ingress-nginx 的 Service,关键是这一行
spec:
externalTrafficPolicy: Local # <--- 保留源 IP,代价是流量必须落在有 pod 的节点上
如果前面还套了 CDN 或者自建 nginx,还得让 Controller 认 X-Forwarded-For,不然拿到的还是那层代理的 IP:
controller:
config:
use-forwarded-headers: "true"
compute-full-forwarded-for: "true"
proxy-real-ip-cidr: "10.0.0.0/8" # <--- 可信代理的网段,按实际情况改
要注意的是,use-forwarded-headers 一开,这些头客户端是能伪造的,务必确认前面没有裸奔的直连入口,proxy-real-ip-cidr 也别图省事配成 0.0.0.0/0。另外不同版本的参数名有点出入,以你那个版本的文档为准。
坑二:阈值会按副本数悄悄放大
nginx 的 limit_req_zone 是每个 Controller pod 各自独立计数的,那块共享内存 zone 不跨实例同步。你 3 个副本,配了 100 rps,实际放行上限就是 300 rps。这不算 bug,是 nginx 的机制,但配的时候十个人有九个会忘。
顺带一提,limit-burst-multiplier 默认是 5,突发窗口里实际能冲进来 5 倍的量,而且是 nodelay,超出的请求不排队、直接拒,别指望它会帮你把请求攒着慢慢放。
同一件事放到应用层也一样:代码里拿 Guava 的 RateLimiter 配了个 1000 的全局限流,Deployment 4 个副本,那就是 4 个各限 1000,总量 4000。要么拿总数除以副本数,要么老老实实上 Redis + Lua 做集中式限流,进程内存是做不了全局的。
坑三:探针被限流,pod 进入无限重启
如果限流一上线 pod 就开始接连重启,八成是它。kubelet 发的探针请求源 IP 是节点 IP,不在白名单里,一旦撞上限流窗口,readiness 失败被摘流量,liveness 失败直接重启,重启完冷启动又慢,雪崩就这么来的。
把节点和 LB 健康检查来源的网段加进白名单:
nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8,172.16.0.0/12"
网段按你自己的集群环境改,别照抄我的。或者干脆给探针留个单独的 path,在 nginx 层对这个 path 不做限流。不出意外的话,探针就不会再被误杀了。
坑四:默认返回 503,监控半夜喊你起来
ingress-nginx 触发限流时默认返回 503。可 503 是服务端错误啊,客户端超速关服务什么事?可能历史原因吧... 这个码一挂上去,你的错误率告警、SLO 统计全把它算成服务端故障。
在 Controller 的 ConfigMap 里改掉:
data:
limit-req-status-code: "429"
改完记得把 429 从 5xx 告警里摘出来单独算一类。还有个连带问题:不少 SDK 看到 429 会自动重试,对方要是没做退避,你的限流反而会放大流量,跟调用方定契约的时候把这个说清楚。
坑五:CPU limit,最隐蔽的一种“限流”
还有一种限流特别容易被忽略:给 pod 设了 CPU limit,内核 CFS 是按 100ms 一个周期给配额的,周期内用完,进程直接被暂停到下个周期,这就是 throttling。它不报 429 也不报 503,表现就是延迟周期性抖动,CPU 使用率看起来还不高,特别迷惑。
拿这个指标确认:
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m])
比例高的话,要么调大 limit,要么评估下去掉 limit 只留 request。Java 这种应用启动就吃满核的更得注意,别问我是怎么知道的。
最后,一份填完坑的配置
上面的坑都填上之后,大概是这个样子,阈值按你自己压测的结果来:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-api
annotations:
nginx.ingress.kubernetes.io/limit-rps: "30"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: my-api
port:
number: 8080
上线前先在灰度环境压一轮,盯一下 429 的比例和上游真实 QPS,别拍脑袋定阈值。限流这东西配松了等于没配,配紧了,半夜起来回滚的就是你自己。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。