API 优先级和公平性
Kubernetes v1.29 [stable]
在过载情况下控制 Kubernetes API 服务器的行为是集群管理员的一项关键任务。kube-apiserver 具有一些可用控件(即 `—max-requests-inflight` 和 `—max-mutating-requests-inflight` 命令行参数)来限制将接受的未完成工作的数量,防止入站请求的洪水使 API 服务器过载并可能导致其崩溃,但这些标志不足以确保在流量高峰期最重要的请求能够通过。
API 优先级和公平性功能 (APF) 是一种替代方案,它改进了上述最大进行中请求的限制。APF 以更细粒度的方式对请求进行分类和隔离。它还引入了有限的队列,因此在非常短暂的突发情况下不会拒绝任何请求。请求使用公平排队技术从队列中分派,这样,例如,行为不当的控制器就不会饿死其他控制器(即使在同一优先级级别)。
此功能旨在与标准控制器配合良好,标准控制器使用 Informer 并通过指数退避来响应 API 请求失败,以及其他以这种方式工作的客户端。
注意
某些被分类为“长时间运行”的请求(例如远程命令执行或日志尾随)不受 API 优先级和公平性过滤器的限制。在未启用 API 优先级和公平性功能的情况下,`—max-requests-inflight` 标志也是如此。API 优先级和公平性**确实**适用于 **watch** 请求。当禁用 API 优先级和公平性时,**watch** 请求不受 `—max-requests-inflight` 限制。启用/禁用 API 优先级和公平性
API 优先级和公平性功能由命令行参数控制,并且默认启用。有关可用 kube-apiserver 命令行选项及其启用和禁用方式的通用说明,请参阅选项。APF 的命令行选项名称是“—enable-priority-and-fairness”。此功能还涉及一个API 组,其中包含:(a) 稳定的 `v1` 版本,在 1.29 中引入,默认启用;(b) `v1beta3` 版本,默认启用,并在 v1.29 中弃用。您可以通过向 `kube-apiserver` 调用添加以下命令行参数来禁用 API 组 Beta 版本 `v1beta3`
kube-apiserver \
--runtime-config=flowcontrol.apiserver.k8s.io/v1beta3=false \
# …and other flags as usual
命令行参数 `—enable-priority-and-fairness=false` 将禁用 API 优先级和公平性功能。
递归服务器场景
在递归服务器场景中必须谨慎使用 API 优先级和公平性。这些场景是指服务器 A 在处理请求时,向服务器 B 发出辅助请求。服务器 B 甚至可能进一步向服务器 A 发出辅助调用。在优先级和公平性控制应用于原始请求和某些辅助请求(无论递归深度如何)的情况下,存在优先级倒置和/或死锁的危险。
递归的一个例子是 `kube-apiserver` 向服务器 B 发出准入 Webhook 调用,在处理该调用时,服务器 B 进一步向 `kube-apiserver` 发出辅助请求。递归的另一个例子是 `APIService` 对象指示 `kube-apiserver` 将有关某个 API 组的请求委托给自定义外部服务器 B(这是“聚合”之一)。
当已知原始请求属于某个优先级级别,并且辅助受控请求被分类到更高的优先级级别时,这是一种可能的解决方案。当原始请求可以属于任何优先级级别时,辅助受控请求必须免受优先级和公平性限制。一种方法是使用下面讨论的配置分类和处理的对象。另一种方法是完全禁用服务器 B 上的优先级和公平性,使用上面讨论的技术。第三种方法是当服务器 B 不是 `kube-apisever` 时最简单的使用方法,即在代码中禁用服务器 B 的优先级和公平性。
概念
API 优先级和公平性功能涉及几个不同的特性。传入请求通过FlowSchema根据请求属性进行分类,并分配给优先级级别。优先级级别通过维护独立的并发限制来增加一定程度的隔离,从而使分配给不同优先级级别的请求不会相互饿死。在一个优先级级别内,公平排队算法防止不同流的请求相互饿死,并允许请求排队以防止突发流量在平均负载可接受的低时导致请求失败。
优先级级别
未启用 APF 时,API 服务器中的总体并发由 `kube-apiserver` 标志 `--max-requests-inflight` 和 `--max-mutating-requests-inflight` 限制。启用 APF 后,这些标志定义的并发限制会求和,然后将总和分配给一组可配置的优先级级别。每个传入请求都被分配到一个优先级级别,每个优先级级别只分派其特定限制允许的并发请求数量。
例如,默认配置包括用于领导选举请求、来自内置控制器的请求以及来自 Pods 的请求的单独优先级级别。这意味着行为不当的 Pod 用请求淹没 API 服务器时,不会阻止领导选举或内置控制器的操作成功。
优先级级别的并发限制会定期调整,允许未充分利用的优先级级别暂时将并发借给充分利用的级别。这些限制基于名义限制以及优先级级别可以借出和借入多少并发的上限,所有这些都来自下面提到的配置对象。
请求占用的座位
上面关于并发管理的描述是基线故事。请求具有不同的持续时间,但在任何给定时刻与优先级级别的并发限制进行比较时,它们的计数是相等的。在基线故事中,每个请求占用一个并发单元。“座位”这个词用于表示一个并发单元,灵感来自于火车或飞机上的每位乘客占用一个固定供应的座位的方式。
但是有些请求占用不止一个座位。其中一些是服务器估计会返回大量对象的**列表**请求。已发现这些请求给服务器带来了异常沉重的负担。因此,服务器会估计将返回的对象数量,并认为请求占用的座位数量与该估计数量成比例。
watch 请求的执行时间调整
API 优先级和公平性管理 **watch** 请求,但这涉及到一些偏离基线行为的额外情况。首先是 **watch** 请求占用其座位的时间。根据请求参数,对 **watch** 请求的响应可能以或不以所有相关预先存在对象的 **create** 通知开始。API 优先级和公平性认为 **watch** 请求在其初始突发通知(如果有)结束后就完成了其座位占用。
每当服务器收到对象创建/更新/删除通知时,正常通知就会并发地发送到所有相关的 **watch** 响应流。为了考虑这项工作,API 优先级和公平性认为每个写入请求在实际写入完成后会额外占用一些时间。服务器估计将发送的通知数量,并调整写入请求的座位数量和座位占用时间以包括这项额外工作。
排队
即使在同一优先级级别内,也可能存在大量不同的流量来源。在过载情况下,防止一个请求流饿死其他请求(特别是,在单个有缺陷的客户端用请求淹没 kube-apiserver 的相对常见情况下,理想情况下,该有缺陷的客户端对其他客户端几乎没有可衡量的影响)是很有价值的。这通过使用公平排队算法来处理分配给同一优先级级别的请求来解决。每个请求都被分配到一个流,该流由匹配的 FlowSchema 的名称加上一个流区分符(即请求用户、目标资源的命名空间或无)标识,并且系统尝试对同一优先级级别中不同流的请求给予大致相等的权重。为了实现不同实例的独立处理,具有多个实例的控制器应该使用不同的用户名进行认证。
将请求分类到流后,API 优先级和公平性功能可能会将请求分配到队列。这种分配使用一种称为随机分片的技术,该技术能相对高效地利用队列来隔离低强度流与高强度流。
排队算法的细节可以针对每个优先级级别进行调整,允许管理员权衡内存使用、公平性(当总流量超过容量时,独立流都将取得进展的特性)、对突发流量的容忍度以及排队引起的额外延迟。
豁免请求
有些请求被认为是足够重要的,因此它们不受此功能施加的任何限制。这些豁免可以防止配置不当的流控制配置完全禁用 API 服务器。
资源
流控制 API 涉及两种资源。PriorityLevelConfiguration 定义了可用的优先级级别、每个级别可以处理的可用并发预算份额,并允许微调排队行为。FlowSchema 用于分类单个入站请求,将每个请求匹配到单个 PriorityLevelConfiguration。
PriorityLevelConfiguration
PriorityLevelConfiguration 表示单个优先级级别。每个 PriorityLevelConfiguration 对未完成请求的数量和排队请求的数量都有独立的限制。
PriorityLevelConfiguration 的名义并发限制不是以绝对的座位数指定,而是以“名义并发份额”指定。API 服务器的总并发限制按照这些份额的比例分配给现有的 PriorityLevelConfiguration,以便为每个级别提供其名义座位限制。这允许集群管理员通过重新启动 `kube-apiserver` 并为 `--max-requests-inflight`(或 `--max-mutating-requests-inflight`)设置不同的值来增加或减少服务器的总流量,并且所有 PriorityLevelConfiguration 都将看到其最大允许并发以相同的比例增加(或减少)。
注意
在 `v1beta3` 之前的版本中,相关的 PriorityLevelConfiguration 字段名为“assured concurrency shares”而不是“nominal concurrency shares”。此外,在 Kubernetes 1.25 及更早版本中没有定期调整:名义/保证限制始终在不进行调整的情况下应用。优先级级别可以借出和借入多少并发的上限在 PriorityLevelConfiguration 中表示为该级别名义限制的百分比。通过将名义限制/100.0 相乘并四舍五入,将其解析为绝对的座位数。优先级级别的动态调整并发限制被限制在(a)其名义限制减去其可借出座位数的下限和(b)其名义限制加上其可借入座位数的上限之间。每次调整时,动态限制由每个优先级级别回收最近出现需求的所有已借出座位,然后在上述限制内共同公平地响应优先级级别的最近座位需求。
注意
启用优先级和公平性功能后,服务器的总并发限制设置为 `—max-requests-inflight` 和 `—max-mutating-requests-inflight` 的总和。不再区分变异请求和非变异请求;如果您希望对给定资源分别处理它们,请创建分别匹配变异和非变异动词的单独 FlowSchema。当分配给单个 PriorityLevelConfiguration 的入站请求量超过其允许的并发级别时,其规范的 `type` 字段决定了额外请求的处理方式。类型为 `Reject` 意味着超出流量将立即被拒绝,并返回 HTTP 429(请求过多)错误。类型为 `Queue` 意味着超过阈值的请求将被排队,并使用随机分片和公平排队技术来平衡请求流之间的进度。
排队配置允许对优先级级别的公平排队算法进行微调。该算法的详细信息可以在增强提案中阅读,简而言之,就是:
增加 `queues` 会降低不同流之间的冲突率,但会增加内存使用量。此处的值为 1 实际上会禁用公平排队逻辑,但仍允许请求排队。
增加 `queueLengthLimit` 允许在不丢弃任何请求的情况下维持更大的突发流量,但会增加延迟和内存使用量。
更改 `handSize` 允许您调整不同流之间发生冲突的概率以及在过载情况下单个流可用的总体并发性。
注意
较大的 `handSize` 使两个独立的流发生冲突(从而一个流能够饿死另一个流)的可能性较小,但使少量流能够主导 API 服务器的可能性较大。较大的 `handSize` 也可能增加单个高流量流可能造成的延迟量。单个流可能排队的最大请求数是 `handSize * queueLengthLimit`。
下表显示了一个有趣的洗牌分片配置集合,显示了对于每个配置,在说明性的大象(高强度流)数量下,给定老鼠(低强度流)被大象压扁的概率。请参阅 https://play.golang.org/p/Gi0PLgVHiUg,它计算了此表。
HandSize | 队列 | 1 只大象 | 4 只大象 | 16 只大象 |
---|---|---|---|---|
12 | 32 | 4.428838398950118e-09 | 0.11431348830099144 | 0.9935089607656024 |
10 | 32 | 1.550093439632541e-08 | 0.0626479840223545 | 0.9753101519027554 |
10 | 64 | 6.601827268370426e-12 | 0.00045571320990370776 | 0.49999929150089345 |
9 | 64 | 3.6310049976037345e-11 | 0.00045501212304112273 | 0.4282314876454858 |
8 | 64 | 2.25929199850899e-10 | 0.0004886697053040446 | 0.35935114681123076 |
8 | 128 | 6.994461389026097e-13 | 3.4055790161620863e-06 | 0.02746173137155063 |
7 | 128 | 1.0579122850901972e-11 | 6.960839379258192e-06 | 0.02406157386340147 |
7 | 256 | 7.597695465552631e-14 | 6.728547142019406e-08 | 0.0006709661542533682 |
6 | 256 | 2.7134626662687968e-12 | 2.9516464018476436e-07 | 0.0008895654642000348 |
6 | 512 | 4.116062922897309e-14 | 4.982983350480894e-09 | 2.26025764343413e-05 |
6 | 1024 | 6.337324016514285e-16 | 8.09060164312957e-11 | 4.517408062903668e-07 |
FlowSchema
FlowSchema 匹配一些入站请求并将它们分配给一个优先级级别。每个入站请求都会针对 FlowSchema 进行测试,从数字上最低的 `matchingPrecedence` 开始,然后向上进行。第一个匹配的 FlowSchema 获胜。
注意
对于给定请求,只有第一个匹配的 FlowSchema 才重要。如果多个 FlowSchema 匹配单个入站请求,它将根据具有最高 `matchingPrecedence` 的那个进行分配。如果多个具有相同 `matchingPrecedence` 的 FlowSchema 匹配同一请求,则 `name` 在字典顺序上较小的 FlowSchema 将获胜,但最好不要依赖此行为,而是确保没有两个 FlowSchema 具有相同的 `matchingPrecedence`。如果 FlowSchema 的至少一个 `rules` 匹配,则该 FlowSchema 匹配给定的请求。如果其至少一个 `subjects` **以及**至少一个 `resourceRules` 或 `nonResourceRules`(取决于传入请求是针对资源还是非资源 URL)匹配请求,则该规则匹配。
对于 subject 中的 `name` 字段,以及资源和非资源规则的 `verbs`、`apiGroups`、`resources`、`namespaces` 和 `nonResourceURLs` 字段,可以指定通配符 `*` 来匹配给定字段的所有值,从而有效地将其从考虑中移除。
FlowSchema 的 `distinguisherMethod.type` 决定了匹配该 schema 的请求如何分离到流中。它可以是 `ByUser`,在这种情况下,一个请求用户将无法饿死其他用户的容量;`ByNamespace`,在这种情况下,一个命名空间中资源的请求将无法饿死其他命名空间中资源的容量;或者为空(或 `distinguisherMethod` 可以完全省略),在这种情况下,所有由该 FlowSchema 匹配的请求都将被视为单个流的一部分。给定 FlowSchema 的正确选择取决于资源和您的特定环境。
默认值
每个 kube-apiserver 都维护两种 APF 配置对象:强制性配置对象和建议性配置对象。
强制性配置对象
这四个强制配置对象反映了固定的内置防护行为。在这些对象存在之前,服务器就具有这种行为,当这些对象存在时,它们的规范反映了这种行为。这四个强制对象如下。
强制性的 `exempt` 优先级级别用于完全不受流控制的请求:它们将始终立即分派。强制性的 `exempt` FlowSchema 将来自 `system:masters` 组的所有请求分类到此优先级级别。如果适用,您可以定义其他 FlowSchema,将其他请求定向到此优先级级别。
强制性的 `catch-all` 优先级级别与强制性的 `catch-all` FlowSchema 结合使用,以确保每个请求都能得到某种分类。通常,您不应依赖此全捕获配置,而应创建自己的全捕获 FlowSchema 和 PriorityLevelConfiguration(或使用默认安装的建议的 `global-default` 优先级级别)。由于不期望正常使用,强制性的 `catch-all` 优先级级别具有非常小的并发份额,并且不排队请求。
建议的配置对象
建议的 FlowSchema 和 PriorityLevelConfiguration 构成了合理的默认配置。如果您愿意,可以修改这些或创建额外的配置对象。如果您的集群可能会遇到高负载,您应该考虑哪种配置效果最好。
建议配置将请求分组为六个优先级级别
`node-high` 优先级级别用于节点的健康更新。
`system` 优先级级别用于来自 `system:nodes` 组(即 Kubelets)的非健康请求,它们必须能够联系 API 服务器才能在其上调度工作负载。
`leader-election` 优先级级别用于内置控制器(特别是来自 `system:kube-controller-manager` 或 `system:kube-scheduler` 用户以及 `kube-system` 命名空间中的服务账号的 `endpoints`、`configmaps` 或 `leases` 请求)的领导选举请求。将这些请求与其他流量隔离非常重要,因为领导选举失败会导致其控制器失败并重新启动,这反过来又会随着新控制器同步其 informer 而导致更昂贵的流量。
`workload-high` 优先级级别用于来自内置控制器的其他请求。
`workload-low` 优先级级别用于来自任何其他服务帐户的请求,通常包括来自 Pod 中运行的所有控制器的请求。
`global-default` 优先级级别处理所有其他流量,例如非特权用户运行的交互式 `kubectl` 命令。
建议的 FlowSchema 用于将请求导向上述优先级级别,此处不再赘述。
强制性配置对象和建议性配置对象的维护
每个 `kube-apiserver` 使用初始和周期性行为独立维护强制性和建议性配置对象。因此,在不同版本服务器混合的情况下,只要不同服务器对这些对象的正确内容有不同意见,就可能会出现抖动。
每个 `kube-apiserver` 对强制性和建议性配置对象进行初始维护,之后对这些对象进行定期维护(每分钟一次)。
对于强制配置对象,维护包括确保对象存在,并且如果存在,则具有正确的规范。服务器拒绝允许创建或更新与服务器防护行为不一致的规范。
建议配置对象的维护旨在允许覆盖其规范。另一方面,删除不被尊重:维护将恢复该对象。如果您不想要建议的配置对象,则需要保留它但将其规范设置为具有最小影响。建议对象的维护还旨在支持在推出新版本 `kube-apiserver` 时自动迁移,尽管在服务器混合存在时可能会出现抖动。
建议配置对象的维护包括:如果对象不存在,则使用服务器建议的规范创建它。另一方面,如果对象已经存在,则维护行为取决于 `kube-apiservers` 或用户是否控制该对象。在前一种情况下,服务器确保对象的规范是服务器建议的;在后一种情况下,规范保持不变。
谁控制对象的答案首先通过查找键为 `apf.kubernetes.io/autoupdate-spec` 的注解来回答。如果存在这样的注解且其值为 `true`,则 kube-apiservers 控制该对象。如果存在这样的注解且其值为 `false`,则用户控制该对象。如果以上两种情况都不成立,则会查看对象的 `metadata.generation`。如果其值为 1,则 kube-apiservers 控制该对象。否则,用户控制该对象。这些规则是在 1.22 版本中引入的,其对 `metadata.generation` 的考虑是为了从更简单的早期行为进行迁移。希望控制建议配置对象的用户应将其 `apf.kubernetes.io/autoupdate-spec` 注解设置为 `false`。
强制或建议配置对象的维护还包括确保其具有 `apf.kubernetes.io/autoupdate-spec` 注解,该注解准确反映 kube-apiservers 是否控制该对象。
维护还包括删除既不是强制也不是建议但被注解为 `apf.kubernetes.io/autoupdate-spec=true` 的对象。
健康检查并发豁免
建议配置并未对 kube-apiserver 上来自其本地 kubelet 的健康检查请求提供特殊处理——这些请求倾向于使用安全端口但未提供凭据。在建议配置下,这些请求被分配给 `global-default` FlowSchema 和相应的 `global-default` 优先级级别,在那里其他流量可能会将其挤出。
如果您添加以下额外的 FlowSchema,这将使这些请求免受速率限制。
注意
做出此更改也允许任何恶意方以其喜欢的任何量发送匹配此 FlowSchema 的健康检查请求。如果您有 Web 流量过滤器或类似的外部安全机制来保护您的集群 API 服务器免受一般 Internet 流量的影响,您可以配置规则来阻止源自集群外部的任何健康检查请求。apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
name: health-for-strangers
spec:
matchingPrecedence: 1000
priorityLevelConfiguration:
name: exempt
rules:
- nonResourceRules:
- nonResourceURLs:
- "/healthz"
- "/livez"
- "/readyz"
verbs:
- "*"
subjects:
- kind: Group
group:
name: "system:unauthenticated"
可观测性
指标
注意
在 Kubernetes v1.20 之前的版本中,标签 `flow_schema` 和 `priority_level` 分别不一致地命名为 `flowSchema` 和 `priorityLevel`。如果您运行的是 Kubernetes v1.19 及更早版本,则应参考您版本的文档。启用 API 优先级和公平性功能后,kube-apiserver 会导出额外的指标。监控这些指标可以帮助您确定您的配置是否不适当地限制了重要流量,或者发现可能损害系统健康的、行为不当的工作负载。
成熟度级别 BETA
`apiserver_flowcontrol_rejected_requests_total` 是一个计数器向量(自服务器启动以来的累计值),表示被拒绝的请求,按 `flow_schema`(指示匹配请求的 FlowSchema)、`priority_level`(指示分配给请求的优先级级别)和 `reason` 标签细分。`reason` 标签将是以下值之一:
- `queue-full`,表示排队请求过多。
- `concurrency-limit`,表示 PriorityLevelConfiguration 配置为拒绝而不是排队额外请求。
- `time-out`,表示请求在队列中时其排队时间限制已到期。
- `cancelled`,表示请求未被清除锁定并已从队列中弹出。
`apiserver_flowcontrol_dispatched_requests_total` 是一个计数器向量(自服务器启动以来的累积值),表示已开始执行的请求,按 `flow_schema` 和 `priority_level` 细分。
`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` 细分。
`apiserver_flowcontrol_request_wait_duration_seconds` 是一个直方图向量,表示请求在队列中等待的时间,按 `flow_schema`、`priority_level` 和 `execute` 标签细分。`execute` 标签指示请求是否已开始执行。
注意
由于每个 FlowSchema 始终将请求分配给单个 PriorityLevelConfiguration,您可以将一个优先级级别所有 FlowSchema 的直方图相加,以获得分配给该优先级级别请求的有效直方图。`apiserver_flowcontrol_nominal_limit_seats` 是一个计量向量,保存每个优先级级别的名义并发限制,该限制由 API 服务器的总并发限制和优先级级别配置的名义并发份额计算得出。
成熟度级别 ALPHA
`apiserver_current_inqueue_requests` 是一个计量向量,表示最近队列请求数的最高水位线,按名为 `request_kind` 的标签分组,其值为 `mutating` 或 `readOnly`。这些最高水位线描述了最近完成的一秒窗口中观察到的最大数量。它们补充了旧的 `apiserver_current_inflight_requests` 计量向量,后者保存了上一个窗口中正在主动服务的请求数的最高水位线。
`apiserver_current_inqueue_seats` 是一个计量向量,表示排队请求中每个请求将占用的最大座位数的总和,按标签 `flow_schema` 和 `priority_level` 分组。
`apiserver_flowcontrol_read_vs_write_current_requests` 是一个直方图向量,每纳秒末对请求数量进行观测,按 `phase` 标签(取值 `waiting` 和 `executing`)和 `request_kind` 标签(取值 `mutating` 和 `readOnly`)细分。每个观测值都是一个比率,介于 0 到 1 之间,表示请求数量除以相应的请求限制(等待的队列容量限制和执行的并发限制)。
`apiserver_flowcontrol_request_concurrency_in_use` 是一个计量向量,保存瞬时占用的座位数,按 `priority_level` 和 `flow_schema` 细分。
`apiserver_flowcontrol_priority_level_request_utilization` 是一个直方图向量,每纳秒末对请求数量进行观测,按 `phase` 标签(取值 `waiting` 和 `executing`)和 `priority_level` 细分。每个观测值都是一个比率,介于 0 到 1 之间,表示请求数量除以相应的请求限制(等待的队列容量限制和执行的并发限制)。
`apiserver_flowcontrol_priority_level_seat_utilization` 是一个直方图向量,每纳秒末对优先级级别的并发限制利用率进行观测,按 `priority_level` 细分。此利用率是(占用座位数)/(并发限制)的比例。此指标考虑所有请求(除了 WATCH 请求)的执行的所有阶段(正常阶段和写入结束时为相应通知工作而进行的额外延迟);对于 WATCH 请求,它仅考虑传递预先存在对象通知的初始阶段。向量中的每个直方图也用 `phase: executing` 标记(等待阶段没有座位限制)。
`apiserver_flowcontrol_request_queue_length_after_enqueue` 是一个直方图向量,表示队列的队列长度,按 `priority_level` 和 `flow_schema` 细分,由排队请求采样。每个排队请求都会为其直方图贡献一个样本,报告请求添加后队列的长度。请注意,这会产生与无偏调查不同的统计数据。
注意
这里直方图中的异常值意味着可能存在单个流(即,一个用户或一个命名空间的请求,取决于配置)正在淹没 API 服务器,并被限制。相反,如果一个优先级级别的直方图显示该优先级级别的所有队列都比其他优先级级别的队列长,则可能适合增加该 PriorityLevelConfiguration 的并发份额。`apiserver_flowcontrol_request_concurrency_limit` 与 `apiserver_flowcontrol_nominal_limit_seats` 相同。在优先级级别之间引入并发借用之前,这始终等于 `apiserver_flowcontrol_current_limit_seats`(它不存在作为独立指标)。
`apiserver_flowcontrol_lower_limit_seats` 是一个计量向量,保存每个优先级级别动态并发限制的下限。
`apiserver_flowcontrol_upper_limit_seats` 是一个计量向量,保存每个优先级级别动态并发限制的上限。
`apiserver_flowcontrol_demand_seats` 是一个直方图向量,每纳秒末对每个优先级级别(座位需求)/(名义并发限制)的比率进行计数。优先级级别的座位需求是排队请求和处于执行初始阶段的请求中,请求在初始和最终执行阶段占用的最大座位数之和。
`apiserver_flowcontrol_demand_seats_high_watermark` 是一个计量向量,为每个优先级级别保存上次并发借用调整期间观察到的最大座位需求。
`apiserver_flowcontrol_demand_seats_average` 是一个计量向量,为每个优先级级别保存上次并发借用调整期间观察到的时间加权平均座位需求。
`apiserver_flowcontrol_demand_seats_stdev` 是一个计量向量,为每个优先级级别保存上次并发借用调整期间观察到的座位需求的时间加权总体标准差。
`apiserver_flowcontrol_demand_seats_smoothed` 是一个计量向量,为每个优先级级别保存上次并发调整时确定的平滑包络座位需求。
`apiserver_flowcontrol_target_seats` 是一个计量向量,为每个优先级级别保存借用分配问题中的并发目标。
`apiserver_flowcontrol_seat_fair_frac` 是一个计量,保存上次借用调整中确定的公平分配分数。
`apiserver_flowcontrol_current_limit_seats` 是一个计量向量,为每个优先级级别保存上次调整中派生的动态并发限制。
`apiserver_flowcontrol_request_execution_seconds` 是一个直方图向量,表示请求实际执行的时间,按 `flow_schema` 和 `priority_level` 细分。
`apiserver_flowcontrol_watch_count_samples` 是一个直方图向量,表示与给定写入相关的活动 WATCH 请求的数量,按 `flow_schema` 和 `priority_level` 细分。
`apiserver_flowcontrol_work_estimated_seats` 是一个直方图向量,表示与请求相关的估计座位数(执行的初始阶段和最终阶段的最大值),按 `flow_schema` 和 `priority_level` 细分。
`apiserver_flowcontrol_request_dispatch_no_accommodation_total` 是一个计数器向量,表示原则上可能导致请求被分派但由于缺乏可用并发而未分派的事件数量,按 `flow_schema` 和 `priority_level` 细分。
`apiserver_flowcontrol_epoch_advance_total` 是一个计数器向量,表示将优先级级别的进度计量器向后跳转以避免数值溢出的尝试次数,按 `priority_level` 和 `success` 分组。
使用 API 优先级和公平性的最佳实践
当给定优先级级别超过其允许的并发量时,请求可能会经历增加的延迟,或者被 HTTP 429(请求过多)错误拒绝。为了防止 APF 的这些副作用,您可以修改工作负载或调整 APF 设置,以确保有足够的座位来处理您的请求。
要检测请求是否因 APF 而被拒绝,请检查以下指标:
- apiserver_flowcontrol_rejected_requests_total:每个 FlowSchema 和 PriorityLevelConfiguration 被拒绝的请求总数。
- apiserver_flowcontrol_current_inqueue_requests:每个 FlowSchema 和 PriorityLevelConfiguration 当前排队的请求数量。
- apiserver_flowcontrol_request_wait_duration_seconds:排队等待请求的额外延迟。
- apiserver_flowcontrol_priority_level_seat_utilization:每个 PriorityLevelConfiguration 的座位利用率。
工作负载修改
为了防止请求因 APF 而排队增加延迟或被拒绝,您可以通过以下方式优化请求:
- 降低请求执行的速率。在固定时间内较少的请求将导致在给定时间需要较少的座位。
- 避免同时发出大量昂贵的请求。请求可以优化为使用更少的座位或具有更低的延迟,以便这些请求占用座位的时间更短。列表请求可能占用超过 1 个座位,具体取决于请求期间获取的对象数量。限制列表请求中检索的对象数量,例如通过使用分页,将在更短的时间内使用更少的总座位。此外,用 watch 请求替换列表请求将需要更低的并发份额,因为 watch 请求在其初始突发通知期间仅占用 1 个座位。如果使用 1.27 及更高版本中的流式列表,watch 请求将在其初始突发通知期间占用与列表请求相同的座位数,因为必须流式传输整个集合的状态。请注意,在两种情况下,watch 请求在此初始阶段之后都不会占用任何座位。
请记住,APF 引起的排队或被拒绝的请求可能是由请求数量增加或现有请求延迟增加引起的。例如,如果通常需要 1 秒执行的请求开始需要 60 秒,则 APF 可能会开始拒绝请求,因为请求由于延迟增加而占用座位的时间比正常情况更长。如果 APF 在工作负载没有显著变化的情况下开始拒绝跨多个优先级级别的请求,则可能是控制平面性能存在潜在问题,而不是工作负载或 APF 设置的问题。
优先级和公平性设置
您还可以修改默认的 FlowSchema 和 PriorityLevelConfiguration 对象,或者创建这些类型的新对象,以更好地适应您的工作负载。
APF 设置可以修改为:
- 为高优先级请求提供更多座位。
- 隔离非必要或昂贵的请求,这些请求如果与其他流共享并发级别,则会耗尽该并发级别。
为高优先级请求提供更多座位
- 如果可能,可以通过增加 `max-requests-inflight` 和 `max-mutating-requests-inflight` 标志的值来增加特定 `kube-apiserver` 所有优先级级别的可用座位数。或者,横向扩展 `kube-apiserver` 实例的数量将增加集群中每个优先级级别的总并发量,前提是请求的负载均衡足够。
- 您可以创建一个新的 FlowSchema,它引用一个具有较大并发级别的 PriorityLevelConfiguration。这个新的 PriorityLevelConfiguration 可以是现有级别,也可以是一个拥有自己名义并发份额的新级别。例如,可以引入一个新的 FlowSchema,将您的请求的 PriorityLevelConfiguration 从 global-default 更改为 workload-low,以增加您用户可用的座位数量。创建一个新的 PriorityLevelConfiguration 将减少指定给现有级别的座位数量。请注意,编辑默认的 FlowSchema 或 PriorityLevelConfiguration 需要将 `apf.kubernetes.io/autoupdate-spec` 注解设置为 false。
- 您还可以增加为高优先级请求提供服务的 PriorityLevelConfiguration 的 NominalConcurrencyShares。或者,对于 1.26 及更高版本,您可以增加竞争优先级级别的 LendablePercent,以便给定的优先级级别拥有更高的可借用座位池。
隔离非必要请求,使其不耗尽其他流
为了进行请求隔离,您可以创建一个 FlowSchema,其主体匹配发出这些请求的用户,或者创建一个匹配请求内容(对应于 resourceRules)的 FlowSchema。接下来,您可以将此 FlowSchema 映射到一个具有少量席位的 PriorityLevelConfiguration。
例如,假设来自 default 命名空间中运行的 Pods 的列表事件请求每个使用 10 个座位并执行 1 分钟。为了防止这些昂贵的请求影响使用现有服务帐户 FlowSchema 的其他 Pod 的请求,您可以应用以下 FlowSchema 来隔离这些列表调用与其他请求。
用于隔离列表事件请求的示例 FlowSchema 对象
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
name: list-events-default-service-account
spec:
distinguisherMethod:
type: ByUser
matchingPrecedence: 8000
priorityLevelConfiguration:
name: catch-all
rules:
- resourceRules:
- apiGroups:
- '*'
namespaces:
- default
resources:
- events
verbs:
- list
subjects:
- kind: ServiceAccount
serviceAccount:
name: default
namespace: default
- 此 FlowSchema 捕获默认命名空间中由默认服务帐户进行的所有列表事件调用。匹配优先级 8000 低于现有 `service-accounts` FlowSchema 使用的 9000 值,因此这些列表事件调用将匹配 `list-events-default-service-account` 而不是 `service-accounts`。
- 全捕获 PriorityLevelConfiguration 用于隔离这些请求。全捕获优先级级别具有非常小的并发份额,并且不排队请求。
下一步
- 您可以访问流控制参考文档以了解更多故障排除信息。
- 有关 API 优先级和公平性设计背景信息,请参阅增强提案。
- 您可以通过 SIG API Machinery 或该功能的 Slack 频道提出建议和功能请求。