API 优先级和公平性(APF)

2023-05-16

1. 概述

  • 目前apiserver默认的限流方式太过简单

  • 目前k8s缺少客户端业务请求隔离,一个错误的客户端发送大量请求可能造成其他客户端请求异常,也不支持突发流量。

2. 开启APF

APF测试 开启APF,需要在apiserver配置

提案。

API 优先级和公平性(1.15以上,alpha版本), 以更细粒度(byUser,byNamespace)对请求进行分类和隔离。 支持突发流量,通过使用公平排队技术从队列中分发请求从而避免饥饿。

APF限流通过两种资源,PriorityLevelConfigurations定义隔离类型和可处理的并发预算量,还可以调整排队行为。 FlowSchemas用于对每个入站请求进行分类,并与一个 PriorityLevelConfigurations相匹配。

可对用户或用户组或全局进行某些资源某些请求的限制,如限制default namespace写services put/patch请求。

优点

  • 考虑情况较全面,支持优先级,白名单等
  • 可支持server/namespace/user/resource等细粒度级别的限流

缺点

  • 配置复杂,不直观,需要对APF原理深入了解
  • 功能较新,缺少生产环境验证

整体设计:

  • APF 的实现依赖两个非常重要的资源 FlowSchema, PriorityLevelConfiguration
  • APF 对请求进行更细粒度的分类,每一个请求分类对应一个 FlowSchema (FS)
  • FS 内的请求又会根据 distinguisher 进一步划分为不同的 Flow
  • FS 会设置一个优先级 (Priority Level, PL),不同优先级的并发资源是隔离的。所以不同优先级的资源不会相互排挤。特定优先级的请求可以被高优处理。
  • 一个 PL 可以对应多个 FS,PL 中维护了一个 QueueSet,用于缓存不能及时处理的请求,请求不会因为超出 PL 的并发限制而被丢弃。
  • FS 中的每个 Flow 通过 shuffle sharding 算法从 QueueSet 选取特定的 queues 缓存请求。
  • 每次从 QueueSet 中取请求执行时,会先应用 fair queuing 算法从 QueueSet 中选中一个 queue,然后从这个 queue 中取出 oldest 请求执行。所以即使是同一个 PL 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象。
    在这里插入图片描述

注意: 属于 “长时间运行” 类型的某些请求(例如远程命令执行或日志拖尾)不受 API 优先级和公平性过滤器的约束。 如果未启用 APF 特性,即便设置 --max-requests-inflight 标志,该类请求也不受约束。 APF 适用于 watch 请求。当 APF 被禁用时,watch 请求不受 --max-requests-inflight 限制。

3. FlowSchema

3.1 对请求进行分类

  1. 用户可以通过创建 FlowSchema 资源对象自定义分类方式。

FS 代表一个请求分类,包含多条匹配规则,如果某个请求能匹配其中任意一条规则就认为这个请求属于这个 FS (只匹配第一个匹配的 FS)。

type PolicyRulesWithSubjects struct {
	Subjects []Subject
	ResourceRules []ResourcePolicyRule
	NonResourceRules []NonResourcePolicyRule
}

type Subject struct {
	Kind SubjectKind `json:"kind" protobuf:"bytes,1,opt,name=kind"`
	User *UserSubject `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"`
	Group *GroupSubject `json:"group,omitempty" protobuf:"bytes,3,opt,name=group"`
	ServiceAccount *ServiceAccountSubject `json:"serviceAccount,omitempty" protobuf:"bytes,4,opt,name=serviceAccount"`
}

type ResourcePolicyRule struct {
	Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
	APIGroups []string `json:"apiGroups" protobuf:"bytes,2,rep,name=apiGroups"`
	Resources []string `json:"resources" protobuf:"bytes,3,rep,name=resources"`
	ClusterScope bool `json:"clusterScope,omitempty" protobuf:"varint,4,opt,name=clusterScope"`
	Namespaces []string `json:"namespaces" protobuf:"bytes,5,rep,name=namespaces"`
}

