高可用背后的原理
这两个月和博云合作的项目是要用于客户生产环境的,这个和我以前做的东西有很大的不同,所有基础架构必须给出高可用的解决方案。在这之前我只做过一些流量较小的用户产品或者一些原型项目,一开始基础架构都只给出了单节点的解决方案,结果被大师兄喷这个在生产环境根本不可用。不过事实确实是在一个真实的分布式系统中硬件损坏、进程崩溃、网络不通都可能直接导致系统不可用,所以后来花了很大的精力将项目的基础架构层面改造成完全高可用的,这个过程对一个之前完全没做过这方面东西的人来说确实会感觉比较困难,不过在这个过程中也确实实践了不少分布式系统的理论,了解了各种各样的高可用解决方案,也对Kubernetes的架构运行原理有了更加深刻的认识。下面我会从我调研的高可用解决方案的各个细节来给大家介绍一下高可用的Kubernetes集群背后的原理。
ETCD高可用
Kubernetes使用ETCD持久化存储集群状态信息。ETCD是CoreOS开源的一个强一致性的分布式键值存储服务,ETCD使用raft算法将一组主机组成集群,集群中的每个节点都可以根据集群运行的情况在三种状态间切换:follower, candidate 与 leader。leader 和 follower 之间保持心跳。如果follower在一段时间内没有收到来自leader的心跳,就会转为candidate,发出新的选主请求。
集群初始化的时候内部的节点都是follower节点,之后会有一个节点因为没有收到leader的心跳转为candidate节点,发起选主请求。当这个节点获得了大于一半节点的投票后会转为leader节点。
正在上传…重新上传取消
当leader节点服务异常后,其中的某个follower节点因为没有收到leader的心跳转为candidate节点,发起选主请求。只要集群中剩余的正常节点数目大于集群内主机数目的一半,ETCD集群就可以正常对外提供服务。
当集群内部的网络出现故障集群可能会出现“脑裂”问题,这个时候集群会分为一大一小两个集群(奇数节点的集群),较小的集群会处于异常状态,较大的集群可以正常对外提供服务。
ETCD的作用是用来存储,其高可用的很大一个作用是用来备份存储的数据,因此它基于Raft的高可用解决方案会使每个节点都在工作。还有一个值得注意的一点,ETCD 2个节点是不能实现高可用的,最少需要3个节点,这个是Raft协议的特性使之的,大家感兴趣可以去读一下Raft的论文。
控制器高可用保证
Kubernetes 提供了Leader 选举机制,用以确保多个控制器的实例同时运行,并且只有Leader 实例提供真正的服务。其他实例处于准备就绪状态,如果Leader 出现故障,则取代Leader 以保证Pod 能被及时调度。此机制以占用更多资源为代价,提升了Kubernetes控制器的可用性。
资源锁可保存在Configmap、Endpoints 和Lease 三种对象中。推荐使用Lease,因为Lease 对象本身就是用来协调租约对象的,其Spec 定义与Leader 选举机制需要操控的属性是一致的。使用Configmap 和Endpoints 对象更多是为了向后兼容,伴随着一定的负面影响。
以Endpoints 为例,Leader 每隔固定周期就要续约,这使得Endpoints 对象处于不断的变化中。Endpoints 对象会被每个节点的kube-proxy 等监听,任何Endpoints 对象的变更都会推送给所有节点的kube-proxy,这为集群引入了不必要的网络流量。
controller manager和scheduler的高可用
kube-controller-mansger和kube-scheduler,其中kube-controller-mansger和kube-scheduler组件
自身通过选择机制已经实现了高可用,就是说,如果集群里配置了多个kube-controller-manager和
kube-scheduler组件,他们会根据自己的竞争机制选出主用组件。
Kubernetes使用scheduler、controller manager来实现pod的调度和rc、deployment等的副本控制。Kubernetes也是通过controller manager和应用健康检查机制实现了它上面部署应用的高可用。
而scheduler、controller manager的高可用原理和上面又是不一样的,为了保证集群运行正常,scheduler、controller manager同一时刻只允许一个服务处以具体的任务。Kubernetes中实现了一套简单的类似于分布式锁的方案,依赖ETCD实现scheduler和controller-manager的选主功能。
如果scheduler和controller manager在启动的时候设置了 --leader-elect
参数,它们在启动后会先尝试获取leader节点身份,只有在获取leader节点身份后才可以执行具体的业务逻辑。
它们分别会在ETCD中创建scheduler和controller manager的endpoint,endpoint的信息中记录了当前的leader节点信息,以及记录的上次更新时间。leader节点会定期更新endpoint的信息,维护自己的leader身份。每个从节点的服务都会定期检查endpoint的信息,如果endpoint的信息在时间范围内没有更新,它们会尝试更新自己为leader节点。
scheduler服务以及controller manager服务之间不会进行通信,利用ETCD的强一致性,能够保证在分布式高并发情况下leader节点的全局唯一性。
当集群中的leader节点服务异常后,其它节点的服务会尝试更新自身为leader节点,当有多个节点同时更新endpoint时,由Etcd保证只有一个服务的更新请求能够成功。
通过这种机制sheduler和controller manager可以保证在leader节点宕机后其它的节点可以顺利选主,保证服务故障后快速恢复。
etcd使用场景
- 可以用于键值对存储,应用程序可以读取和写入etcd 中的数据
- etcd 比较多的应用场景是用于服务注册与发现
它有lease的机制,续约机制一般用在什么场景下?用于服务发现的场景,比如spring cloud,跑一个应用实例,有一个注册中心,当应用实例运行完之后就要去注册中心把自己注册上,但是注册的时间是有时效性的比如30s,作为客户端如何保证自己是活着的,这就需要不停的去续约,一旦续约没有完成,比如中间网络中断,那么之前的键值就失效了,所以它很自然的就落在了服务发现的场景,我写一个键值对注册自己,并且不停的续约,如果续约不成功,那么键值对失效。
通过这种机制就可以发现整个集群里面的service。
服务注册与发现
有一个服务注册中心,这里都有服务提供者和服务消费者,服务提供者比如说提供了web服务,我将自己的ip和服务名称写到etcd里面,通过续约的机制写进去,通过lease的机制写进去,通过心跳来保持这个续约有效,如果中间链路或者自身出现了问题,那么租约就失败了。
另外一边服务消费者,他就去服务注册中心发现服务,服务有哪些endpoint,只要在服务注册中心里面服务发现这个查询能够发现的这些endpoint,那就说明它是当前的有效的服务提供者,那么就可以产生绑定关系。
消费者要相信etcd会将失效的服务提供者,也就是没有及时续约的,那些提供者的ip都会自动拿掉,通过这种场景就可以做服务注册和发现。
apiserver的高可用
Kubernetes的接入层服务主要是kube-apiserver,官方文档上说apiserver是Kubernetes资源的唯一入口。其实后来根据我对Kubernetes的使用和对其源码的阅读,可以看作apiserver实现了Kubernetes对象的model存到ETCD里面,然后自己作为一个RESTful的HTTP server和外界交互作CRUD操作。apiserver是无状态的,无状态的意思是在哪个apiserver上面接收请求都会得到相同的结果,我们可以同时开多个apiserver处理外部请求,这一点是和scheduler和controller manager不同的。因此apiserver的高可用没必要像scheduler、controller manager一样通过分布式锁强一致性实现同一时间只有一个leader在工作。
而使用多活apiserver的最大难点是如何实现能保证高可用的负载均衡,为了解决这个问题主要使用了keepalived和Nginx。可以使用keepalived配置节点的优先级和检测失活脚本,然后可以给外部一个同一的VIP,并且保证机器肯定能接收到请求,然后配置Nginx可以将请求负载到可用的apiserver上。
使用keepalived、Nginx这种IP漂移、负载均衡的方式比分布式锁服务保持单个leader这种方式可以保持多个实例、性能更高。
除了这个三个主要的之外,我还做了一些例如kube-DNS、CNI插件Calico、Docker镜像仓库的高可用。但是这些都可以是可以基于Kubernetes的副本控制去做的,这里我就只说明一下Kubernetes自身高可用的解决方案,其他就不赘述了。