AIGC标识 kube-apiserver 如何针对某个 ServiceAccount 创建限流规则

结论

kube-apiserver 的限流由 APF(API Priority and Fairness,API 优先级与公平性) 实现,通过两个集群级对象配合完成:

  1. PriorityLevelConfiguration(简称 PLC):定义限流策略——并发份额、是否排队、队列参数,是真正“限流”的地方;
  2. FlowSchema(简称 FS):定义“哪些请求进哪个 PLC”,通过 subjects 字段可以精确匹配某个 ServiceAccount(kind: ServiceAccount + serviceAccount.name / serviceAccount.namespace)。

针对某个 ServiceAccount 限流,只需两步:创建一个 Limited 型 PLC(或复用现有 PLC)→ 创建一个 matchingPrecedence 足够小、subjects 命中该 SA 的 FlowSchema 绑定过去。配置热加载,kubectl apply 后立即生效,无需重启 kube-apiserver。

关键前提:默认的 service-accounts FlowSchema 的 matchingPrecedence 是 9000(命中后进 workload-low),自定义 FlowSchema 的 matchingPrecedence 必须小于 9000(例如 8000),才能在该 SA 请求被默认规则截走之前抢先命中。

分析版本

  • 软件:Kubernetes kube-apiserver
  • 版本:v1.37(2026-08 发布);APF 特性自 v1.29 起稳定(Stable),flowcontrol.apiserver.k8s.io/v1 API 自 v1.29 起稳定
  • 源码路径staging/src/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap/default.go(默认对象定义)
  • 官方文档https://kubernetes.io/docs/concepts/cluster-administration/flow-control/

详细分析

1. 前置知识:SA 请求在 kube-apiserver 里的身份

Pod 内进程使用 ServiceAccount Token 访问 kube-apiserver 时,认证后得到的身份信息是固定格式:

身份字段 取值示例
用户名(user) system:serviceaccount:default:my-app
所属组(groups) system:serviceaccounts
所属组(按命名空间) system:serviceaccounts:default
所属组 system:authenticated

因此,在 FlowSchema 里匹配某个 SA 有三种写法:

  1. kind: ServiceAccount + serviceAccount.name / serviceAccount.namespace(推荐,字段可读性最好,name 支持 * 匹配该命名空间下所有 SA);
  2. kind: User + user.name: system:serviceaccount:default:my-app(直接匹配完整用户名);
  3. kind: Group + group.name: system:serviceaccounts:default(匹配该命名空间下所有 SA,适合按命名空间整体限流)。

2. APF 分类与限流流程

image

请求进入 kube-apiserver 后的处理要点:

  • FlowSchema 匹配:所有请求按 matchingPrecedence 从小到大依次与 FlowSchema 比较,第一个命中即生效(值越小优先级越高)。
  • 匹配语义:一个 FlowSchema 内有 rules 列表,任意一条 rule 命中即命中;一条 rule 需同时满足 subjects(任一命中)AND(resourceRules 任一命中 OR nonResourceRules 任一命中)。verbsapiGroupsresourcesnamespacesnonResourceURLs 以及 subject 的 name 字段都支持 * 通配。
  • 绑定 PLC:FlowSchema 通过 priorityLevelConfiguration.name 绑定到一个 PLC;distinguisherMethod 决定 flow 的划分方式(ByUser / ByNamespace / 空表示单流)。
  • PLC 限流:PLC 分为 Exempt(不限流,直接放行)和 Limited(限流)。Limited 型按 nominalConcurrencyShares 在总并发中分得席位;超出并发时按 limitResponse 处理——Queue 排队等待,Reject 直接返回 HTTP 429。
  • 总并发:默认 --max-requests-inflight=400 + --max-mutating-requests-inflight=200 = 600 席,各 PLC 按 shares 比例分配,空闲席位可借出(lendablePercent)。

3. v1.37 默认配置:某个 SA 的请求默认去哪

不做任何自定义时,普通命名空间下的 SA 请求会命中默认的 service-accounts FlowSchema,进入 workload-low PLC。v1.37 源码里的默认对象如下:

默认 FlowSchema(按 matchingPrecedence 升序):

FlowSchema matchingPrecedence 绑定的 PLC 命中条件
exempt 1 exempt system:masters
probes 2 exempt /healthz/readyz/livez
system-leader-election 100 leader-election 内置控制器的 leader election(leases)
system-node-high 400 node-high system:nodes 组的节点状态上报
system-nodes 500 system system:nodes 组的其他请求
kube-controller-manager 800 workload-high kube-controller-manager 用户
kube-scheduler 800 workload-high kube-scheduler 用户
kube-system-service-accounts 900 workload-high kube-system 命名空间下的所有 SA
service-accounts 9000 workload-low 所有 SA(system:serviceaccounts 组)
global-default 9900 global-default 其余所有已认证/未认证请求
catch-all 10000 catch-all 兜底(Reject,份额极小)

