Kubernetes 控制平面的高可用性保证

高可用拓扑选项 | Kubernetes

控制平面的高可用保证


聚焦到Kubernetes 集群本身,要想实现Kubernetes 高可用的目标,首要任务是确保控制平面组件高可用。

控制平面的基础核心组件包括etcd、API Server、Scheduler 和Controller Manger。它们之间的任意一个组件不可用,都可能造成用户的请求无法得到及时处理,业务可能因此受到影响。更具挑战性的是,分布式系统由众多组件联动完成不同的控制操作,如果部分组件失效,很可能导致其他组件行为异常。假设Controller Manager 出现问题导致Endpoint 对象无法及时更新,并且kube-proxy 设置的转发规则也无法及时更新,那么将造成业务数据流向异常,进而影响业务的可用性。

控制平面高可用的开发和部署,与Kubernetes 的应用程序类似,应贯穿于组件开发和运维的整个生命周期,遵循类似的设计原则,采用类似的高可用手段,例如合理的故障域规划、业务拆分、冗余设计和负载均衡等。

针对大规模的集群,应该为控制平面组件划分单独节点,减少业务容器对控制平面容器或守护进程的干扰和资源抢占。控制平面所在的节点,应确保在不同机架上,以防止因为某些机架的交换机或电源出问题,造成所有的控制面节点都无法工作。保证控制平面的每个组件有足够的CPU、内存和磁盘资源,过于严苛的资源限制会导致系统效率低下,降低集群可用性。

管理节点配置建议 

名称

最小配置

推荐配置

CPU

8

16 +

Memory

16

64 +

系统盘

100G

200G +

数据盘

200G

500G+

etcd 高可用保证


可以说etcd 是Kubernetes 控制平面组件中唯一的有状态应用。它是Kubernetes 的唯一
数据库
,存储了所有资源对象的配置数据、状态和元数据。因此etcd 是否高可用直接影响
到整个集群的高可用性。

etcd 本身是分布式键值存储数据库。etcd 集群可以有多个成员,成员间采用raft 一致
性协议来保证自身数据的一致性和可用性。多个成员可在不同服务器上同时运行,各自都
维护了集群上的所有数据。
不同于传统的以表格形式存储数据的数据库,etcd 为每个记录
创建一个数据页面,在更新一个记录时不会妨碍其他记录的读取和更新。
如图所示,
etcd 中有3 个数据,即3 个键值对,其键分别是 “/foo”“/bar/this”“/bar/that”,对应的值分别
是数组[“i”,“am”,” array”]、整数42 和字符串 “i am a string”。客户端可利用restful API(通
过数据的地址链接)对这些数据进行同步更新,而不会相互影响。这使etcd 非常容易应对
高并发写请求的应用场景。

                                                   etcd 数据存储形式 

etcd 高可用拓扑


针对etcd 集群部署,社区推荐了两种可选的拓扑方案:堆叠式etcd 集群的高可用拓扑
(Stacked etcd Topology)和外部etcd 集群的高可用拓扑(External etcd Topology)。在构
建etcd 集群时,应充分考虑每个拓扑的优缺点。

1.堆叠式etcd 集群的高可用拓扑


如图3-6 所示,堆叠式是指etcd 堆叠在运行控制平面组件的管理节点(也就是常说的Master 节点)之上。每个Master 节点上都运行了etcd、API Server、Controller Manager 和Scheduler 的实例。所有Master 节点上的etcd 实例组成etcd 集群,但API Server 仅与此节点本地的etcd 成员通信。

                                                图3-6 堆叠式etcd 集群的高可用拓扑

这种拓扑将相同节点上的控制平面和etcd 成员耦合在一起。优点在于建立起来非常容易,并且对副本的管理也更容易。

但是,堆叠式存在耦合失败的风险。如果一个节点发生故障,则etcd 成员和控制平面实例都会丢失,并且集群冗余也会受到损害。可以通过添加更多控制平面节点来减轻这种风险。因此,为实现集群高可用应该至少运行三个堆叠的Master 节点。(下面可以看到在单节点master当中,所以kubeadm部署的k8s至少3个master节点)

[root@k8s-master ~]# kubectl get pod --all-namespaces
NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
kube-system            etcd-k8s-master                              1/1     Running   1          291d


