bigywater.tech
Kubernetes · 23

API 优先级与公平:429 是怎么产生的

429 Too Many Requests 不是应用返回的,是 API Server 的 APF 在保护自己。这篇把 APF 的默认配置、请求怎么被归类、并发席位怎么算,以及超限时 Reject 和 Queue 两种处理方式的真实差别都跑了一遍。

312 个 429
自建的 1 份额级别(3 个席位)上,80 线程压 5 秒的 6041 个请求里被拒绝的数量
600 × 份额 ÷ 245
并发席位的算法:workload-low(份额 100)得到 245 席,份额 1 的自建级别得到 3 席
0 个 429
同样压力下把 limitResponse 改成 Queue 后返回 429 的数量

默认配置

实测 · 本机执行输出
  优先级名称             类型        并发份额   超限时      队列数   并发席位(seats)
  catch-all         Limited   5      Reject   -     13
  exempt            Exempt    -      -        -     0
  global-default    Limited   20     Queue    128   49
  leader-election   Limited   10     Queue    16    25
  node-high         Limited   40     Queue    64    98
  system            Limited   30     Queue    64    74
  workload-high     Limited   40     Queue    128   98
  workload-low      Limited   100    Queue    128   245
  FlowSchema 共 11 个;其中 matchingPrecedence 最小(最先匹配)的前 4 个:exempt(1)→exempt, probes(2)→exempt, system-leader-election(100)→leader-election, system-node-high(400)→node-high

集群里有 8 个 PriorityLevelConfiguration 和 11 个 FlowSchema。每个优先级按“份额”分到并发席位:总并发(默认 400 + 200 = 600)乘以份额再除以所有 Limited 级别的总份额(245),向上取整,所以 workload-low(100 份)得到 245 席,node-high、workload-high(40 份)各 98 席,catch-all(5 份)13 席。超限时 catch-all 直接 Reject,其他级别排队;exempt 不受限。FlowSchema 按 matchingPrecedence 从小到大匹配,先匹配上的生效。

请求被归到哪里

实测 · 本机执行输出
  kubectl(客户端证书,身份 kubernetes-admin|["kubeadm:cluster-admins","system:authenticated"]):FlowSchema=global-default PriorityLevel=global-default
  匿名访问 /version:HTTP 200;FlowSchema=global-default PriorityLevel=global-default
  ServiceAccount k17/hammer(绑定了 view)请求 pods(HTTP 200):FlowSchema=service-accounts PriorityLevel=workload-low
  无效令牌:HTTP 401;响应头里 没有 FlowSchema(认证失败的请求在进入 APF 之前就被拒了)

响应头 X-Kubernetes-PF-FlowSchema-UID 和 …PriorityLevel-UID 能反查出归类结果。意外的是:kubeadm 的管理员证书(kubernetes-admin,组 kubeadm:cluster-admins)并不是 exempt,它和匿名访问一样归 global-default;ServiceAccount 归 service-accounts → workload-low,即使没有 RBAC 权限、请求最终是 403 也是一样——APF 在鉴权之前。认证失败(无效令牌)的 401 响应头里则没有归类,在进入 APF 之前就被拒了。

Reject:429

实测 · 本机执行输出
  创建 PriorityLevelConfiguration k17-tiny(份额 1,limitResponse=Reject)和 FlowSchema k17-fs(matchingPrecedence=100,匹配 SA hammer);k17-tiny 得到的并发席位 nominal_limit_seats=3
  hammer 再请求一次:FlowSchema=k17-fs PriorityLevel=k17-tiny(原来是 service-accounts / workload-low,因为 100 比默认的更靠前)
  80 个线程连续请求 5 秒(GET /api/v1/pods):共 6041 个请求(1208/秒),状态码 {200: 5729, 429: 312};成功请求的延迟 p50=65 ms p95=117 ms 最大=392 ms
  第一个 429 的响应头 Retry-After=3;x-kubernetes-pf-flowschema-uid → k17-fs;响应体:Too many requests, please try again later.

  apiserver_flowcontrol_rejected_requests_total{priority_level="k17-tiny"} 增加了 312(reason=concurrency-limit)

自建 k17-tiny(份额 1,得到 3 个席位,limitResponse: Reject)和一个 matchingPrecedence: 100 的 FlowSchema 把 SA hammer 引过去。80 个线程连续请求 5 秒:6041 个请求里 312 个被 429,响应头带 Retry-After: 3,响应体是 Too many requests, please try again later.;apiserver_flowcontrol_rejected_requests_total{reason="concurrency-limit"} 增加了 312。client-go 会按 Retry-After 退避重试,自己写的客户端要记得处理 429。

Queue:排队

实测 · 本机执行输出
  limitResponse=Queue(queues=4,queueLengthLimit=100,handSize=2),同样 80 线程 5 秒:共 6071 个请求(1214/秒),状态码 {200: 6071};成功请求的延迟 p50=63 ms p95=112 ms 最大=164 ms
  这一轮 rejected 增加了 0;排过队后执行的请求(request_wait_duration_seconds_count{execute="true"})增加了 6071

同样的压力下把 limitResponse 改成 Queue(4 个队列、每队最长 100):6071 个请求全部是 200,没有 429,rejected 增加 0,所有请求都是先排队、再执行。成功请求的 p95 约 112 ms、最大 164 ms,Reject 那轮分别是 117 ms、392 ms。这轮压力下两者吞吐几乎一样(约 1210 个/秒),差别在于排队的请求最终都成功了,而 Reject 把超出的直接打回。队列满了才会 429。

分步演示

APF 的位置
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0,apiserver 并发参数为默认值);压力来自宿主机上 80 个 Python 线程(http.client,每个线程一个长连接)连续 GET /api/v1/pods 5 秒,身份是绑定了 view 的 ServiceAccount hammer 的令牌;身份归类用响应头里的 UID 对照 flowschemas / prioritylevelconfigurations 反查;席位数取自 /metrics 的 apiserver_flowcontrol_nominal_limit_seats;拒绝与排队计数用 /metrics 前后差值;延迟是客户端测得的(包含网络与 JSON 传输);实验后删除了自建的 FlowSchema 与 PriorityLevelConfiguration;每种情形各一次,数字每次运行不同(Retry-After 也观察到过 1)
    • 没有验证:公平排队(同一优先级内不同用户之间如何分摊,shuffle sharding 的效果);借用(lendablePercent / borrowingLimitPercent);请求的 “seat” 数不止 1 的情形(大 list、watch 初始化);exempt 与 probes 的行为;apiserver_flowcontrol_current_inqueue_requests 等其他指标;--max-requests-inflight 与 APF 的关系
    京ICP备2026067502号-1京公网安备11010502063487号