API 优先级与公平性
Kubernetes v1.29 [stable]
在过载情况下控制 Kubernetes API 服务器的行为是集群管理员的一项关键任务。 kube-apiserver 提供了一些控制手段(例如命令行参数 --max-requests-inflight
和 --max-mutating-requests-inflight
)来限制将被接受的未完成工作量,防止入站请求洪流使 API 服务器过载并可能导致崩溃,但这些标志不足以确保在流量高峰期最重要的请求能够通过。
API 优先级与公平性(APF)特性是一个改进上述 max-inflight 限制的替代方案。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) 在 1.29 中引入的稳定的 v1
版本,默认启用;(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-apiserver
时最简单,是在代码中构建服务器 B 时禁用优先级与公平性。
概念
API 优先级与公平性特性涉及几个不同的功能。入站请求使用 *FlowSchemas* 根据请求的属性进行分类,并分配到优先级级别。优先级级别通过维护独立的并发限制来增加隔离度,以便分配到不同优先级级别的请求不会相互饿死。在同一优先级级别内,公平排队算法可防止不同 *流* 中的请求相互饿死,并允许请求排队,以防止在平均负载可接受地低时,突发流量导致请求失败。
优先级级别
未启用 APF 时,API 服务器的总并发受 kube-apiserver
标志 --max-requests-inflight
和 --max-mutating-requests-inflight
的限制。启用 APF 后,这些标志定义的并发限制会被求和,然后总和被分配给一组可配置的*优先级级别*。每个入站请求被分配到一个单一的优先级级别,每个优先级级别仅调度其特定限制所允许的并发请求数量。
例如,默认配置包括用于领导者选举请求、来自内置控制器的请求以及来自 Pod 的请求的单独优先级级别。这意味着行为不端的 Pod 向 API 服务器发送大量请求不会阻止领导者选举或内置控制器执行成功。
优先级级别的并发限制会周期性地调整,允许利用率低的优先级级别暂时将并发借给利用率高的级别。这些限制基于名义限制以及优先级级别可以借出和借入多少并发的边界,所有这些都源自下面提到的配置对象。
请求占用的“座位”
上述并发管理描述是基本情况。请求有不同的持续时间,但在任何给定时刻与优先级级别的并发限制进行比较时,它们都被同等计算。在基本情况中,每个请求占用一个并发单位。词语“座位”(seat)用于表示一个并发单位,灵感来自于火车或飞机上的每个乘客都占用一个固定供应的座位。
但有些请求会占用不止一个座位。其中一些是 **list** 请求,服务器估计会返回大量对象。这些请求已被发现会给服务器带来异常沉重的负担。因此,服务器会估计将返回的对象数量,并认为该请求占用的座位数与估计数量成比例。
watch 请求的执行时间调整
API 优先级与公平性管理 **watch** 请求,但这涉及对基线行为进行一些额外的调整。第一个调整涉及 **watch** 请求被认为占用其座位的时间长度。根据请求参数,**watch** 请求的响应可能以也可能不以所有相关现有对象的 **create** 通知开始。API 优先级与公平性认为 **watch** 请求一旦完成其最初的通知突发(如果有),就释放其座位。
当服务器收到对象创建/更新/删除的通知时,常规通知会并发突发地发送给所有相关的 **watch** 响应流。为了考虑这项工作,API 优先级与公平性认为每个写请求在实际写入完成后,会额外占用一些时间来占用座位。服务器会估算需要发送的通知数量,并调整写请求的座位数和座位占用时间以包含这项额外工作。
排队
即使在同一优先级级别内,也可能有大量不同的流量来源。在过载情况下,防止一个请求流饿死其他请求流非常重要(特别是对于一个有 Bug 的客户端向 kube-apiserver 发送大量请求这种相对常见的情况,理想情况下,这个有 Bug 的客户端不应对其他客户端产生可衡量的影响)。这通过使用公平排队算法来处理分配到同一优先级级别的请求来实现。每个请求被分配到一个*流*,由匹配的 FlowSchema 名称加上一个*流区分符*标识——流区分符可以是请求用户、目标资源的命名空间,或者什么都没有——系统会尝试对同一优先级级别内不同流中的请求给予大致相同的权重。为了实现对不同实例的区分处理,拥有多个实例的控制器应使用不同的用户名进行认证。
将请求分类到一个流之后,API 优先级与公平性特性可能会将请求分配到一个队列。这种分配使用一种称为shuffle sharding(洗牌分片)的技术,该技术相对高效地利用队列来隔离低强度流与高强度流。
排队算法的细节可以针对每个优先级级别进行调优,允许管理员在内存使用、公平性(当总流量超过容量时,独立流都能取得进展的属性)、突发流量的容忍度以及排队引起的额外延迟之间进行权衡。
豁免请求
一些请求被认为足够重要,不受此特性施加的任何限制。这些豁免可防止配置不当的流控制配置完全禁用 API 服务器。
资源
流控制 API 涉及两种资源。 PriorityLevelConfigurations 定义了可用的优先级级别、每个级别可以处理的可用并发预算的份额,并允许精细调整排队行为。FlowSchemas 用于对每个入站请求进行分类,将每个请求匹配到一个 PriorityLevelConfiguration。
PriorityLevelConfiguration
一个 PriorityLevelConfiguration 代表一个单一的优先级级别。每个 PriorityLevelConfiguration 对未完成请求数量有独立限制,并且对排队请求数量有限制。
PriorityLevelConfiguration 的名义并发限制不是以绝对的座位数量指定,而是以“名义并发份额”指定。API 服务器的总并发限制根据这些份额按比例分配给现有的 PriorityLevelConfiguration,以座位数的形式为每个级别设定其名义限制。这允许集群管理员通过使用不同的 --max-requests-inflight
(或 --max-mutating-requests-inflight
)值重新启动 kube-apiserver
来按比例增加或减少服务器的总流量,并且所有 PriorityLevelConfiguration 将看到其最大允许并发以相同的比例增加(或减少)。
注意
在v1beta3
之前的版本中,相关的 PriorityLevelConfiguration 字段名为“assured concurrency shares”(保证并发份额)而不是“nominal concurrency shares”(名义并发份额)。此外,在 Kubernetes 1.25 及更早版本中,没有周期性调整:名义/保证限制始终直接应用,不进行调整。优先级级别可以借出和借入多少并发的边界在 PriorityLevelConfiguration 中表示为该级别名义限制的百分比。通过将名义限制除以 100.0 并四舍五入,将这些百分比转换为绝对的座位数量。优先级级别的动态调整并发限制被限制在 (a) 其名义限制减去可借出的座位数的下限,以及 (b) 其名义限制加上可借入的座位数的上限之间。在每次调整时,动态限制是通过每个优先级级别回收最近出现需求的借出座位,然后在上述范围内共同公平地响应优先级级别上的近期座位需求来确定的。
注意
启用优先级与公平性特性后,服务器的总并发限制设置为--max-requests-inflight
和 --max-mutating-requests-inflight
的总和。不再区分变更(mutating)和非变更(non-mutating)请求;如果你想针对给定资源单独处理它们,请分别创建匹配变更动词和非变更动词的独立 FlowSchemas。当分配给单一 PriorityLevelConfiguration 的入站请求量超过其允许的并发级别时,其规范的 type
字段决定了如何处理多余的请求。Reject
类型表示超出容量的流量将立即被拒绝,返回 HTTP 429 (Too Many Requests) 错误。Queue
类型表示超出阈值的请求将被排队,并使用 shuffle sharding 和公平排队技术来平衡不同请求流的进度。
排队配置允许针对优先级级别调整公平排队算法。算法的详细信息可以在增强提案中阅读,但简而言之:
增加
queues
会降低不同流之间的冲突率,但会增加内存使用。在此设置为 1 effectively 禁用公平排队逻辑,但仍然允许请求排队。增加
queueLengthLimit
允许在不丢弃任何请求的情况下维持更大的突发流量,但会增加延迟和内存使用。更改
handSize
允许你调整不同流之间发生冲突的概率以及在过载情况下单个流可用的总并发量。注意
较大的handSize
使两个独立流发生冲突的可能性降低(因此一个流饿死另一个流的可能性也降低),但少数流更有可能支配 apiserver。较大的handSize
也可能增加单个高流量流造成的延迟量。单个流可能的最大排队请求数量是handSize * queueLengthLimit
。
下表显示了一些有趣的 shuffle sharding 配置,针对不同数量的“大象”(高强度流),显示了给定“老鼠”(低强度流)被“大象”压扁的概率。计算此表的代码请参见 https://play.golang.org/p/Gi0PLgVHiUg 。
HandSize | 队列数 (Queues) | 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
的 FlowSchema 进行分配。如果多个具有相同 matchingPrecedence
的 FlowSchema 匹配同一请求,则按字典顺序较小的 name
的 FlowSchema 将胜出,但最好不要依赖此行为,而应确保没有两个 FlowSchema 具有相同的 matchingPrecedence
。如果 FlowSchema 的至少一个 rules
匹配,则 FlowSchema 匹配给定请求。如果规则的至少一个 subjects
*和* 至少一个 resourceRules
或 nonResourceRules
(取决于入站请求是针对资源还是非资源 URL)匹配请求,则该规则匹配。
对于 subjects 中的 name
字段,以及资源和非资源规则的 verbs
、apiGroups
、resources
、namespaces
和 nonResourceURLs
字段,可以指定通配符 *
来匹配给定字段的所有值,从而有效地将其从考虑范围中移除。
FlowSchema 的 distinguisherMethod.type
决定了与该模式匹配的请求如何被分离到不同的流。它可以是 ByUser
,在这种情况下,一个请求用户将无法耗尽其他用户的容量;可以是 ByNamespace
,在这种情况下,针对一个命名空间中的资源的请求将无法耗尽针对其他命名空间中资源的请求的容量;或者为空白(或完全省略 distinguisherMethod
),在这种情况下,所有与此 FlowSchema 匹配的请求都将被视为单个流的一部分。给定 FlowSchema 的正确选择取决于资源和你的具体环境。
默认配置
每个 kube-apiserver 维护两类 APF 配置对象:强制对象和建议对象。
强制配置对象
四个强制配置对象反映了固定的内置防护行为。这是服务器在这些对象存在之前就有的行为,并且当这些对象存在时,它们的规范反映了这种行为。这四个强制对象如下。
强制的
exempt
优先级级别用于完全不受流控制的请求:它们将始终立即被调度。强制的exempt
FlowSchema 将所有来自system:masters
组的请求分类到此优先级级别。如果合适,你可以定义其他 FlowSchemas 将其他请求导向此优先级级别。强制的
catch-all
优先级级别与强制的catch-all
FlowSchema 结合使用,以确保每个请求都获得某种分类。通常你不应依赖此 catch-all 配置,而应酌情创建自己的 catch-all FlowSchema 和 PriorityLevelConfiguration(或使用默认安装的建议的global-default
优先级级别)。由于它通常不会被使用,强制的catch-all
优先级级别具有非常小的并发份额,并且不会对请求进行排队。
建议配置对象
建议的 FlowSchema 和 PriorityLevelConfiguration 构成了一个合理的默认配置。你可以修改它们和/或创建额外的配置对象。如果你的集群可能经历重负载,则应考虑哪种配置最有效。
建议配置将请求分组到六个优先级级别
node-high
优先级级别用于来自节点的健康更新。system
优先级级别用于来自system:nodes
组(即 Kubelet)的非健康请求,这些请求必须能够联系 API 服务器,以便工作负载能够在其上调度。leader-election
优先级级别用于来自内置控制器的领导者选举请求(特别是来自kube-system
命名空间中用户system:kube-controller-manager
或system:kube-scheduler
和服务账号对endpoints
、configmaps
或leases
的请求)。将这些请求与其他流量隔离非常重要,因为领导者选举失败会导致其控制器失败并重新启动,这反过来又会导致新的控制器同步其 Informer 时产生更昂贵的流量。workload-high
优先级级别用于来自内置控制器的其他请求。workload-low
优先级级别用于来自任何其他服务账号的请求,通常包括在 Pod 中运行的控制器的所有请求。global-default
优先级级别处理所有其他流量,例如由非特权用户运行的交互式kubectl
命令。
建议的 FlowSchema 用于将请求导向上述优先级级别,此处不再枚举。
强制和建议配置对象的维护
每个 kube-apiserver
使用初始和周期性行为独立维护强制和建议的配置对象。因此,在不同版本服务器混合的情况下,只要不同服务器对这些对象的正确内容有不同意见,就可能发生抖动(thrashing)。
每个 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
的对象。
健康检查并发豁免
建议配置对来自本地 kubelet 对 kube-apiserver 的健康检查请求没有特殊处理——这些请求倾向于使用安全端口但不提供凭证。在使用建议配置的情况下,这些请求会被分配到 global-default
FlowSchema 和相应的 global-default
优先级级别,在那里它们可能会被其他流量挤占。
如果你添加以下额外的 FlowSchema,这将使这些请求免受速率限制。
注意
进行此更改也允许任何恶意方发送与此 FlowSchema 匹配的健康检查请求,并且数量不限。如果你有 Web 流量过滤器或类似的外部安全机制来保护集群的 API 服务器免受一般互联网流量的影响,则可以配置规则来阻止任何源自集群外部的健康检查请求。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"
可观测性
指标
注意
在 v1.20 之前的 Kubernetes 版本中,标签flow_schema
和 priority_level
的命名不一致,分别为 flowSchema
和 priorityLevel
。如果你运行的是 v1.19 及更早版本的 Kubernetes,你应该参考对应版本的文档。当你启用 API 优先级与公平性 (APF) 功能时,kube-apiserver 会导出额外的指标。监控这些指标可以帮助你确定你的配置是否不恰当地限制了重要流量,或者发现可能损害系统健康的异常工作负载。
成熟度 BETA
apiserver_flowcontrol_rejected_requests_total
是一个计数器向量(自服务器启动以来的累计值),表示被拒绝的请求数量,按标签flow_schema
(指示匹配的模式)、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
是一个 Gauge 向量,表示当前排队中(未执行)的请求数量,按priority_level
和flow_schema
进行细分。apiserver_flowcontrol_current_executing_requests
是一个 Gauge 向量,表示当前执行中(未在队列中等待)的请求数量,按priority_level
和flow_schema
进行细分。apiserver_flowcontrol_current_executing_seats
是一个 Gauge 向量,表示当前占用的席位数量,按priority_level
和flow_schema
进行细分。apiserver_flowcontrol_request_wait_duration_seconds
是一个直方图向量,表示请求在队列中等待的时长,按标签flow_schema
、priority_level
和execute
进行细分。execute
标签指示请求是否已开始执行。注意
由于每个 FlowSchema 总是将请求分配给一个 PriorityLevelConfiguration,因此你可以将属于某个优先级的所有 FlowSchema 的直方图相加,以获得分配给该优先级请求的有效直方图。apiserver_flowcontrol_nominal_limit_seats
是一个 Gauge 向量,表示每个优先级的名义并发限制,该限制是根据 API 服务器的总并发限制和优先级的配置名义并发份额计算得出的。
成熟度 ALPHA
apiserver_current_inqueue_requests
是一个 Gauge 向量,表示最近排队请求数量的高水位标记,按标签request_kind
(值为mutating
或readOnly
)进行分组。这些高水位标记描述了最近一秒窗口中观察到的最大数量。它们是对旧的apiserver_current_inflight_requests
Gauge 向量的补充,后者记录了上一个窗口中正在被积极服务的请求数量的高水位标记。apiserver_current_inqueue_seats
是一个 Gauge 向量,表示排队请求所需的最大席位总和,按标签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
是一个 Gauge 向量,表示当前占用的席位数量,按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
是一个 Gauge 向量,表示每个优先级动态并发限制的下限。apiserver_flowcontrol_upper_limit_seats
是一个 Gauge 向量,表示每个优先级动态并发限制的上限。apiserver_flowcontrol_demand_seats
是一个直方图向量,记录了每纳秒结束时观察到的每个优先级的 (席位需求) / (名义并发限制) 比率。一个优先级的席位需求是排队请求和处于执行初始阶段的请求所需席位之和,该和取请求在初始和最终执行阶段所占用席位数量的最大值。apiserver_flowcontrol_demand_seats_high_watermark
是一个 Gauge 向量,表示在上次并发借用调整期间每个优先级观察到的最大席位需求。apiserver_flowcontrol_demand_seats_average
是一个 Gauge 向量,表示在上次并发借用调整期间每个优先级观察到的时间加权平均席位需求。apiserver_flowcontrol_demand_seats_stdev
是一个 Gauge 向量,表示在上次并发借用调整期间每个优先级观察到的时间加权总体标准差席位需求。apiserver_flowcontrol_demand_seats_smoothed
是一个 Gauge 向量,表示在上次并发调整时确定的每个优先级的平滑包络席位需求。apiserver_flowcontrol_target_seats
是一个 Gauge 向量,表示每个优先级进入借用分配问题的并发目标。apiserver_flowcontrol_seat_fair_frac
是一个 Gauge,表示在上次借用调整中确定的公平分配分数。apiserver_flowcontrol_current_limit_seats
是一个 Gauge 向量,表示在上次调整中为每个优先级导出的动态并发限制。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 优先级与公平性 (APF) 的最佳实践
当给定优先级超出其允许的并发量时,请求可能会经历延迟增加或被 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 而排队、增加延迟或被丢弃,你可以通过以下方式优化你的请求:
- 降低请求的执行速率。在固定时间内减少请求数量,可以减少在给定时间点所需的席位数量。
- 避免同时发出大量昂贵的请求。可以优化请求以使用更少的席位或降低延迟,从而使这些请求占用席位的时间更短。List 请求根据请求期间获取的对象数量,可能会占用超过 1 个席位。限制 List 请求检索的对象数量,例如通过使用分页,可以在更短时间内使用更少的总席位。此外,用 watch 请求替换 list 请求将需要更低的并发总份额,因为 watch 请求在初始的通知突发期间仅占用 1 个席位。如果在 1.27 及更高版本中使用流式列表,watch 请求在初始的通知突发期间将占用与 list 请求相同的席位数量,因为需要流式传输整个集合的状态。请注意,在这两种情况下,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,其 Subject 匹配发出这些请求的用户,或者创建一个 FlowSchema,其 ResourceRules 匹配请求的类型。接下来,你可以将此 FlowSchema 映射到并发份额较低的 PriorityLevelConfiguration。
例如,假设 default 命名空间中运行的 Pod 发出的 list event 请求每个使用 10 个席位,并执行 1 分钟。为了防止这些昂贵的请求影响使用现有 service-accounts FlowSchema 的其他 Pod 的请求,你可以应用以下 FlowSchema 将这些列表调用与其他请求隔离。
隔离 list event 请求的 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 捕获 default 命名空间中 default 服务账号发出的所有 list event 调用。匹配优先级 8000 低于现有 service-accounts FlowSchema 使用的 9000,因此这些 list event 调用将匹配 list-events-default-service-account 而不是 service-accounts。
- 使用 catch-all PriorityLevelConfiguration 来隔离这些请求。兜底优先级具有非常小的并发份额,并且不会对请求进行排队。
接下来
- 你可以访问流控制参考文档,了解更多关于故障排除的信息。
- 有关 API 优先级与公平性的设计详情背景信息,请参阅增强提案。
- 你可以通过 SIG API Machinery 或该功能的 Slack 频道提出建议和功能请求。