[root@k8s-master ~]# kubectl get pod  -n kube-system -o wide
NAME                                       READY   STATUS    RESTARTS   AGE    IP                NODE         NOMINATED NODE   READINESS GATES
etcd-k8s-master                            1/1     Running   1          291d   192.168.179.102   k8s-master   <none>           <none>

2.外部etcd 集群的高可用拓扑


 如图3-7 所示,外部etcd 集群的高可用拓扑是指etcd 集群位于运行控制平面组件的Master 节点之外。Master 节点上仅运行了API Server、Controller Manager 和Scheduler 的实例,etcd 成员则在其他单独主机上运行,组成存储集群。API Server 可与etcd 集群中任意一个etcd 成员进行通信。

                                               图3-7 外部etcd 集群的高可用拓扑 

这里API Server 有两种etcd 地址的配置方式:

  1. 第一种是配置etcd 集群的负载均衡器的VIP 地址,具体连接到哪个etcd 实例由负载均衡策略决定。
  2. 第二种是配置多个etcd 实例的地址,用逗号隔开,API Server 会按顺序依次尝试连接。如果采用第二种方式,那么为了均衡etcd 集群的流量,每个API Server 处etcd 实例地址的顺序配置应略有不同。

该拓扑将控制平面和etcd 成员解耦。丢失一个Master 节点对etcd 成员的影响较小,不会像堆叠式拓扑那样对集群冗余产生太大影响。但是,此拓扑所需的主机数量是堆叠式拓扑的两倍。具有此拓扑的集群至少需要三个主机用于控制平面节点,三个主机用于etcd集群。 

什么不管哪种拓扑结构都至少需要3 个etcd 实例呢?原因有两个。

第一,如果 etcd 集群仅有一个成员,那么一旦这个成员出现故障,会导致整个Kubernetes 集群不可用。

第二,基于raft 协议的etcd 的Leader 选举和数据写入都需要半数以上的成员投票通过确认,因此,集群最好由奇数个成员组成,以确保集群内部一定能够产生多数投票通过的场景。这也就是为什么etcd 集群至少需要3 个以上的成员。建议etcd集群成员数量不要超过7 个,推荐是3 个或5 个。个数越多,投票所需时间就越多,写的吞吐量会越低。虽然提高了读的性能和可用性,但是极大损伤了写的性能。具体应该是3 个还是5 个呢?

etcd 集群是有一定的容灾能力的,并且能够自动从临时故障中恢复(例如节点重启)。对于一个N 节点的集群,允许最多出现在 (N-1)/2 个节点发生永久性故障(比如硬件故障或磁盘损耗)之后还能正常对外服务。当永久性故障的节点个数超过 (N-1)/2 时,就会陷入不可逆的失去仲裁的境地。一旦仲裁丢失,集群就无法保证一致性,因此集群就会置为只读模式,无法再接收更新请求了。

3 个成员的etcd 集群是高可用的最低需求。etcd 实例的数目越少,其维护成本越低,并且只需2 个成员投票即可将数据写入,因此具有较高的效率。仅允许1 个成员发生故障。发生故障后,需要运维人员立即介入。如若另一个成员再发生故障,集群将会变成只读的。只读的集群表象是平台变为静止。但因为集群中所有节点的kubelet 都无法不汇报状态,所以Controller Manager 中的Pod 驱逐控制器会将所有节点上的Pod 放入驱逐队列,一旦etcd 恢复,会导致大量Pod 被同时驱逐,从而造成服务器过载、服务不可用等事故。

[root@master02 ~]# kubectl get node
Error from server: etcdserver: request timed out

 5 个成员的etcd 集群是一种常见的配置,此配置具有更高的可用性。只有当超过两个成员出现故障时,集群才变成只读的。因此,当第一个成员出现故障时,管理员无须立即介入。对运维响应速度的要求降低了,这是以管理复杂度换取运维响应时间的常规手段。

etcd 性能调优


etcd 的性能影响了Kubernetes 操作的吞吐量和延时,进而影响了集群的稳定性。应实时监测每个etcd 成员的性能,及时修复或移除性能差的成员。否则这些成员可能会拖慢整个etcd 集群的处理速度。

