Kubernetes 高可用API Server

API Serverd的一些硬核原理其实前面说的差不多了,就是认证,鉴权,准入,限流。这是API server里面最核心的能力,还有一些watch的缓存,因为apiserver除了承担之前的那些职责之外,它还承担了一些角色是保护etcd,apiserver是唯一可以访问etcd这样一个组件,可以有效的收敛外面过来的请求,从apiserver通过同一个客户端,以长连接连到etcd里面,有效的减小etcd的压力。

同时它在这边有基于内存的缓存机制,有些读操作你直接到apiserver。

启动APIServer示例


 

构建高可用的多副本 apiserver


高可用模式其实就是冗余部署和负载均衡,冗余部署其实就是任何的硬件是不可靠的,我们不能相信基础架构这个层面是完全可靠的,我们就要为这种可能出现的故障做准备,冗余部署多个节点。

我们可以通过冗余部署来实现apiserver的高可用,但是apiserver本身是无状态的rest server,从宏观上面不看它的技术细节,它就是简单的rest server,接受请求然后将请求存下来,这是apiserver唯一要做的事情,所以它是无状态的,无状态的服务管理就会变得非常的简单。

apiserver是无状态的Rest Server,无状态所以方便Scale Up/down 负载均衡。

在多个 apiserver 实例之上,配置负载均衡·,证书可能需要加上LoadbalancerVIP重新生成·

预留充足的CPU、内存资源


如果是通过kubeadm这种方式部署,通过kubelet将apiserver拉起来的,好处是可以为每个apiserver将其封装为k8s的pod,那么就可以探活了,因为支持探测,我要去get一个真正的url,看它这个url还能不能正常返回,如果不正常返回那么就认为这个服务出错了,那么kubelet就会重启。

第二点,既然使用pod方式运行,那么就可以做资源限制的配置,可以限制你的cpu,限制你的资源。 

随着集群中节点数量不断增多,APIServer对CPU和内存的开销也不断增大。过少的CPU资源会降低其处理效率,过少的内存资源会导致 Pod 被 OOMKilled,直接导致服务不可用。在规划APIServer资源时,不能仅看当下需求,也要为未来预留充分。

在早期的时候,运行生产化集群,集群规模很小,所以给apiserver留的cpu memory都很小,跑着跑着集群规模越来越大,内存就爆了,天天oom,这个时候我要去变更,将内存扩容出来,跑着跑着发现集群越来越慢,因为cpu有限制了,就给了它两个cpu,不能高效的跑,所以这个时候要去调cpu。

所以在生产化部署的时候,一定要预留好我将来我这个集群规模有多大。他会用多少cpu,多少内存,预留出来,这里面就不要想着省钱了,因为apiserver是如此重要。

善用速率限制(RateLimit)


还要确保我的apiserver不会被oom-kill掉,确保它性能基础之上还要限流。我们要用"--max-requests-inflight"和"--max-mutating-requests-inflight"用来保护我的apiserver的一个总体并发的request,确保它不会被压死。同时配合APF,priority and fairness,通过这种方式来确保我做了一个精细化的限流,当然初始阶段使用默认的配置就好了,之后随着业务的变更再去做调整。

APIServer的参数"--max-requests-inflight"和"--max-mutating-requests-inflight"支持在给定时间内限制并行处理读请求(包括Get、List和Watch操作)和写请求(包括Create、Delete、Update和Patch操作)的最大数量。

当APIServer接收到的请求超过这两个参数设定的值时,再接收到的请求将会被直接拒绝。通过速率限制机制,可以有效地控制APIServer内存的使用。

如果该值配置过低,会经常出现请求超过限制的错误,如果配置过高,则APIServer可能会因为占用过多内存而被强制终止,因此需要根据实际的运行环境,结合实时用户请求数量和APIServer的资源配置进行调优。

客户端在接收到拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重APIServer的压力,导致性能进一步降低。

针对并行处理请求数的过滤颗粒度太大,在请求数量比较多的场景,重要的消息可能会被拒绝掉,自1.18版本开始,社区引入了优先级和公平保证(Priority and Fairness)功能,以提供更细粒度地客户端请求控制。

该功能支持将不同用户或不同类型的请求进行优先级归类,保证高优先级的请求总是能够更快得到处理,从而不受低优先级请求的影响。

设置合适的缓存大小


apiserver本身它还是一个缓存,在apiserver去get etcd对象的时候,它会有一个本地cache,watch-cache,它本身是一个ringbuffer,也就是它从etcd拿的那些数据会存在自己的ringbuffer,只要这个ringbuffer没有满,你就可以理解所有的信息都会被缓存掉。如果ringbuffer满了,那么它之前存下的信息就会被覆盖掉。所以ringbuffer就是环状的内存结构。

设置适合的缓存大小,比如--watch-cache-sizes,是apiserver的一个参数,通过这个参数来调节apiserver这边缓存设置多大。集群越大这个值也建议设置的越大,这样apiserver多拿缓存数据减少对etcd的压力,只要本地有就不去查了。

API Server与etcd之间基于 gRPC 协议进行通信,gRPC协议保证了二者在大规模集群中的数据高速传输。gRPC基于连接复用的HTTP/2协议,即针对相同分组的对象,APIServer和etcd之间共享相同的TCP连接,不同请求由不同的stream传输。

一个HTTP/2连接有其stream配额,配额的大小限制了能支持的并发请求。

APIServer提供了集群对象的缓存机制,当客户端发起查询请求时,APIServer默认会将其缓存直接返回给客户端。缓存区大小可以通过参数"--watch-cache-sizes"设置。

针对访问请求比较多的对象,适当设置缓存的大小,极大降低对etcd的访问频率,节省了网络调

用,降低了对 etcd 集群的读写压力,从而提高对象访问的性能。

但是APIServer也是允许客户端忽略缓存的,例如客户端请求中ListOption中没有设置resourceVersion,这时API Server 直接从etcd拉取最新数据返回给客户端。

客户端应尽量避免此操作,应在ListOption中设置resourceVersion为0,APIServer则将从缓存里面读取数据,而不会直接访问etcd。(apiserver是允许客户端忽略缓存的,比如说你要list一个对象,这个对象里面又没有家resourceversion,客户端其实是告诉apiserver说我是不信任你的缓存的,我要的是最新的数据,这个适时候apiserver是穿透的,他要从etcd里面拉取最新的数据,客户端尽量写resource version,这样的话有效的利用了apiserver的缓存)

客户端尽量使用长连接


在架构设计上,优先使用监听,而少用轮询,后端是一个分布式存储,分布式存储支持的并发很弱的,然后你又不相信我apiserver的缓存,你要一直来list数据,我要将这个请求丢到etcd,etcd很累,它会跟不上。

既然etcd apiservre都支持长连接,支持监听机制,那么就建议客户端使用监听机制来获取最新的数据,而不要一直来轮询,一直轮询就会导致我这边压力过大。

监听的话还ok,最多支持长连接,比如单实例支持不了那么多的长连接,比如几十万,上百万,那我还可以横向扩展。通过横向扩展来分散这种并发连接的压力,通过这种机制有效的减少了对服务器端的压力。

当查询请求的返回数据较大且此类请求并发量较大时,容易引发TCP链路的阻塞,导致其他查询操作超时。

因此基于Kubernetes开发组件时,例如某些DaemonSet和Controller,如果要查询某类对象,应尽量通过长连接List Watch监听对象变更,避免全量从APIServer获取资源。

如果在同一应用程序中,如果有多个Informer 监听 API Server 资源变化,可以将这些Informer

合并,减少和APIServer的长连接数,从而降低对APIServer的压力。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/125859731