type NonResourcePolicyRule struct {
	Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
	NonResourceURLs []string `json:"nonResourceURLs" protobuf:"bytes,6,rep,name=nonResourceURLs"`
}

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

  1. FS 内的请求进一步划分 Flow
    有两种方式对请求进行 Flow 划分:

    • distinguisher = ByUser, 根据请求的 User 划分不同 Flow;可以让来自不同用户的请求平等使用 PL 内的资源。
    • distinguisher = ByNamespace, 根据请求的 namespace 划分不同的 Flow;可以让来自不同 namespace 的请求平等使用 PL 内的资源。
    • distinguisher = nil,表示不划分
    3.2 Priority Level 
    

    如果 api-sever 启动了 APF,它的总并发数为 --max-requests-inflight --max-mutating-requests-inflight 两个配置值之和。这些并发数被分配给各个 PL,分配方式是根据 PriorityLevelConfiguration.Spec.Limited.AssuredConcurrencyShares 的数值按比例分配。 PL 的 AssuredConcurrencyShare 越大,分配到的并发份额越大 。

    每个 PL 都对应维护了一个 QueueSet,其中包含多个 queue ,当 PL 达到并发限制时,收到的请求会被缓存在 QueueSet 中,不会丢弃,除非 queue 也达到了容量限制。

    当入站请求的数量大于分配的 PriorityLevelConfiguration 中允许的并发级别时, type 字段将确定对额外请求的处理方式。 Reject 类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。 Queue 类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。

    QueueSet 中 queue 数量由PriorityLevelConfiguration.Spec.Limited.LimitResponse.Queuing.Queues 指定;每个 queue 的长度由 PriorityLevelConfiguration.Spec.Limited.LimitResponse.Queuing.QueueLengthLimit 指定。

    例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。 这表示即使异常的 Pod 向 API 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。

    优先级的并发限制会被定期调整,允许利用率较低的优先级将并发度临时借给利用率很高的优先级。 这些限制基于一个优先级可以借出多少个并发度以及可以借用多少个并发度的额定限制和界限, 所有这些均源自下述配置对象。

    请求占用的席位

    • 6个推荐的 PL
    PLrequest
    global-default所有其他的请求
    leader-election优先级用于内置控制器的领导选举的请求 (特别是来自 kube-system 名字空间中 system:kube-controller-managersystem:kube-scheduler 用户和服务账号,针对 endpointsconfigmapsleases 的请求)
    node-high优先级用于来自节点的健康状态更新。
    system优先级用于 system:nodes 组(即 kubelet)的与健康状态更新相关的请求; kubelets 必须能连上 API 服务器,以便工作负载能够调度到其上
    workload-high优先级用于内置控制器的其他请求
    workload-low优先级用于来自所有其他服务帐户的请求,通常包括来自 Pod 中运行的控制器的所有请求。
    // sort into the order to be used for matching
    sort.Sort(fsSeq)
    
    // Supply missing mandatory FlowSchemas, in correct position
    if !haveExemptFS {
    	// 放在第一位
    	fsSeq = append(apihelpers.FlowSchemaSequence{fcboot.MandatoryFlowSchemaExempt}, fsSeq...)
    }
    if !haveCatchAllFS {
    	// 放在最后一位
    	fsSeq = append(fsSeq, fcboot.MandatoryFlowSchemaCatchAll)
    }
    

    自定义PL和FS

    • FlowShema配置
    apiVersion: flowcontrol.apiserver.k8s.io/v1alpha1
    kind: PriorityLevelConfiguration
    metadata:
      name: leader-election
    spec:
      limited: #限制策略
        assuredConcurrencyShares: 10 
        limitResponse: #如何处理被限制的请求
          queuing: #类型为Queue时,列队的设置
            handSize: 4 #队列
            queueLengthLimit: 50 #队列长度
            queues: 16 #队列数
          type: Queue #Queue或者Reject,Reject直接返回429,Queue将请求加入队列
      type: Limited #类型,Limited或Exempt, Exempt即不限制
    

    4. 问题诊断

    启用了 APF 的 API 服务器,它每个 HTTP 响应都有两个额外的 HTTP 头: X-Kubernetes-PF-FlowSchema-UIDX-Kubernetes-PF-PriorityLevel-UID, 注意与请求匹配的 FlowSchema 和已分配的优先级。 如果请求用户没有查看这些对象的权限,则这些 HTTP 头中将不包含 API 对象的名称, 因此在调试时,你可以使用类似如下的命令:

    5. 源码分析 
    

    5.1 请求流程

    API Server 接收到请求后,先按照前面提到的方式,找到与之匹配的 FS,实现分类,并根据 FS 确定请求的所属的Flow 和 PL。

    APF 利用 FS 的 name 和请求的 userName 或 namespace 计算一个 hashFlowID 标识 Flow。

    func (d *Dealer) Deal(hashValue uint64, pick func(int)) {
    	// 15 is the largest possible value of handSize
    	var remainders [15]int
    
    	for i := 0; i < d.handSize; i++ {
    		hashValueNext := hashValue / uint64(d.deckSize-i)
    		remainders[i] = int(hashValue - uint64(d.deckSize-i)*hashValueNext)
    		hashValue = hashValueNext
    	}
    
    	// 防止重复:正反馈机制,大者更大
    	for i := 0; i < d.handSize; i++ {
    		card := remainders[i]
    		for j := i; j > 0; j-- {
    			if card >= remainders[j-1] {
            // 不会出现 card > deckSize
            // 因为 hashValue % uint64(d.deckSize-i) <= d.deckSize-i-1,而第 i 个 card 最多自增 i 次
    				card++
    			}
    		}
    		pick(card)
    	}
    }
    

    判断是否入队这个请求:如果队列已满且 PL 中正在执行的请求数达到 PL 的并发限制,就会拒绝这个请求,否则入队这个请求。

    此处保证了不同 Flow 的请求不会挤掉其他 Flow 的请求。Flow 是按照用户或 namespace 划分的,它的实际意义就是来自不同用户或 namespace 的请求不会挤掉同优先级的其他用户或 namespace 的请求。

    5.2 分发请求

    为了保证同一个 PL 中缓存的不同 Flow 的请求被处理机会平等,每次分发请求时,都会先应用 fair queuing 算法从 PL 的 QueueSet 中选中一个 queue:

    type queue struct {
    	requests []*request
      
    	// 如果队列中没有 request 且没有 request 在执行 (requestsExecuting = 0), virtualStart = queueSet.virtualTime
    	// 每分发一个 request, virtualStart = virtualStart + queueSet.estimatedServiceTime
    	// 每执行完一个 request, virtualStart = virtualStart - queueSet.estimatedServiceTime + actualServiceTime,用真实的执行时间,校准 virtualStart
    	// 计算第 J 个 request 的 virtualFinishTime = virtualStart + (J+1) * serviceTime
    	virtualStart float64
    
    	requestsExecuting int
    	index             int
    }
    

    virtualStart 初始化是直接设置为 QueueSet 中维护的 virtualTime。而 QueueSet.virtualTime 是在这个 PL 初始化的时候赋值为 0。此后,如果 QueueSet 中的 queue 如有任何状态变化,都要执行更新,根据自身两次变更历经的 realTime 按比例增加:

    func (qs *queueSet) syncTimeLocked() {
    realNow := qs.clock.Now()
    timeSinceLast := realNow.Sub(qs.lastRealTime).Seconds()
    qs.lastRealTime = realNow
    qs.virtualTime += timeSinceLast * qs.getVirtualTimeRatioLocked()
    }

    其中,这个比例计算方式为:min(QueueSet 中正字执行的请求数, PL 的并发配额) / QueueSet 中活跃的 queue 数目。

    virtualTime 实际对应于 bit-by-bit round-robin 算法中的 R(t),当前时间 round-robin 轮数。具体可以参考文后第4个链接。

    1. 选 queue 时,会估计每个 queue 中 oldest 请求的虚拟执行完毕时间,选择这个虚拟执行完毕时间最小的 queue

    选中 queue 之后,从 queue 中取出 oldest 请求,设置执行标记。重复执行以上选 queue 给 oldest 请求设置执行标志,直到 PL 所有的 Queue 中都没有缓存的请求或达到 PL 的并发限制。

    注:此处是尽可能多的分发 PL 中缓存的请求,有可能当前新加入的请求不会被分发。

    5.3 请求阻塞监听执行

    完成以上操作之后,该请求会进入阻塞监听状态,直到被分发。

    5.4 请求执行 
    

    如果这个请求被唤醒,并收到了 decisionExecute 标记,便会开始执行。

    
    
                      									
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