另外,当发现etcd 集群性能低下时,应如何调优etcd 性能呢?影响etcd 的性能(尤其是提交延迟)的因素主要有两个:网络延迟和磁盘I/O 延迟,即etcd成员之间的网络往返时间(Round Trip Time,RTT)和将数据提交到永久存储所需的时间。

API Server 高可用保证


API Server 作为Kubernetes 集群的API 网关,接收来自用户和其他Kubernetes 组件的所有restful 请求。其本身是无状态的。数据的持久化职责均由后端数据库etcd 承担

当API Server 压力较大无法支撑并发业务需求时,横向扩展实例数量是最直接有效的方式。
采用堆叠式etcd 集群的拓扑时,API Server 与etcd 一对一部署在相同的Master 节点上,为
了不影响etcd 集群的成员个数,可以将API Server 单独进行横向扩容。多实例冗余与负载
均衡是API Server 高可用的主要手段。 

API Server 高可用配置
对于实力冗余与负载均衡,如图所示,最直接的方案是客户端及其他控制平面组件使用负载均衡器与API Server 通信。虽然这样可以均衡API Server 的负载但是将单点故障转移到了负载均衡器上。 

 因此,在这个基础上还应进行一些优化配置,以获得更高的容错性和灵活性。

1.多个负载均衡器
如图3-10 所示,在配置多个负载均衡器时,API Server 提供负载均衡层的连接冗余。通过为这些负载均衡器设置智能DNS 或虚拟IP,使得客户端及其他控制平面组件使用DNS 或虚拟IP 与API Server 通信。如果其中一个负载均衡器发生故障,那么该负载均衡器上的流量将被移除,流量将被转移到其他负载均衡器,最终转发到API Server 处。

2.完善的健康检查

即使API Server 采用多实例运行,也很难保证每一个实例都能一直正常地运行下去。

因此,在负载均衡器处应该对每个实例进行健康状态检查。当某个实例处于不健康的状态时,负载均衡器应停止向这个实例分发请求。API Server 的健康状态检查是否准确将直接影响到API Server 的高可用。如果出现误报,例如API Server 不能进行服务但状态却标记为健康,那么就会造成客户的某些请求不能及时响应。

API Server 是所有客户端访问etcd 的入口,其生命周期应与etcd 紧耦合。API Server的早期版本对etcd 所做的健康检查只依赖ping 命令。但是有这样的场景:etcd 处于假死状态,ping 的结果返回成功,但etcd 的真实请求可能已经无法执行。API Server 由于没有感知到etcd 的异常,所以会继续接收用户请求,但接收的请求无法被etcd 处理,从而导致超时。

在后续的版本中,etcd 的健康检查被强化,API Server 的健康检查是基于etcd 的真实请求,如果etcd 无法处理该请求,那么API Server 会将自身状态置为 “不健康” 以避免继续提供服务。

对于集群内部的客户端,应优先访问API Server 的ClusterIP,利用kube-proxy 建立的转发规则将流量送达API Server 处。这样,当外部的负载均衡器出现问题时,不会影响集群内部客户端访问API Server,也不会对集群内的客户端产生巨大影响。

API Server 性能调优


API Server 是API 请求处理的中枢,API Server 的处理能力直接影响集群的处理能力。随着集群规模的增大,默认的API Server 参数可能无法再满足其性能要求。因此,有必要对集群的未来规模做出预判,并调整参数以适应业务发展,具体方法如下:


1.预留充足的CPU、内存资源

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

2.善用速率限制(RateLimit)
API Server 的参数 “--max-requests-inflight” 和 “--max-mutating-requests-inflight” 支持在
给定时间内限制并行处理读请求(包括Get、List 和Watch 操作)和写请求(包括Create、
Delete、Update 和Patch 操作)的最大数量。当API Server 接收的请求超过这两个参数设定
的值时,再接收的请求将会被直接拒绝。

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


客户端在接收拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重API Server 的压力,导致性能进一步降低。针对并行处理请求数的过滤颗粒度太大,在请求数量比较多的场景,重要的消息可能会被拒绝掉,自1.18 版本开始,社区引入了优先级和公平保证(Priority and Fairness)功能,以提供更细粒度的客户端请求控制。该功能支持将不同用户或不同类型的请求进行优先级归类,保证高优先级的请求总是能够更快得到处理,从而不受低优先级请求的影响。

