46 KiB
| title | content_type | min-kubernetes-server-version |
|---|---|---|
| API 优先级和公平性 | concept | v1.18 |
{{< feature-state state="alpha" for_k8s_version="v1.18" >}}
对于集群管理员来说,控制 Kubernetes API 服务器在过载情况下的行为是一项关键任务。
{{< glossary_tooltip term_id="kube-apiserver" text="kube-apiserver" >}}
有一些控件(例如:命令行标志 --max-requests-inflight 和 --max-mutating-requests-inflight ),
可以限制将要接受的未处理的请求,从而防止过量请求入站,潜在导致 API 服务器崩溃。
但是这些标志不足以保证在高流量期间,最重要的请求仍能被服务器接受。
API 优先级和公平性( APF )是一种替代方案,可提升上述最大并发限制。 APF 以更细粒度的方式对请求进行分类和隔离。 它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。 通过使用公平排队技术从队列中分发请求,这样, 一个行为不佳的 {{< glossary_tooltip text="控制器" term_id="controller" >}} 就不会饿死其他控制器(即使优先级相同)。
{{< caution >}}
属于“长时间运行”类型的请求(主要是 watch )不受 API 优先级和公平性过滤器的约束。
如果未启用 APF 特性,即便设置 --max-requests-inflight 标志,该类请求也不受约束。
{{< /caution >}}
启用 API 优先级和公平性
APF 特性由特性门控控制,默认情况下不启用。有关如何启用和禁用特性门控的描述,
请参见特性门控。
APF 的特性门控叫做 APIPriorityAndFairness 。
此特性要求必须启用某个 {{< glossary_tooltip term_id="api-group" text="API Group" >}}。
你可以在启动 kube-apiserver 时,添加以下命令行标志来完成这些操作:
kube-apiserver \
--feature-gates=APIPriorityAndFairness=true \
--runtime-config=flowcontrol.apiserver.k8s.io/v1alpha1=true \
# …其他配置与之前相同
命令行标志 --enable-priority-fairness=false 将彻底禁用 APF 特性,即使其他标志启用它也是无效。
概念
APF 特性包含几个不同的功能。 传入的请求通过 流模式 按照其属性分类,并分配优先级。 每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。 在同一个优先级内,公平排队算法可以防止来自不同 流 的请求相互饿死。 该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。
优先级
如果未启用 APF ,API 服务器中的整体并发量将受到 kube-apiserver 的参数
--max-requests-inflight 和 --max-mutating-requests-inflight 的限制。
启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的 优先级 中。
每个传入的请求都会分配一个优先级,每个优先级仅分发其配置允许的并发请求数。
例如,默认配置包括针对领导者选举请求,内置控制器请求和 Pod 请求,都单独设置优先级。 这表示即使异常的 Pod 向 API 服务器发送大量请求,但无法阻止领导者选举或内置控制器的操作执行成功。
排队
即使在同一优先级内,也可能存在大量不同的流量源。 在过载情况下,防止一个请求流饿死其他流是非常有价值的 (尤其是在一个较为常见的场景中,一个 bug 客户端会疯狂请求 kube-apiserver , 理想情况下,该 bug 客户端不会对其他客户端产生太大的影响)。 公平排队算法在处理具有相同优先级的请求时,实现了上述场景。 该算法给每个请求都分配一个 流 ,该 流 由匹配的 流模式 的名称加上一个 流区分器 (可以是发出请求的用户、目标资源的名称空间或什么都不是)标识, 并且系统尝试为不同流中具有相同优先级的的请求,赋予近似相等的权重。
将请求划分到流中之后,APF 功能将请求分配到队列中。 分配时使用一种称为 {{< glossary_tooltip term_id="shuffle-sharding" text="混洗分片" >}} 的技术, 该技术可以相对有效地利用队列,隔离低强度流与高强度流。
排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、 公平性(当总流量超标时,独立流将都会继续前进)、 突发流量的容忍度以及排队引发的额外延迟之间进行权衡。
豁免请求
某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用 API 服务器。
默认值
APF 特性附带推荐配置,该配置足够进行实验; 如果你的集群有可能承受较大的负载,那么你应该考虑哪种配置最有效。 推荐配置将请求分为五个优先级:
system优先级用于system:nodes组(即 Kubelets )的请求, kubelets 必须能连上 API 服务器,以便工作负载能够调度到其上。
leader-election优先级用于内置控制器的领导选举的请求 (特别是来自system:kube-controller-manager的endpoints、configmaps或leases的请求, 或者在kube-system命名空间下的system:kube-scheduler的用户和服务帐户)。 隔离其他流量对这些请求非常重要,因为领导者选举失败会导致控制器发生故障并重新启动, 这反过来会导致新启动的控制器在同步信息时,流量开销更大。
workload-high优先级用于内置控制器的请求。
workload-low优先级适用于来自任何服务帐户的请求,通常包括来自 Pods 中运行的控制器的所有请求。
global-default优先级可处理所有其他流量,例如:非特权用户运行的交互式kubectl命令。
此外,内置了两个优先级配置和两个流模式,它们可能不在上述五个等级之内:
- 特殊的
exempt优先级的请求完全不受流控限制:它们总是立刻被分发。 特殊的exempt流模式把system:masters组的所有请求都归类该优先级。 如果合适,你可以自定义流模式,将其他请求定向到该优先级。
- 特殊的
catch-all优先级与特殊的catch-all流模式结合使用,以确保每个请求都分类。 一般地,你不应该依赖于catch-all的配置,而应适当地创建自己的catch-all流模式和优先级配置 (或使用默认安装的global-default配置)。 为了帮助捕获部分请求未分类的配置错误,强制要求catch-all优先级仅允许一个并发份额, 并且不对请求进行排队,使得仅与catch-all流模式匹配的流量被拒绝的可能性更高,并显示 HTTP 429 错误。
健康检查并发豁免
推荐配置没有对本地 kubelet 在 kube-apiserver 上运行健康检查请求进行任何特殊处理
——它们倾向于使用安全端口,但不提供凭据。
通过推荐配置,这些请求将分配 global-default 流模式和 global-default 优先级,
这样其他流量可以排除健康检查。
如果添加以下流模式,健康检查请求不受速率限制。
{{< caution >}}
进行此更改后,任何敌对方都可以发送与此流模式匹配的任意数量的健康检查请求。 如果你有 Web 流量过滤器或类似的外部安全机制保护集群的 API 服务器免受常规网络流量的侵扰, 则可以配置规则,阻止所有来自集群外部的健康检查请求。
{{< /caution >}}
{{< codenew file="priority-and-fairness/health-for-strangers.yaml" >}}
资源
流控 API 涉及两种资源。 [优先级配置](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#prioritylevelconfiguration-v1alpha1-flowcontrol-apiserver-k8s-io) 定义隔离类型和可处理的并发预算量,还可以微调排队行为。 [流模式](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#flowschema-v1alpha1-flowcontrol-apiserver-k8s-io) 用于对每个入站请求进行分类,并匹配一个优先级配置。
优先级配置
一个优先级配置表示单个隔离类型。每个优先级配置对未完成的请求数都有独立的限制,对排队中的请求数也有限制。
优先级配置的并发限制不是指定请求绝对数量,而是在“并发份额”中指定。
API 服务器的总并发量限制通过这些份额按例分配到现有优先级配置中。
集群管理员可以更改 --max-requests-inflight (或 --max-mutating-requests-inflight )的值,
再重新启动 kube-apiserver 来增加或减小服务器的总流量,
然后所有的优先级配置将看到其最大并发增加(或减少)了相同的比例。
{{< caution >}}
启用 APF 功能后,服务器的总并发量限制将设置为
--max-requests-inflight 和 --max-mutating-requests-inflight 之和。
可变请求和不可变请求之间不再有任何区别;
如果对于某种资源,你需要区别对待不同请求,请创建不同的流模式分别匹配可变请求和不可变请求。
{{< /caution >}}
当入站请求的数量大于分配的优先级配置中允许的并发级别时, type 字段将确定对额外请求的处理方式。
Reject 类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。
Queue 类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。
公平排队算法支持通过排队配置对优先级微调。 可以在增强建议中阅读算法的详细信息,但总之:
queues递增能减少不同流之间的冲突概率,但代价是增加了内存使用量。 值为1时,会禁用公平排队逻辑,但仍允许请求排队。
queueLengthLimit递增可以在不丢弃任何请求的情况下支撑更大的突发流量, 但代价是增加了等待时间和内存使用量。
- 修改
handSize允许你调整过载情况下不同流之间的冲突概率以及单个流可用的整体并发性。 {{< note >}} 较大的handSize使两个单独的流程发生碰撞的可能性较小(因此,一个流可以饿死另一个流), 但是更有可能的是少数流可以控制 apiserver。 较大的handSize还可能增加单个高并发流的延迟量。 单个流中可能排队的请求的最大数量为handSize *queueLengthLimit。 {{< /note >}}
下表显示了有趣的随机分片配置集合, 每行显示给定的老鼠(低强度流)被不同数量的大象挤压(高强度流)的概率。 表来源请参阅: https://play.golang.org/p/Gi0PLgVHiUg
{{< table caption = "Example Shuffle Sharding Configurations" >}}
| 随机分片 | 队列数 | 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 |
| {{< /table >}} |
流模式
流模式匹配一些入站请求,并将它们分配给优先级。
每个入站请求都会对所有流模式测试是否匹配,
首先从 matchingPrecedence 数值最低的匹配开始(我们认为这是逻辑上匹配度最高),
然后依次进行,直到首个匹配出现。
{{< caution >}}
对一个请求来说,只有首个匹配的流模式才有意义。
如果一个入站请求与多个流模式匹配,则将基于 matchingPrecedence 值最高的请求进行筛选。
如果一个请求匹配多个流模式且 matchingPrecedence 的值相同,则按 name 的字典序选择最小,
但是最好不要依赖它,而是确保不存在两个流模式具有相同的 matchingPrecedence 值。
{{< /caution >}}
当给定的请求与某个流模式的 rules 的其中一条匹配,那么就认为该请求与该流模式匹配。
判断规则与该请求是否匹配,不仅要求该条规则的 subjects 字段至少存在一个与该请求相匹配,
而且要求该条规则的 resourceRules 或 nonResourceRules
(取决于传入请求是针对资源URL还是非资源URL)字段至少存在一个与该请求相匹配。
对于 subjects 中的 name 字段和资源和非资源规则的
verbs,apiGroups,resources,namespaces 和 nonResourceURLs 字段,
可以指定通配符 * 来匹配任意值,从而有效地忽略该字段。
流模式的 distinguisherMethod.type 字段决定了如何把与该模式匹配的请求分散到各个流中。
可能是 ByUser ,在这种情况下,一个请求用户将无法饿死其他容量的用户;
或者是 ByNamespace ,在这种情况下,一个名称空间中的资源请求将无法饿死其它名称空间的资源请求;
或者它可以为空(或者可以完全省略 distinguisherMethod ),
在这种情况下,与此流模式匹配的请求将被视为单个流的一部分。
资源和你的特定环境决定了如何选择正确一个流模式。
诊断程序
启用了 APF 的 API 服务器,它每个 HTTP 响应都有两个额外的 HTTP 头:
X-Kubernetes-PF-FlowSchema-UID 和 X-Kubernetes-PF-PriorityLevel-UID,
注意与请求匹配的流模式和已分配的优先级。
如果请求用户没有查看这些对象的权限,则这些 HTTP 头中将不包含 API 对象的名称,
因此在调试时,你可以使用类似如下的命令:
kubectl get flowschemas -o custom-columns="uid:{metadata.uid},name:{metadata.name}"
kubectl get prioritylevelconfigurations -o custom-columns="uid:{metadata.uid},name:{metadata.name}"
来获取 UID 到流模式的名称和 UID 到优先级配置的名称的映射。
可观察性
指标
当你开启了 APF 后,kube-apiserver 会暴露额外指标。 监视这些指标有助于判断你的配置是否不当地限制了重要流量, 或者发现可能会损害系统健康的,行为不良的工作负载。
apiserver_flowcontrol_rejected_requests_total是一个计数器向量, 记录被拒绝的请求数量(自服务器启动以来累计值), 由标签flowSchema(表示与请求匹配的流模式),priorityLevel(表示分配给请该求的优先级)和reason拆分。reason标签将具有以下值之一:queue-full,表明已经有太多请求排队,concurrency-limit,表示将 PriorityLevelConfiguration 配置为Reject而不是Queue,或者time-out, 表示在其排队时间超期的请求仍在队列中。
apiserver_flowcontrol_dispatched_requests_total是一个计数器向量, 记录开始执行的请求数量(自服务器启动以来的累积值), 由标签flowSchema(表示与请求匹配的流模式)和priorityLevel(表示分配给该请求的优先级)拆分。
apiserver_current_inqueue_requests是一个表向量, 记录最近排队请求数量的高水位线, 由标签request_kind分组,标签的值为mutating或readOnly。 这些高水位线表示在最近一秒钟内看到的最大数字。 它们补充说明了老的表向量apiserver_current_inflight_requests(该量保存了最后一个窗口中,正在处理的请求数量的高水位线)。
apiserver_flowcontrol_read_vs_write_request_count_samples是一个直方图向量, 记录当前请求数量的观察值, 由标签phase(取值为waiting和executing)和request_kind(取值mutating和readOnly)拆分。 定期以高速率观察该值。
apiserver_flowcontrol_read_vs_write_request_count_watermarks是一个直方图向量, 记录请求数量的高/低水位线, 由标签phase(取值为waiting和executing)和request_kind(取值为mutating和readOnly)拆分; 标签mark取值为high和low。apiserver_flowcontrol_read_vs_write_request_count_samples向量观察到有值新增,则该向量累积。 这些水位线显示了样本值的范围。
apiserver_flowcontrol_current_inqueue_requests是一个表向量, 记录包含排队中的(未执行)请求的瞬时数量, 由标签priorityLevel和flowSchema拆分。
apiserver_flowcontrol_current_executing_requests是一个表向量, 记录包含执行中(不在队列中等待)请求的瞬时数量, 由标签priorityLevel和flowSchema拆分。
apiserver_flowcontrol_priority_level_request_count_samples是一个直方图向量, 记录当前请求的观测值,由标签phase(取值为waiting和executing)和priorityLevel拆分。 每个直方图都会定期进行观察,直到相关类别的最后活动为止。观察频率高。
apiserver_flowcontrol_priority_level_request_count_watermarks是一个直方图向量, 记录请求数的高/低水位线,由标签phase(取值为waiting和executing)和priorityLevel拆分; 标签mark取值为high和low。apiserver_flowcontrol_priority_level_request_count_samples向量观察到有值新增,则该向量累积。 这些水位线显示了样本值的范围。
apiserver_flowcontrol_request_queue_length_after_enqueue是一个直方图向量, 记录请求队列的长度,由标签priorityLevel和flowSchema拆分。 每个排队中的请求都会为其直方图贡献一个样本,并在添加请求后立即上报队列的长度。 请注意,这样产生的统计数据与无偏调查不同。 {{< note >}}
相反,如果一个优先级的直方图显示该优先级的所有队列都比其他优先级的队列长,则增加优先级配置的并发份额是比较合适的。 {{< /note >}}直方图中的离群值在这里表示单个流(即,一个用户或一个名称空间的请求,具体取决于配置)正在疯狂请求 API 服务器,并受到限制。
apiserver_flowcontrol_request_concurrency_limit是一个表向量, 记录并发限制的计算值(基于 API 服务器的总并发限制和优先级配置的并发份额), 并按标签priorityLevel拆分。
apiserver_flowcontrol_request_wait_duration_seconds是一个直方图向量, 记录请求排队的时间, 由标签flowSchema(表示与请求匹配的流模式),priorityLevel(表示分配该请求的优先级) 和execute(表示请求是否开始执行)拆分。 {{< note >}}
{{< /note >}}由于每个流模式总会给请求分配优先级配置,因此你可以为一个优先级添加所有流模式的直方图,以获取分配给该优先级的请求的有效直方图。
apiserver_flowcontrol_request_execution_seconds是一个直方图向量, 记录请求实际执行需要花费的时间, 由标签flowSchema(表示与请求匹配的流模式)和priorityLevel(表示分配给该请求的优先级)拆分。
调试端点
启用 APF 特性后, kube-apiserver 会在其 HTTP/HTTPS 端口提供以下路径:
-
/debug/api_priority_and_fairness/dump_priority_levels—— 所有优先级及其当前状态的列表。你可以这样获取:kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels输出类似于:
PriorityLevelName, ActiveQueues, IsIdle, IsQuiescing, WaitingRequests, ExecutingRequests, workload-low, 0, true, false, 0, 0, global-default, 0, true, false, 0, 0, exempt, <none>, <none>, <none>, <none>, <none>, catch-all, 0, true, false, 0, 0, system, 0, true, false, 0, 0, leader-election, 0, true, false, 0, 0, workload-high, 0, true, false, 0, 0,
-
/debug/api_priority_and_fairness/dump_queues——所有队列及其当前状态的列表。你可以这样获取:kubectl get --raw /debug/api_priority_and_fairness/dump_queues输出类似于:
PriorityLevelName, Index, PendingRequests, ExecutingRequests, VirtualStart, workload-high, 0, 0, 0, 0.0000, workload-high, 1, 0, 0, 0.0000, workload-high, 2, 0, 0, 0.0000, ... leader-election, 14, 0, 0, 0.0000, leader-election, 15, 0, 0, 0.0000,
-
/debug/api_priority_and_fairness/dump_requests——当前正在队列中等待的所有请求的列表。你可以这样获取:kubectl get --raw /debug/api_priority_and_fairness/dump_requests输出类似于:
PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher, ArriveTime, exempt, <none>, <none>, <none>, <none>, <none>, system, system-nodes, 12, 0, system:node:127.0.0.1, 2020-07-23T15:26:57.179170694Z,除了排队的请求外,对于每个优先级,输出还包括一条不受限制的虚拟线。
你可以使用以下命令获得更详细的清单:
kubectl get --raw '/debug/api_priority_and_fairness/dump_requests?includeRequestDetails=1'输出类似于:
PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher, ArriveTime, UserName, Verb, APIPath, Namespace, Name, APIVersion, Resource, SubResource, system, system-nodes, 12, 0, system:node:127.0.0.1, 2020-07-23T15:31:03.583823404Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps, system, system-nodes, 12, 1, system:node:127.0.0.1, 2020-07-23T15:31:03.594555947Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps,
{{% heading "whatsnext" %}}
有关API优先级和公平性的设计细节的背景信息, 请参阅增强建议。 你可以通过 SIG APIMachinery 提出建议和特性请求。