k8s之限流机制

时间:2022-11-15 00:58:30


一、限流算法

1、漏桶leaky bucket

漏桶的概念是一个底部有孔的桶,无论水进入桶的速度是多少,它都会以恒定的速度通过孔从桶中泄漏出来。如果桶中没有水,则流速为零,如果桶已满,则多余的水溢出并丢失。其实就是将突发流量入桶转换为恒定流量发送。

k8s之限流机制

  2、令牌桶token bucket

qps设置为100, burst设置为200, 就会以每10ms一个的速度往令牌桶中生成令牌,而令牌桶的容量为200。

每一个request被处理需要首先从桶中获取一个令牌,桶里没有令牌可取时,则拒绝服务(延迟、丢弃、标记)

k8s之限流机制

漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
比如一秒来了200个request,QPS是100,如果是漏铜算法,200个处理不了,多余的100个扔掉。但如果是令牌桶,因为桶里本身有100个,在加上QPS也是100,那这200个就处理了。

二、kube-apiserver限流

kube-apiserver组件是外部操作集群资源的入口,作为一个完善的api类组件的实现,为保障api组件平稳运行,限流功能自然是必不可少的。

3个限流参数:
--max-requests-inflight=400(非mutating操作:get,list,watch等查询操作,默认是400,0表示无限制)
inflight是k8s apiserver从接受一个请求到一直将这个请求处理完,即所有apiserver已经接受但是还没有处理完的请求都叫inflight;同时,inflight的200和400不是qps,具体实现是申请一个200容量的缓存channel,当请求到达时占用一个channel的坑位,请求处理完后释放一个channel的坑位;

--max-mutating-requests-inflight=200(mutating操作:update, delete等操作,默认是200,0表示无限制);

k8s之限流机制

--enable-priority-and-fairness=true(APF,API Priority and Fairness,即请求分优先级且同级别请求被公平对待);

k8s 1.8之前的限流只是将请求分成了mutating和非mutating请求,当某个客户端错误的向kube-apiserver发起大量的请求时,必然会触发kube-apiserver的限流操作,影响其他的客户端的请求,因此高阶的限流APF就诞生了;

在k8s 1.18 apiserver中默认开启APF,启用 APF 特性后,服务器的总并发量限制将设置为 ​​--max-requests-inflight​​​ 和 ​​--max-mutating-requests-inflight​​ 之和。 可变请求和不可变请求之间不再有任何区别。并且这些并发数被分配给各个 PL,分配方式是根据 PriorityLevelConfiguration.Spec.Limited.AssuredConcurrencyShares 的数值按比例分配,PL 的 AssuredConcurrencyShare 越大,分配到的并发份额越大。

APF的优点

  1. APF以更细粒度的方式对请求进行分类和隔离;
  2. APF引入了空间有限的排队机制,因此在非常短暂的突发情况下,APIServer不会拒绝任何请求;
  3. 通过公平排队从队列中分发请求,这样一个行为不佳的控制器就不会饿死其他控制器;

通俗一点就是:

多等级:其实就是按照不同用户,不同场景,不同的组件,用不同的等级队列 Flow1,Flow2.........,每一个等级队列它限流完全不是一个,也就有多个通道,不同通道它的限流机制是一样的,但是它限流不是共享单一队列的,它们完全是独立的限流队列,这样一个队列阻塞了,那么其他的队列还是通的。

多队列:并且在每个等级,如果也是一个单队列,假设flow1对应了一个队列,那么一样会有问题,万一flow1里面有坏用户发了很多的请求,那么整个flow流里面,所有的请求都在后面排了,它的破坏力就很大的,那么APF里面也有多队列的概念,就是在这个flow里面提供多个队列,即使是同一个flow,即使我是系统组件发的请求,不同的组件发送到的队列也可能是不一样的。

这样,即使有一个坏的用户造成了大量的请求堆积,那么它也只塞某个等级的某个队列,如果你的请求是在别的队列里面,那也是不影响的。 也就是通过多等级,多队列的方式,来实现精细化的限流。

APF的实现
APF限流主要依赖两种资源,PriorityLevelConfigurations定义限制策略和队列的容量,FlowSchemas对请求进行分类并能指定用户或用户组,并与一个PriorityLevelConfigurations相匹配。

kubectl get flowschemas.flowcontrol.apiserver.k8s.io kube-controller-manager -oyaml

kubectl get flowschemas.flowcontrol.apiserver.k8s.io kube-controller-manager -oyaml

apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: FlowSchema
metadata:
name: kube-controller-manager
spec:
# 如何把与该模式匹配的请求分散到各个流中。ByUser,把不同用户的请求分散到不同的流中;ByNamespace把不同ns的请求分散到不同流中;为空,与此 FlowSchema 匹配的请求将被视为单个流的一部分;
distinguisherMethod:
type: ByNamespace
matchingPrecedence: 800 # 匹配到请求的优先级,1~10000,越小优先级越高
priorityLevelConfiguration: # 关联的PriorityLevelConfigurations
name: workload-high
rules: # 匹配规则,如果为空,则不会有请求匹配到该FS
- nonResourceRules: # 非资源规则,health、metrics等
- nonResourceURLs:
- '*'
verbs:
- '*'
- resourceRules: # 资源规则
- apiGroups:
- '*'
clusterScope: true
namespaces:
- '*'
resources:
- '*'
verbs:
- '*'
subjects: # 匹配请求主体,可以是用户、组、sa
- kind: User
user:
name: system:kube-controller-manager