默认 PriorityLevelConfiguration:

PLC nominalConcurrencyShares lendablePercent limitResponse queues / handSize / queueLengthLimit
system 30 33% Queue 64 / 6 / 50
node-high 40 25% Queue 64 / 6 / 50
leader-election 10 0% Queue 16 / 4 / 50
workload-high 40 50% Queue 128 / 6 / 50
workload-low 100 90% Queue 128 / 6 / 50
global-default 20 50% Queue 128 / 6 / 50
catch-all 5 0% Reject
exempt Exempt

可以看到,workload-low 的份额最大(100)、借出比例最高(90%),对普通 SA 比较宽松。如果某个 SA(比如一个写得不好的监控/同步组件)会用大量 LIST 请求打满 apiserver,它会和其他所有普通 SA 共享 workload-low 的 100 份额,互相影响。自定义限流的目的就是把它单独隔离到一个低份额的 PLC,避免拖累别人。

4. 实操:为某个 SA 创建限流规则

image

假设要限流的目标是 default 命名空间下名为 my-app 的 ServiceAccount。完整 YAML 如下:

apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: PriorityLevelConfiguration
metadata:
  name: my-app-limited
spec:
  type: Limited
  limited:
    # 并发份额:总并发 600 席中占 5/(所有 Limited PLC shares 之和)
    nominalConcurrencyShares: 5
    # 空闲席位不借给其他 PLC,隔离更彻底
    lendablePercent: 0
    limitResponse:
      # Queue:超出并发时排队;Reject:超出直接 429
      type: Queue
      queuing:
        queues: 8            # 队列数,越多越公平、内存越大
        handSize: 3          # shuffle sharding 每组队列数
        queueLengthLimit: 20 # 单队列最大长度
---
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: my-app-fs
spec:
  # 必须小于 9000(默认 service-accounts 的 precedence),才能抢先命中
  matchingPrecedence: 8000
  # 绑定到上面创建的 PLC
  priorityLevelConfiguration:
    name: my-app-limited
  # flow 划分:ByNamespace 表示同一命名空间的请求视为同一条流
  distinguisherMethod:
    type: ByNamespace
  rules:
    - subjects:
        # 精确匹配 default/my-app 这个 ServiceAccount
        - kind: ServiceAccount
          serviceAccount:
            name: my-app
            namespace: default
      # 匹配所有资源请求(可按需收窄)
      resourceRules:
        - verbs: ["*"]
          apiGroups: ["*"]
          resources: ["*"]
          namespaces: ["*"]
          clusterScope: true
      # 匹配所有非资源请求(如 /api、/apis、/version)
      nonResourceRules:
        - verbs: ["*"]
          nonResourceURLs: ["*"]

应用并确认:

kubectl apply -f my-app-limit.yaml
# prioritylevelconfiguration.flowcontrol.apiserver.k8s.io/my-app-limited created
# flowschema.flowcontrol.apiserver.k8s.io/my-app-fs created

kubectl get flowschemas my-app-fs
kubectl get prioritylevelconfiguration my-app-limited

字段要点说明:

  • matchingPrecedence: 8000:小于默认 service-accounts 的 9000,因此该 SA 的请求会先命中 my-app-fs,而不是走默认的 workload-low。
  • nominalConcurrencyShares: 5:份额越小,并发上限越低。实际并发席位 = 600 × 5 /(所有 Limited PLC shares 之和)。默认所有 Limited PLC shares 之和 = 30+40+10+40+100+20+5+5 = 250,因此 5 份额约等于 600×5/250 = 12 席(注意:新增 PLC 会让分母变大,其他 PLC 的绝对席位会同比略降)。
  • lendablePercent: 0:该 PLC 的空闲席位不借给别人,别人的空闲席位也不会被它借走(借出是双向的,lendablePercent 控制本 PLC 可借出的比例)。设为 0 隔离最彻底。
  • limitResponse.type: Queue:突发流量先排队而不是直接拒绝,适合对延迟不敏感但不能丢请求的场景;如果希望硬限流直接拒绝,改成 Reject
  • distinguisherMethod.type: ByNamespace:同一命名空间的请求视为同一条 flow,公平排队。如果这个 SA 会被多个命名空间的 Pod 共用(不太常见),可以用 ByUser 按用户划分。
  • resourceRulesclusterScope: true + namespaces: ["*"]:同时覆盖集群级请求(如 list nodes)和命名空间级请求。如果只写 namespaces: ["*"] 不写 clusterScope: true,集群级请求不会命中。

5. 变体场景

场景 A:只限制该 SA 的 LIST / WATCH 请求(写操作不受限)

resourceRules 收窄:

