MaxInFlightLimit(server 级别整体限流)
在 APF 出现之前,API Server 进行限流的方式是使用 API Server 的 --max-requests-inflight
和 --max-mutating-requests-inflight
参数。其中
-
--max-requests-inflight:在给定时间内的最大 non-mutating 请求数(非mutating操作:get,list 等查询操作)
-
--max-mutating-requests-inflight:在给定时间内的最大 mutating 请求数(mutating 操作:update, delete 等操作)
也就是说 API Server 的并发量受到上述两者参数的限制。
watch 请求不受
--max-requests-inflight
限制
源码
staging/src/k8s.io/apiserver/pkg/server/filters/maxinflight.go:WithMaxInFlightLimit()
参数推荐
默认值 | 节点数1000-3000 | 节点数 > 3000 | |
---|---|---|---|
max-requests-inflight | 400 | 1500 | 3000 |
max-mutating-requests-inflight | 200 | 500 | 1000 |
缺点
-
粒度粗:无法为不同用户,不同场景设置不同的限流
-
单队列:共享限流窗口/桶,一个坏用户可能会将整个系统堵塞,其他正常用户的请求无法被及时处理
-
不公平:正常用户的请求会被排到队尾,无法及时处理而饿死
-
无优秀级:重要的系统指令一并被限流,系统故障难以恢复
APF
kubernetes 文档:https://kubernetes.io/zh-cn/docs/concepts/cluster-administration/flow-control/
设计文档:https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/1040-priority-and-fairness
为了解决 API Server 限流机制存在的问题,API Server 添加了 APF 机制(1.15以上,alpha版本),
- APF 可以以更细粒度(user/resource/namespace)的方式对请求进行分类和隔离,比如针对 pod 查询的请求进行请求限制和优先级分配。
- 引入了空间有限的排队机制,因此在非常短暂的突发情况下,API Server 不会拒绝任何请求。
- 通过使用公平排队技术从队列中分发请求,这样,一个行为不佳的控制器就不会饿死其他控制器(即使优先级相同)。
APF 的核心是
- 多等级
- 多队列
采用 APF 机制之后,API Server 限制的总流量为 --max-requests-inflight
和 --max-mutating-requests-inflight
的总和,不再区分是否是 mutating 操作,而总和又会被分配到一组可配置的优先级中(每个请求最终都会对应一个优先级)。
APF 的实现依赖两个非常重要的资源 FlowSchema、PriorityLevelConfiguration,
- 传入的请求都会匹配到一个 FlowSchema,FlowSchema 内的请求又会根据 distinguisher 进一步划分为不同的 Flow。同时,FlowSchema 会设置一个 PriorityLevelConfiguration,也就是请求最终都会被分配给相应的 PriorityLevel。
- PriorityLevelConfiguration 用于维护相应的并发限制,不同 PriorityLevel 的并发资源是隔离的,这样不同 PriorityLevel 的请求,就不会相互排挤,加强了隔离度。
- 针对同一个 PriorityLevelConfiguration 分配到的请求,公平排队算法可以防止不同 flow 的请求不会相互饿死。该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突然而导致请求失败。
FlowSchema
每个入站请求都会对所有 FlowSchema 测试是否匹配,首先会从 matchingPrecedence 数值最低的匹配开始(我们认为这是逻辑上匹配度最高),然后依次进行,直接首个匹配出现。FlowSchema 匹配了一些入站请求后,会将它们分配给相应的优先级。匹配的规则见 rules 字段,相应的优先级见 priorityLevelConfiguration 字段。
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: FlowSchema
metadata:
name: kube-scheduler # FlowSchema 名
spec:
distinguisherMethod:
type: ByNamespace # Distinguisher,和 FlowSchema 一起,共同确定一个 flow
matchingPrecedence: 800 # 规则优先级
priorityLevelConfiguration: # 对应的队列优先级
name: workload-high
rules:
- resourceRules:
- resources:
- '*'
verbs:
- '*'
subjects:
- kind: User
user:
name: system:kube-scheduler
上述 FlowSchema 的意思是:用户 system:kube-scheduler 发出的请求(rules 字段),会根据 namespace 分成不同的 flow,同时这些请求都会被分配到 workload-high 这个优先级。
需要注意的是:
- PriorityLevelConfiguration 中会维护了一个 QueueSet 用于缓存不能及时处理的请求,请求不会因为超出 PriorityLevelConfiguration 的并发限制而被丢弃。
- FlowSchema 中的每个 Flow 通过 shuffle sharding 算法从 QueueSet 选取特定的 queues 缓存请求。
- 每次从 QueueSet 中取请求执行时,会先应用 fair queuing 算法从 QueueSet 中选中一个 queue,然后从这个 queue 中取出 oldset 请求执行。所以即使是同一个 PriorityLevelConfiguration 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象。
kubernetes 有默认提供以下几个 FlowSchema,
$ kubectl get FlowSchema
NAME PRIORITYLEVEL MATCHINGPRECEDENCE DISTINGUISHERMETHOD AGE MISSINGPL
exempt exempt 1 <none> 12d False
probes exempt 2 <none> 12d False
system-leader-election leader-election 100 ByUser 12d False
workload-leader-election leader-election 200 ByUser 12d False
system-nodes system 500 ByUser 12d False
kube-controller-manager workload-high 800 ByNamespace 12d False
kube-scheduler workload-high 800 ByNamespace 12d False
kube-system-service-accounts workload-high 900 ByNamespace 12d False
service-accounts workload-low 9000 ByUser 12d False
global-default global-default 9900 ByUser 12d False
catch-all catch-all 10000 ByUser 12d False
PriorityLevelConfiguration
PriorityLevelConfiguration 其实是一个优先级等级的配置,它会指定该优先级限制的流量占总流量的比例(使用 assuredConcurrencyShares 字段表示),最终限制的流量为:总流量*(该优先级的 assuredConcurrencyShares /所有优先级 assuredConcurrencyShares 的总和)。
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration
metadata:
...
name: global-default
spec:
limited:
assuredConcurrencyShares: 20
...
type: Limited
status: {}
需要注意的是:
- 一个 PriorityLevelConfiguration 可以分配给多个 FlowSchema。
kubernetes 有默认提供以下几个优先级,包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。这表示即使异常 Pod 向 API Server 发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。
$ kubectl get PriorityLevelConfiguration
NAME TYPE ASSUREDCONCURRENCYSHARES QUEUES HANDSIZE QUEUELENGTHLIMIT AGE
catch-all Limited 5 <none> <none> <none> 7d4h
exempt Exempt <none> <none> <none> <none> 7d4h
global-default Limited 20 128 6 50 7d4h
leader-election Limited 10 16 4 50 7d4h
system Limited 30 64 6 50 7d4h
workload-high Limited 40 128 6 50 7d4h
workload-low Limited 100 128 6 50 7d4h
启用/禁用 APF
APF 特性通过 feature gate 来启动和禁用, 它的 feature gate 是 APIPriorityAndFairness,默认是开启的。而它使用的 API 组中,v1alpha1
版本,默认被禁用,而 v1beta1
和 v1beta2
版本,默认被启用。
如下的配置,则会禁用 APF 和 v1beta1、v1beta2 两个 API 版本。
kube-apiserver \
--feature-gates=APIPriorityAndFairness=false \
--runtime-config=flowcontrol.apiserver.k8s.io/v1beta1=false,flowcontrol.apiserver.k8s.io/v1beta2=false \
# ...其他配置不变
--runtime-config=flowcontrol.apiserver.k8s.io/v1alpha1=true
启用 API 组的 v1alpha1 版本。
另外,设置命令行标志 --enable-priority-fairness=false
将彻底禁用 APF 特性,此时即使设置其他标志启用 APF 也是无效的。
总结
APF 采用的方式是先将请求根据 user/resource/namespace 进行隔离,隔离之后的每组都会绑定相应的优先级。不同优先级的请求具有不同的并发限制,并且不会互相影响。隔离之后的每组请求又可能根据 namespace 等信息划分到不同的 queue 中。在从一个优先级中取请求的时候,它会采用公平算法选择一个 queue,然后从这个 queue 中取出最老的请求进行处理。
举个例子,比如将来自 scheduler 的请求进行隔离,并且都绑定到一个优先级中。由于该优先级会分配到一定的并发数,因此 scheduler 的并发数就受到这个优先级的限制,但是不会影响其他请求。同时对于都来自 scheduler 的请求,可能又会按照 namespace 进行区分,分到不同 queue 中,对于同一优先级中的请求,每次它是选择一个 queue,然后取出一个请求,又避免了同一个优先级中的请求隔离的问题。
简单来说的话,其实就是先将请求分成大类,然后再从大类中分出小类。大类本身就有隔离,而小类在隔离的基础之上再进行隔离。
其他限流方式
Client 限流
client-go默认的qps为5,但是只支持客户端限流,集群管理员无法控制用户行为。
EventRateLimit 限流
EventRateLimit在1.13之后支持,只限制event请求,集成在apiserver内部webhoook中,可配置某个用户、namespace、server等event操作限制,通过webhook形式实现。
具体原理可以参考提案,每个eventratelimit 配置使用一个单独的令牌桶限速器,每次event操作,遍历每个匹配的限速器检查是否能获取令牌,如果可以允许请求,否则返回429
。