11.6 运行高可用集群
在Kubernetes上运行应用的一个理由就是,保证运行不被中断,或者说尽量少地人工介入基础设施导致的宕机。为了能够不中断地运行服务,不仅应用要一直运行,Kubernetes控制平面的组件也要不间断运行。接下来我们了解一下达到高可用性需要做到什么。
11.6.1 让你的应用变得高可用
当在Kubernetes运行应用时,有不同的控制器来保证你的应用平滑运行,即使节点宕机也能够保持特定的规模。为了保证你的应用的高可用性,只需通过Deployment资源运行应用,配置合适数量的复制集,其他的交给Kubernetes处理。
运行多实例来减少宕机可能性
需要你的应用可以水平扩展,不过即使不可以,仍然可以使用Deployment,将复制集数量设为1。如果复制集不可用,会快速替换为一个新的,尽管不会同时发生。让所有相关控制器都发现有节点宕机、创建新的pod复制集、启动pod容器可能需要一些时间。不可避免中间会有小段宕机时间。
对不能水平扩展的应用使用领导选举机制
为了避免宕机,需要在运行一个活跃的应用的同时再运行一个附加的非活跃复制集,通过一个快速起效租约或者领导选举机制来确保只有一个是有效的。以防你不熟悉领导者选举算法,提一下,它是一种分布式环境中多应用实例对谁是领导者达成一致的方式。例如,领导者要么是唯一执行任务的那个,其他所有节点都在等待该领导者宕机,然后自己变成领导者;或者是都是活跃的,但是领导者是唯一能够执行写操作的,而其他的只能读数据。这样能保证两个实例不会做同一个任务,否则会因为竞争条件导致不可预测的系统行为。
该机制自身不需要集成到应用中,可以使用一个sidecar容器来执行所有的领导选举操作,通知主容器什么时候它应该活跃起来。一个Kubernetes中领导选举的例子:https://github.com/kubernetes/contrib/tree/master/election。
保证应用高可用相对简单,因为Kubernetes几乎替你完成所有事情。但是假如Kubernetes自身宕机了呢?如果是运行Kubernetes控制平面组件的服务器挂了呢?这些组件是如何做到高可用的呢?
11.6.2 让Kubernetes控制平面变得高可用
本章一开始,学习了Kubernetes控制平面的一些组件。为了使得Kubernetes高可用,需要运行多个主节点,即运行下述组件的多个实例:
- etcd分布式数据存储,所有API对象存于此处
- API服务器
- 控制器管理器,所有控制器运行的进程
- 调度器
不需要深入了解如何安装和运行这些组件的细节。让我们看一下如何让这些组件高可用。图11.18 显示了一个高可用集群的概览。
图11.18 三节点高可用集群
运行etcd集群
因为etcd被设计为一个分布式系统,其核心特性之一就是可以运行多个etcd实例,所以它做到高可用并非难事。你要做的就是将其运行在合适数量的机器上(3个、5个或者7个,如章节刚开始所述),使得它们能够互相感知。实现方式通过在每个实例的配置中包含其他实例的列表。例如,当启动一个实例时,指定其他etcd实例可达的IP和端口。
etcd会跨实例复制数据,所以三节点中其中一个宕机并不会影响处理读写操作。为了增加错误容忍度不仅仅支持一台机器宕机,需要运行 5个或者 7个etcd节点,这样集群可以分别容忍 2个或者3个节点宕机。拥有超过7个实例基本上没有必要,并且会影响性能。
运行多实例API服务器
保证API服务器高可用甚至更简单,因为API服务器是(几乎全部)无状态的(所有数据存储在etcd中,API服务器不做缓存),你需要多少就能运行多少API服务器,它们直接不需要感知对方存在。通常,一个API服务器会和每个etcd实例搭配。这样做,etcd实例之前就不需要任何负载均衡器,因为每个API服务器只和本地etcd实例通信。
而API服务器确实需要一个负载均衡器,这样客户端(kubectl,也有可能是控制器管理器、调度器以及所有Kubelet)总是只连接到健康的API服务器实例。
确保控制器和调度器的高可用性
对比API服务器可以同时运行多个复制集,运行控制器管理器或者调度器的多实例情况就没那么简单了。因为控制器和调度器都会积极地监听集群状态,发生变更时做出相应操作,可能未来还会修改集群状态(例如,当ReplicaSet上期望的复制集数量增加 1 时,ReplicaSet控制器会额外创建一个pod),多实例运行这些组件会导致它们执行同一个操作,会导致产生竞争状态,从而造成非预期影响(如前例提到的,创建了两个新pod而非一个)。
由于这个原因,当运行这些组件的多个实例时,给定时间内只有一个实例有效。幸运的是,这些工作组件自己都做了(由--leader-elect选项控制,默认为true)。只有当成为选定的领导者时,组件才可能有效。只有领导者才会执行实际的工作,而其他实例都处于待命状态,等待当前领导者宕机。当领导者宕机,剩余实例会选举新的领导者,接管工作。这种机制确保不会出现同一时间有两个有效组件做同样的工作(见图 11.19)。
控制器管理器和调度器可以和API服务器、etcd搭配运行,或者也可以运行在不同的机器上。当搭配运行时,可以直接跟本地API服务器通信;否则就是通过负载均衡器连接到API服务器。
图11.19 只有一个控制器管理器和一个调度器有效;其他的待机
控制平面组件使用的领导选举机制
我发现最有趣的是:选举领导时这些组件不需要互相通信。领导选举机制的实现方式是在API服务器中创建一个资源,而且甚至不是什么特殊种类的资源——Endpoint资源就可以拿来用于达到目的(滥用更贴切一点)。
使用Endpoint对象来完成该工作没有什么特别之处。使用Endpoint对象的原因是只要没有同名Service存在,就没有副作用。也可以使用任何其他资源(事实上,领导选举机制不就会使用ConfigMap来替代Endpoint)。
你一定对资源如何被应用于该目的感兴趣。让我们以调度器为例。所有调度器实例都会尝试创建(之后更新)一个Endpoint资源,称为kube-scheduler。可以在kube-system命名空间中找到它,如下面的代码清单所示。
代码清单11.11 用于领导选举的kube-scheduler Endpoint资源
不可用了
$ kubectl get endpoints kubia-scheduler -n kube-system -o yaml
control-plane.alpha.kubernetes.io/leader
注释是比较重要的部分。如你所见,其中包含了一个叫作holderIdentity的字段,包含了当前领导者的名字。第一个成功将姓名填入该字段的实例成为领导者。实例之间会竞争,但是最终只有一个胜出。
还记得之前讨论过的乐观并发概念吗?乐观并发保证如果有多个实例尝试写名字到资源,只有一个会成功。根据是否写成功,每个实例就知道自己是否是领导者。
一旦成为领导者,必须顶起更新资源(默认每 2 秒),这样所有其他的实例就知道它是否还存活。当领导者宕机,其他实例会发现资源有一阵没被更新了,就会尝试将自己的名字写到资源中尝试成为领导者。简单吧,对吧?