3.设置合适的缓存大小
API Server 与etcd 之间基于gPRC 协议进行通信,gPRC 协议保证了两者在大规模集群
中的数据能够高速传输。
gPRC 协议是基于HTTP2 协议的。HTTP2 通过stream 支持了连接的多路复用。一条T
CP 连接可以包含多个stream,多个stream 发送的数据互不影响。在API Server 和etcd之间
,相同分组的资源对象的请求共享同一个TCP 连接,组内不同资源对象的请求由不同的st
ream 进行传输。多路复用会引入资源竞争,流量控制可以保证stream 之间不会严重影响
彼此,但是也限制了能支持的并发请求数量。
API Server 提供了集群对象的缓存机制,当客户端发起查询请求时,API Server 默认
会将其缓存直接返回给客户端。缓存区大小可以通过参数“--watch-cache-sizes”进行设置。
针对访问请求比较多的对象,适当设置缓存的大小,能够降低etcd 的访问频率,节省网络
调用,减少etcd 集群的读写压力,从而提高对象访问的性能。
但是API Server 也是允许客户端忽略缓存的,例如客户端请求中ListOption 中没有设
置resourceVersion,这时API Server 直接从etcd 拉取最新数据返回给客户端。客户端应尽
量避免此操作,应在ListOption 中设置resourceVersion 为0,API Server 将从缓存中读取数
据,而不会直接访问etcd。

4.客户端尽量使用长连接
当查询请求的返回数据较大且此类请求并发量较大时,容易引发TCP 链路的阻塞,
导致其他查询操作超时。因此,基于 Kubernetes 开发组件时,例如某些 DaemonSet 和Con
troller,在查询某类对象时,应尽量通过长连接ListWatch 监听对象变更,避免全量从API
Server 获取资源。在同一应用程序中,如果有多个Informer 监听API Server 的资源变化,
则可以将这些Informer 合并,减少与API Server 的长连接数,从而降低对API Server的压力

控制器高可用保证


 Kubernetes 提供了Leader 选举机制,用以确保多个控制器的实例同时运行,并且只有Leader 实例提供真正的服务。其他实例处于准备就绪状态,如果Leader 出现故障,则取代Leader 以保证Pod 能被及时调度。此机制以占用更多资源为代价,提升了Kubernetes控制器的可用性。

Leader 选举的核心是利用Configmap、Endpoints 或Lease 对象实现分布式资源锁。当多个实例同时启动后,在运行任何业务逻辑之前,都会尝试读取该资源锁。

以Lease 对象为例,在首次抢占过程中,该对象无任何Leader 信息,第一个尝试占有锁的实例会更新该对象的acquireTime 和holderIdentity,并以leaseDurationSeconds 为周期不断更新renewTime。

控制器的判断逻辑是:只有当holderIdentity 与当前实例的Pod 名称完全匹配时,控制器的程序执行才继续,否则等待获取锁。如果Leader 能保证在固定周期内及时更新renewTime,则该锁始终被Leader 占有,任何其他实例周期性地尝试更新holderIdentity 以成为新的Leader。该机制保证Leader 实例出现故障或网络断开时,其Leader租约会到期,其他实例可抢占资源锁迅速成为新Leader。Lease 对象的示例代码如下:

 资源锁可保存在Configmap、Endpoints 和Lease 三种对象中。推荐使用Lease,因为Le
ase 对象本身就是用来协调租约对象的,其Spec 定义与Leader 选举机制需要操控的属性是
一致的。使用Configmap 和Endpoints 对象更多是为了向后兼容,伴随着一定的负面影响

以Endpoints 为例,Leader 每隔固定周期就要续约,这使得Endpoints 对象处于不断的变
化中。Endpoints 对象会被每个节点的kube-proxy 等监听,任何Endpoints 对象的变更都会
推送给所有节点的kube-proxy,这为集群引入了不必要的网络流量。
任何集群控制器均可基于Leader 选举机制进行开发部署。调度器是一个 “特殊” 的控
制器,它基于Leader 选举机制,用于保证服务高可用。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/120064716#comments_22933833
今日推荐