Kubernetes Flow Control Guide: APF, Workload Admission, and Inference Service Rate Limiting¶
A GPU cluster may encounter three types of congestion at the same time: Operators querying resources heavily, slowing down the API Server; training jobs submitted in bursts, competing for limited GPUs; and sudden traffic spikes to model endpoints, causing time to first token (TTFT) to keep increasing. All of these relate to requests exceeding the system's carrying capacity, but they occur at different locations and require different control measures.
Kubernetes already provides mechanisms such as API request classification, fair queuing, concurrency budgets, and resource quotas. Combined with task queues and inference gateways, these can further manage when tasks start and how many user requests a service accepts. This article explains these capabilities along the actual request path and provides configuration and troubleshooting examples.
This article is based on official documentation and API definitions; the figures are architectural illustrations and hypothetical calculation examples. The examples do not represent flow-control load tests that have already been executed on a production cluster.
If you want to validate behavior with real configuration and monitoring, continue reading APF Isolation Real-World Test: a four-phase experiment, Grafana screenshots, and a source-code explanation of why zero-seat Reject still passes requests in Kubernetes 1.30.4.
1. First Identify What Needs Protection¶
When the training platform creates a Job, it calls the API Server; training computation after the task starts does not pass through APF; user access to an already-started model service does not automatically receive business-level rate limiting just because the API Server has flow control configured.
| Control Target | Primary Mechanism | Control Unit | Typical Outcome |
|---|---|---|---|
| Work concurrently handled by the API Server | APF | Concurrency seats, queued requests | Classified, queued, executed, or rejected |
| Rate at which clients issue API requests | client-go RateLimiter | QPS, Burst | Client waits before sending |
| Event write storms | EventRateLimit | Event request rate | Excess event writes are rejected |
| Resources consumed by a Namespace | ResourceQuota, LimitRange | CPU, memory, GPU, object count | Excess or non-compliant requests are rejected |
| When tasks acquire resources | Kueue, Volcano Queue | Workloads, resource quotas | Queued, admitted, shared, or preempted |
| Requests accepted by model services | Gateway and application/engine controls | Rate, tokens, concurrency, wait time | Accepted, queued, degraded, or rejected |
Native NetworkPolicy controls allow/deny relationships for network connections and has no generic HTTP QPS or token rate-limiting fields. Network bandwidth control also depends on CNI or node capabilities; NetworkPolicy cannot replace application-level rate limiting. See NetworkPolicy.
2. Capabilities of Newer Kubernetes Versions and Migration Boundaries¶
As of the time of this review, the latest stable version listed on the official release page is 1.37.0. However, APF has been stable since 1.29, and upgrading to the latest version is not required to use it. See Release Information and APF Feature Status.
| Capability or API | Status | Practical Impact |
|---|---|---|
APF and flowcontrol.apiserver.k8s.io/v1 |
Stable since 1.29; APF enabled by default | New configurations use v1, but actual API Server settings still need to be verified |
APF v1beta3 |
No longer served since 1.32 | Old YAML, clients, and Helm templates need migration |
nominalConcurrencyShares: 0 |
In v1, an explicit 0 is not defaulted to 30 | 0 cannot be treated as "use the default value" |
| EventRateLimit | Still marked Alpha, disabled by default | Event storms will not be automatically limited after upgrade |
| Kueue, Volcano, Gateway rate limiting | Independent projects or implementation-specific capabilities | Require separate installation, configuration, and version verification |
Older tutorials may show assuredConcurrencyShares or v1beta3 and should be migrated according to the target version. See API Migration Guide for specific requirements; see EventRateLimit for event rate limiting status.
3. How APF Protects the API Server¶
3.1 Classify First, Then Allocate Execution Opportunities¶
A single API Server concurrency limit can cap the amount of admitted work, but it cannot adequately distinguish critical controllers from bulk query scripts. APF adds classification and isolation, allocating execution capacity to different categories and then fairly handling different sources within each category.
The default APF configuration already assigns appropriate classifications to traffic such as Leader Election and built-in controllers. A common use of custom rules is to place bulk reads, resource inventory scans, or abnormal clients into a controlled share, reducing the impact of them continuously occupying execution opportunities. See APF Concepts.
3.2 FlowSchema: Which Requests Belong to the Same Category¶
FlowSchema matches requests by user, group, ServiceAccount, as well as resource, operation, and target Namespace, and points to an PriorityLevelConfiguration.
matchingPrecedencedetermines matching order; the smaller the value, the earlier the match. It is not a GPU task priority and does not directly dictate request execution speed.distinguisherMethoddetermines how flows are distinguished within a category.ByUserdistinguishes by request identity;ByNamespacedistinguishes by the Namespace of the request's target resource, and cannot be interpreted as the Namespace of the client Pod.
When multiple scripts share the same ServiceAccount, ByUser can no longer distinguish them as different users. Programs requiring independent governance should use identifiable identities. Field definitions are in FlowSchema v1.
3.3 PriorityLevelConfiguration: How Much Capacity to Allocate¶
For the Limited type, nominalConcurrencyShares represents the nominal concurrency share, not an absolute seat count, and not requests per second. A category's nominal capacity is related to the ratio of its share to the total share; actual operation is also affected by total capacity, rounding, and borrowing/lending constraints. Adding a new category with a positive share can also change the capacity allocation of existing categories.
| Parameter | Meaning |
|---|---|
nominalConcurrencyShares |
Nominal share used to allocate concurrency capacity |
lendablePercent |
Maximum proportion of nominal capacity that can be lent out |
borrowingLimitPercent |
Upper limit proportion of capacity that can be borrowed |
limitResponse.type |
Uses Queue or Reject when capacity is insufficient |
queues, handSize, queueLengthLimit |
Tune fair queuing, flow isolation, and queue boundaries |
Field definitions and legal ranges are in PriorityLevelConfiguration v1. APF performs flow control on each API Server instance; nominal shares cannot be treated as an exact QPS quota shared across the entire HA cluster.
3.4 Seats, WATCH, and Bounded Queuing¶
Ordinary requests are typically understood as occupying one seat; LIST requests returning many objects may occupy multiple seats. Reading a single object versus scanning many objects in full can have different control-plane costs even at the same QPS.
WATCH cannot simply be understood as "one seat held for the entire connection duration." APF handles its establishment and possible initial event phase, and accounts for related notification overhead. Certain long-running requests such as exec and log following are not subject to this APF filter. See APF Seats and WATCH Explanation.
APF uses mechanisms such as flow partitioning and Shuffle Sharding to reduce the interference of a high-traffic source on other flows. Bounded queues can absorb short bursts, but sustained overload still results in waiting, timeouts, and rejections. It does not replace etcd capacity optimization, slow Webhook fixes, or preempt running GPU tasks.
4. A Well-Scoped APF Configuration¶
The following places GET/LIST requests from the dedicated ServiceAccount apf-demo/batch-reader for Pods in apf-demo into a separate category. The Namespace, ServiceAccount, and read permissions must already exist; FlowSchema does not grant RBAC permissions.
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: PriorityLevelConfiguration
metadata:
name: example-batch-reader
spec:
type: Limited
limited:
nominalConcurrencyShares: 5
lendablePercent: 0
borrowingLimitPercent: 0
limitResponse:
type: Queue
queuing:
queues: 64
handSize: 8
queueLengthLimit: 20
---
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
name: example-batch-reader
spec:
matchingPrecedence: 1000
priorityLevelConfiguration:
name: example-batch-reader
distinguisherMethod:
type: ByUser
rules:
- subjects:
- kind: ServiceAccount
serviceAccount:
namespace: apf-demo
name: batch-reader
resourceRules:
- verbs: ["get", "list"]
apiGroups: [""]
resources: ["pods"]
namespaces: ["apf-demo"]
clusterScope: false
matchingPrecedence: 1000 should be compared with existing rules to confirm that no earlier rule intercepts the request. 5 is a share, not 5 QPS or a fixed concurrency of 5. This example disables borrowing/lending to make the boundaries easier to explain; the queue parameters are also teaching examples only and cannot be used to infer throughput or maximum wait time.
These rules do not match LIST across all Namespaces, WATCH, or other resources, and use a separate custom object, so there is no need to directly modify the system default classifications. The full manifest is in apf-batch-reader.yaml.
You can first inspect the actual cluster configuration:
kubectl api-resources --api-group=flowcontrol.apiserver.k8s.io
kubectl get flowschemas
kubectl get prioritylevelconfigurations
Server-side validation should explicitly target the intended context:
kubectl --context <target-context> apply --dry-run=server \
-f examples/kubernetes-flow-control/apf-batch-reader.yaml
dry-run can surface API and admission validation issues, but it does not make the policy take effect, nor does it prove that actual request classification is correct. After enabling in production, you can use the X-Kubernetes-PF-FlowSchema-UID and X-Kubernetes-PF-PriorityLevel-UID fields in responses (provided intermediate proxies preserve these headers) along with APF metrics to identify matched objects. See APF Observability.
Default APF objects may be automatically maintained by the API Server; before modifying them, you should understand the auto-update annotations and forced configuration rules. Webhooks that call the API Server again while processing requests can create recursive waits or priority inversion, which requires separate dependency analysis; do not compress all controllers into the lowest share.
5. Client-Side Rate Limiting: Reducing Request Pressure at the Source¶
client-go provides QPS, Burst, and custom RateLimiter. The default constants in the official source code are QPS=5, Burst=10, but actual components may override the configuration, so you cannot infer the limits of all clients from these defaults. See client-go REST Configuration.
// cfg 已由 kubeconfig 或 InClusterConfig 构造。
// 数值仅作说明,不是所有 Operator 的推荐配置。
cfg.QPS = 10
cfg.Burst = 20
A custom RateLimiter may override the effect of QPS/Burst. When multiple replicas each use a local rate limiter, the aggregate request volume also increases; a single-client threshold is not equal to the cluster-wide threshold.
Controllers/Operators can reduce pressure through Informer/Lister caches, reducing full LIST operations, narrowing query scopes, controlling work-queue concurrency, and bounded backoff. Client retries should distinguish error sources; not all 429s should be retried immediately, or request amplification will result.
6. What EventRateLimit and Resource Quotas Each Control¶
6.1 EventRateLimit: Limiting Event Writes¶
When abnormal components continuously create or update Events, event-specific rate limiting can be considered. EventRateLimit supports buckets by Server, Namespace, User, or SourceAndObject, and requires configuring the API Server's admission plugin and configuration file. It is still Alpha and disabled by default, and managed clusters may not expose the relevant configuration. See EventRateLimit.
Limiting event writes also means some Events may be missing. Recovery status should be assessed in combination with component logs, metrics, and the root cause of the anomaly; do not conclude that the system has recovered simply because Events have decreased.
6.2 ResourceQuota: Limiting Total Resources¶
Namespaces using nvidia.com/gpu extended resources can set GPU and object count limits:
apiVersion: v1
kind: ResourceQuota
metadata:
name: example-team-budget
namespace: apf-demo
spec:
hard:
requests.nvidia.com/gpu: "16"
count/pods: "100"
count/jobs.batch: "50"
Quotas are not the number of creations allowed per second, nor do they guarantee that the cluster has corresponding idle GPUs. Excess creations are typically rejected rather than automatically queued; after a Job is allowed to be created, its child Pods may still fail to be created due to insufficient Pod quota. See ResourceQuota.
LimitRange can constrain the default, minimum, and maximum resource requirements of individual objects or containers. ValidatingAdmissionPolicy can check fields, labels, and specs, but cannot directly serve as a request rate limiter that depends on global counters. See LimitRange and ValidatingAdmissionPolicy.
7. GPU Task Queuing: Kueue and Volcano¶
"Queue now if no GPUs are available, allow borrowing when idle, and reclaim resources by policy when important tasks arrive" is workload admission and scheduling governance.
Kueue manages quotas and admission through objects such as LocalQueue and ClusterQueue, combined with corresponding workload integrations, topology configurations, and AdmissionChecks, to decide whether a task can start. Volcano Queue, combined with its scheduling system, provides resource queues and corresponding sharing and reclamation policies. See Kueue Admission and Volcano Queue.
| Symptom | Check First |
|---|---|
| API calls for creating and querying Jobs slow down the control plane | APF, client-side rate limiting, and Controller behavior |
| Many tasks waiting for GPUs, unfair allocation between teams | Queues, quotas, priorities, and borrowing policies |
| Quota is sufficient, but multi-GPU tasks cannot be placed | Device fragmentation, topology, Gang/TAS, and actual node resources |
| Urgent tasks need GPUs occupied by existing tasks | Queue/scheduler preemption and task recovery policies |
API request fairness and GPU allocation fairness are two separate mechanisms. The same workload can first successfully call the API, then wait for queue admission, and finally wait for node devices to meet conditions. Each layer should provide independent status and wait-time metrics. For more object relationships, see Queues, Fair Sharing, and Multi-Tenancy and GPU Scheduling.
8. Inference Rate Limiting: Beyond RPS, Also Consider Concurrency and Tokens¶
8.1 Identical Request Rates Do Not Equal Identical Backend Pressure¶
Short answers and long document generation occupy engine time differently. Under the premise that steady state is reachable and the statistical scope is consistent, 平均在途请求 ≈ 实际接纳速率 × 平均耗时 can be used to understand concurrency. If traffic persistently exceeds processing capacity, queues grow continuously, and the steady-state assumption no longer holds.
At an average admission rate of 20 req/s with 0.2-second latency, there are approximately 4 requests in flight; with 10-second latency, there are approximately 200. This example illustrates why long generations and streaming requests need concurrency control; it is not a measured model throughput result.
8.2 Gateway and Engine Jointly Control the Admission Boundary¶
Implementations such as Envoy Gateway provide local and global rate limiting. The scope of local counters is determined by the proxy configuration and cannot be directly treated as the total service-wide quota under multiple replicas; global rate limiting typically requires a shared rate-limiting service and state backend. BackendTrafficPolicy is an extension resource of a specific implementation, not a core API natively supported by all Gateways. See Local Rate Limiting and Global Rate Limiting.
| Constraint | Purpose |
|---|---|
| Tenant/API Key request rate | Prevent a single caller from continuously occupying the entry point |
| Input tokens, maximum output tokens | Bound the workload of a single request |
| Running requests/active streams | Control requests that occupy the backend for a long time |
| Waiting requests, maximum wait time | Avoid unbounded backlog and stale work |
| Model or tenant token budget | Make quotas closer to inference cost |
| Timeout, cancellation, and retry budget | Stop work that has lost its consumer, limit retry amplification |
Token quotas should clearly specify whether they account for estimated input, reserved maximum output, or actual post-generation settlement, because the final output length is unknown before the request. Streaming retries must also handle partial results already returned to the user; budgets at the entry point, client, and engine must be coordinated.
APF does not automatically adjust quotas based on model tokens, and Kubernetes Service has no general-purpose token rate-limiting capability. Prefix cache hits also change the actual compute cost of same-length requests; see KV Cache Overview and AI Gateway and Intelligent Routing.
9. Observability Metrics and Troubleshooting Paths¶
9.1 Is APF Actually Queueing or Rejecting¶
In addition to API Server CPU, also check rejections, queueing, and seat usage. Metrics are based on target version /metrics; see APF Metrics.
| Metric | Purpose |
|---|---|
apiserver_flowcontrol_rejected_requests_total |
Locate rejections by FlowSchema, priority level, and reason |
apiserver_flowcontrol_current_inqueue_requests |
Current number of queued requests |
apiserver_flowcontrol_request_wait_duration_seconds |
Wait time added by APF |
apiserver_flowcontrol_priority_level_seat_utilization |
Priority level seat usage |
Aggregate rejection rate by source and reason:
sum by (flow_schema, priority_level, reason) (
rate(apiserver_flowcontrol_rejected_requests_total[5m])
)
For classic Histogram, the P95 of queue wait time can be calculated:
histogram_quantile(
0.95,
sum by (le, priority_level) (
rate(apiserver_flowcontrol_request_wait_duration_seconds_bucket[5m])
)
)
The query assumes the collection targets do not have duplicate counting. Multi-cluster Prometheus should add cluster label filtering or grouping; percentiles may be unstable under low traffic. Queueing is only part of end-to-end latency; it should be combined with execution time, Webhook, and etcd metrics.
9.2 First Identify Which Layer the Error Comes From¶
| Symptom | Priority Check | Common Misjudgment |
|---|---|---|
| API 429, APF rejection count grows in sync | Priority level share, queues, and callers | Only increasing client retries |
| API slow, but APF queueing is not obvious | etcd, Webhook, execution cost, and network | Assuming APF rate limiting in all cases |
| Events decrease or writes fail | Event plugin, writers, and aggregation behavior | Treating event reduction as fault recovery |
| Creating Pod reports exceeded quota | ResourceQuota and current usage | Adjusting APF expecting to obtain GPU |
| Workload not admitted or Pod Pending | Queues, quotas, devices, and topology | Only checking API QPS |
| Model API 429 or long waits | Gateway, engine queues, concurrency, and tokens | Modifying API Server concurrency limits |
The same HTTP 429 can be returned by different components. The source should be confirmed by combining the actual request address, response headers, logs, and metrics before adjusting the corresponding layer's policy.
10. GPU Platform Deployment Recommendations and Material Notes¶
Governance responsibility can be divided into three parts:
- Control plane: Preserve protection for critical system traffic, use dedicated identities for batch programs; configure APF based on real request cost, and constrain query and retry behavior.
- Resource plane: Use Namespace quotas to express hard boundaries, use workload queues to express waiting, borrowing, and sharing; also check multi-card topology and actual capacity.
- Service entry point: Gateway and engine jointly manage rate, tokens, active requests, wait time, and cancellation propagation.
Thresholds should be adjusted based on actual load and latency targets. Increasing queues only delays rejection temporarily; raising concurrency may also amplify etcd, Webhook, or GPU pressure. Configuration needs to clearly specify when requests are admitted, how long they may wait at most, who returns failure, and whether retries create more work.
This document is accompanied by APF Examples and Notes and Diagram Generation Code. Static checks cannot replace server-side validation on the target cluster, rule-matching confirmation, and performance observation. This document has not applied any policies to running clusters, nor has it modified existing business traffic.