resourceRules:
  - verbs: ["get", "list", "watch"]
    apiGroups: ["*"]
    resources: ["*"]
    namespaces: ["*"]
    clusterScope: true

这样 create/update/patch/delete 等写请求不会命中这个 FlowSchema,继续走默认的 workload-low。

场景 B:限制某个命名空间下的所有 SA

用 Group 匹配,而不是逐个 SA 写:

subjects:
  - kind: Group
    group:
      name: system:serviceaccounts:my-namespace

场景 C:限制 kube-system 命名空间下的某个 SA

默认 kube-system-service-accounts 的 matchingPrecedence 是 900,因此自定义 FlowSchema 的 precedence 必须小于 900(例如 800)才能抢先命中。注意:kube-system 里跑的大多是关键组件,限流前务必评估影响。

场景 D:不新建 PLC,直接把请求打到现有 PLC

FlowSchema 的 priorityLevelConfiguration.name 可以指向任何已存在的 PLC,包括默认的 workload-lowglobal-default,甚至强制打到 catch-all(Reject 型,相当于直接拒绝该 SA 的所有请求)。但更推荐新建独立 PLC,这样份额和队列参数可以单独调,不影响默认配置。

6. 验证与监控

查看对象是否生效:

kubectl get flowschemas
kubectl get prioritylevelconfiguration

查看实时限流状态(每个 PLC 的并发、队列、拒绝情况):

kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels
kubectl get --raw /debug/api_priority_and_fairness/dump_flowschemas
kubectl get --raw /debug/api_priority_and_fairness/dump_queues

dump_priority_levels 输出 CSV,包含每个 PLC 的当前执行请求数、排队请求数、已拒绝数等,可直接确认 my-app-limited 是否在承接流量。

关键 Prometheus 指标(kube-apiserver /metrics):

指标 含义
apiserver_flowcontrol_rejected_requests_total{flow_schema, priority_level, reason} 被拒绝的请求总数,reason 为 queue-full / concurrency-limit / time-out / cancelled
apiserver_flowcontrol_current_inqueue_requests{priority_level, flow_schema} 当前排队中的请求数
apiserver_flowcontrol_current_executing_requests{priority_level, flow_schema} 当前正在执行的请求数
apiserver_flowcontrol_current_executing_seats{priority_level, flow_schema} 当前占用的席位数(大 LIST 会占多席)
apiserver_flowcontrol_nominal_limit_seats{priority_level} 该 PLC 的名义并发席位上限
apiserver_flowcontrol_request_wait_duration_seconds{flow_schema, priority_level} 请求排队等待时长直方图

验证自定义规则是否命中:

# 用挂了该 SA 的 Pod 发起请求,观察 my-app-fs 的拒绝计数是否增长
kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total | grep my-app-fs

7. 注意事项

  1. 默认对象会被自动维护:kube-apiserver 每分钟 reconcile 一次默认(mandatory + suggested)对象。删除默认对象会被自动重建;修改默认对象的 spec 会被拉回,除非加上注解 apf.kubernetes.io/autoupdate-spec: "false"自己新建的对象不会被自动覆盖,放心修改。

  2. matchingPrecedence 冲突:如果两个 FlowSchema 的 matchingPrecedence 相同且都命中同一个请求,按名称字典序小的生效。应避免依赖这个行为,保持每个 precedence 唯一。

  3. watch 请求计入 APF:watch 请求占 1 席,初始突发(推送已有对象)结束后释放席位。但 execlog(流式)、portforward 等长连接请求不受 APF 限流

  4. 大 LIST 请求占多席:apiserver 会估算 LIST 返回的对象数,请求占用的席位数与估算对象数成正比。因此一个 list pods --all-namespaces 可能占几十席,限流效果比预期更明显。

  5. 被拒返回 HTTP 429Too Many Requests。客户端(尤其是 controller)应做指数退避重试,不要立即重试加剧压力。

  6. APF 是并发模型,不是每秒 QPS 限流:它限制的是“同时在处理的请求数”,不是“每秒请求数”。如果需要精确的每秒速率限流(令牌桶),应在网关层(如 ingress / API 网关)做,APF 做不到。

  7. 调整总并发需要重启:修改 --max-requests-inflight / --max-mutating-requests-inflight 需要重启 kube-apiserver 才生效;重启后各 PLC 的绝对席位按 shares 比例同比变化,不需要改 PLC 配置。

  8. 多实例独立限流:每个 kube-apiserver 实例独立执行 APF 限流(共享 etcd 中的 FlowSchema/PLC 对象,但并发计数是实例级的)。如果有 3 个 apiserver 实例且负载均衡均匀,实际总并发约为单实例的 3 倍。

8. 参考资料

posted on 2026-09-13 10:30  王景迁  阅读(11)  评论(0)    收藏  举报

导航