API 优先级和公平性(APF) 的相关文章

随机推荐

  • CountDownLatch 的用法

    CountDownLatch 的用法 方法构造方法 CountDownLatch int count countDown long getCount await boolean await long timeout TimeUnit uni
  • @JsonAutoDetect 定义字段、方法的访问可见性规则

    默认情况下 xff0c Jackson 只使用 public 的字段进行序列化和反序列化 没有 public 字段时 xff0c 会使用 public 的 getters setters 可以通过 64 JsonAutoDetect 自定义
  • @JsonUnwrapped 以扁平的数据结构序列化/反序列化属性

    64 JsonUnwrapped 表明属性应该以扁平化的形式进行序列化 xff0c 即 xff0c 目标属性将不会序列化为 JSON 对象 xff0c 但其属性将序列化为包含它的对象的属性 并能以相同的方式反序列化 让我们通过一个例子来理解
  • FeignClient 支持占位符及其原理

    问题与解决方案 在使用 FeignClient 的时候 xff0c 测试环境和线上环境的域名是不同的 xff0c 可以使用占位符来动态配置 如下 span class token annotation punctuation 64 Feig
  • 命令行版 Centos 7 虚拟机安装 xfce4 桌面 + x11vnc + novnc

    命令行版 Centos 7 虚拟机安装 xfce4 桌面 43 x11vnc 43 novnc 安装命令行版 Centos 7 虚拟机xfce4 桌面环境安装 x server安装 启动 xfce4 x11vncnovnc 安装命令行版 C
  • docker Centos 7 安装 xfce4 桌面 + x11vnc + novnc

    docker Centos 7 安装 xfce4 桌面 43 x11vnc 43 novnc 启动容器环境变量xfce4 桌面安装 启动 Xvfb安装 启动 xfce4 桌面 x11vnc安装 x11vnc启动 x11vnc novnc 上
  • docker kali 安装 xfce4 桌面 + tigervnc + novnc

    docker kali 安装 xfce4 桌面 43 tigervnc 43 novnc 启动容器环境变量准备工作安装xfce4 桌面tigervnc安装 tigervnc启动 tigervnc novncxfce4 启动弹窗如何关闭 xf
  • MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现

    MySQL 普通查询 流式查询 游标查询以及使用 mybatis 如何实现 MySQL 普通查询 流式查询 游标查询以及使用 mybatis 如何实现普通查询流式查询游标查询mybatis 如何实现参考 MySQL 普通查询 流式查询 游标
  • Thread.stop() 与 ThreadDeath 的理解、注意点以及误区

    MySQL 普通查询 流式查询 游标查询以及使用 mybatis 如何实现 Thread stop 的缺陷顶级的异常处理器不会打印关于 96 ThreadDeath 96 的信息ThreadDeath 只是一个通知 xff0c 捕获 Thr
  • spring 远程连接 rabbitMQ 报错

    1 springboot配置文件 application peroproperties spring rabbitmq username 61 guest spring rabbitmq password 61 guest spring r
  • Linux驱动开发:uboot启动流程详解

    前言 xff1a uboot 作为Linux驱动开发的 三巨头 之一 xff0c 绝对是一座绕不开的大山 当然 xff0c 即使不去细致了解uboot启动流程 依旧不影响开发者对uboot的简单移植 但秉持着知其然知其所以然 的学习态度 x
  • 基于C++实现HTTP的封装

    因为在项目中有和java后台对接的http接口 xff0c 所以在此学习和总结了一下 xff0c c 43 43 如何实现Http协议的post get put等请求方式 xff0c 通过搜集一些资料发现 xff0c 有现成的封装库可以实现
  • Python隐形水印invisible-watermark教程

    隐形水印 xff0c 顾名思义 xff0c 在图片上打上肉眼不可见的水印 xff0c 但是通过特定的解码方式可以查看水印标识 invisible watermark xff0c 一个Python库 xff0c 实现对图片加上隐形水印 xff
  • 【学习笔记】JavaScript 寻找字串的方法:includes/indexOf/search/match

    在处理数据时 xff0c 查找字符串是一种常见的操作 xff0c JavaScript 提供不同的方法来搜索字符串 其中 xff0c 最常用的方法包括 xff1a search indexOf includes 和 match xff0c
  • SLAM学习笔记——从零搭建完整的gazebo多机仿真SLAM(一)

    文章目录 引言第一部分 搭建gazebo仿真环境使用gazebo的building editor搭建地图使用launch文件加载world文件 第二部分 放入你的机器人第三部分 启动SLAM第四部分 使用键盘控制构建地图并保存键盘控制机器人
  • Ubuntu apt-get 国内镜像源替换(新手必看,超详细!各种镜像源网站都有)

    点击名称即可进入对应网站 阿里云镜像开源镜像站 xff08 已经更换地址 xff09 阿里云镜像开源社区镜像站 xff08 新地址 xff09 网易开源镜像站 清华大学开源镜像站 中科大开源镜像站 1 首先进入开源镜像站获取相应连接 这里以
  • 各种通信协议整理

    一个做嵌入式的工程师 xff0c 无论是做硬件还是软件 xff0c 每天都在接触一些通信相关的名词 xff0c 若不认真整理这些概念 xff0c 就很可能会混淆或忘记具体的含义与区别 xff0c 因为最近正好在做项目用到了SPI协议传输数据
  • client-go开发示例

    1 查看 node列表 2 查看pod列表 3 service列表 4 获取sa列表 5 查看deploy列表 6 查看pv列表 7 查看PVC列表 8 查看ns列表 9 查看ingress列表 10 查看Secret列表 11 创建sec
  • Ceph部署

    1 简介 Ceph是一个高性能 可扩容的分布式存储系统 xff0c 它提供三大功能 xff1a 对象存储 xff1a 提供RESTful接口 xff0c 也提供多种编程语言绑定 兼容S3 Swift块存储 xff1a 由RBD提供 xff0
  • API 优先级和公平性(APF)

    1 概述 目前apiserver默认的限流方式太过简单 目前k8s缺少客户端业务请求隔离 xff0c 一个错误的客户端发送大量请求可能造成其他客户端请求异常 xff0c 也不支持突发流量 2 开启APF APF测试 开启APF xff0c
Powered by Hwhale