kubectl get PriorityLevelConfiguration workload-high -oyaml

kubectl get PriorityLevelConfiguration workload-high -oyaml

apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration
metadata:
name: workload-high
spec:
limited:
# 启用 APF 特性后,服务器的总并发量限制将设置为 --max-requests-inflight 和 --max-mutating-requests-inflight 之和。这些并发数被分配给各个 PL,分配方式是根据AssuredConcurrencyShares 的数值按比例分配,PL 的 AssuredConcurrencyShare 越大,分配到的并发份额越大。
assuredConcurrencyShares: 40
limitResponse: # 定义如何处理当前无法被处理的请求
queuing:
handSize: 6 # shuffle sharding即将请求指派给某个队列的技术,比哈希取模稍好
queueLengthLimit: 50 #队列长度
queues: 128 # 当前PL的队列数
type: Queue #类型,Queue或者Reject,Reject直接返回429并拒绝,Queue将请求加入队列
type: Limited #限制策略,Limited或Exempt, Exempt即不限制

APF流程图

k8s之限流机制

每一个请求分类对应一个FlowSchema,FS 内的请求又会根据 Flow Distinguisher进一步划分为不同的Flow。

FS会设置一个优先级(Priority Level),不同优先级的并发资源是隔离的。所以不同优先级的资源不会相互排挤。特定优先级的请求可以被高优处理。

总之,通过 FS,可以根据请求的主体 (User, Group, ServiceAccout)、动作 (Get, List, Create, Delete …)、资源类型 (pod, deployment …)、namespace、url 对请求进行分类。

三、client-go的限流

1、client-go的client限流,限流器源码目录"k8s.io/client-go/util/flowcontrol",主要有三个参数:

- QPS是指发令牌到令牌桶的速率,默认值是5;

- Burst是指令牌桶的最大桶容量,默认值是10;

- RateLimiter是自定义限流器,参数会覆盖QPS、Burst两个参数。

2、Clientset默认使用的限流器是令牌桶算法,即client-go的flowcontrol.NewTokenBucketRateLimiter(),且需要注意controller-runtime的ctrl.GetConfigOrDie() 默认QPS=20,Burst=30。

3、client-go发出请求时,限流的位置是"k8s.io/client-go/rest/request.go"的tryThrottle()。

四、Controller的workQueue限流

1、限流器实现了对于队列元素的重试规则,源码目录"k8s.io/client-go/util/workqueue",包括三个函数:

- When 获取某个元素应该等待的时间,

- Forget 释放某个元素不再监测,

- NumRequeues 返回该元素已经失败重试的次数。

2、限流器主要有四种类型,主要行为表现在当某一事件元素失败后,等待时间的计算规则不一致。

- 令牌桶算法限速,当某一事件元素失败后,等待时间的计算规则不一致 flowcontrol.NewTokenBucketRateLimiter(qps float32, burst int)
- 排队指数限速,根据失败次数按指数延时, workqueue.NewItemExponentialFailureRateLimiter(baseDelay time.Duration, maxDelay time.Duration)。其中,baseDelay 基础限速时间,maxDelay 最大限速时间
- 计数器模式,先快后慢,  workqueue.NewItemFastSlowRateLimiter(fastDelay, slowDelay time.Duration, maxFastAttempts int)。其中,fastDelay 快限速时间,slowDelay 慢限速时间,maxFastAttempts 快限速元素个数
- 混合模式,多种限速算法同时使用,返回所有RateLimiter的最坏值, NewMaxOfRateLimiter()

3、默认情况下限流器是MaxOfRateLimiter,即(令牌桶算法qps=10,burst=100) +( 根据失败次数按指数延时baseDelay =5ms,maxDelay=1000s),且等待时间=min(1000s,5ms*2^n)。

因此默认的WorkQueue的重试等待时间是5ms*2的n次方,n是重试次数,但是不超过1000s,并且还有一个令牌桶算法处理尖峰流量实现平滑限流,所以流量暴增时等待时间可能比1000s更长。

参考​​K8s之ControllerRateLimiter简单理解_傅里叶、

五、EventRateLimit准入控制器

EventRateLimiter准入控制是目的是限制 Event 请求的速度,缓解了 APIServer 的压力。

启用EventRateLimiter准入控制器:在k8s apiserver的命令行标志 ​​--admission-control-config-file​​​ 设置的文件中, 引用 ​​EventRateLimit​​ 配置文件:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: EventRateLimit
path: eventconfig.yaml
...

  ​​eventconfig.yaml​​ 的示例:

apiVersion: eventratelimit.admission.k8s.io/v1alpha1
kind: Configuration
limits:
- type: Namespace
qps: 50
burst: 100
cacheSize: 2000
- type: User
qps: 10
burst: 50
  • ​Server​​: API 服务器收到的所有事件请求共享一个桶。
  • ​Namespace​​: 每个名字空间都对应一个专用的桶。
  • ​User​​: 为每个用户分配一个桶。
  • ​SourceAndObject​​: 根据事件的来源和涉及对象的各种组合分配桶。

每个eventratelimit 配置使用一个单独的令牌桶限速器,每次event操作,遍历每个匹配的限速器检查是否能获取令牌,如果不允许请求则返回429。

六、问题排查

1、如果请求不多但依然触发了限流,大概率是因为某个客户端的不恰当使用,可以借助审计日志来分析异常请求。
2、出现throttle时,可能是发送request的client对自己进行了限流(k8s.io/client-go/rest.RESTClientFor(rest.Config))