
课程出品人
谢楚瑜,光大科技有限公司,研发工程师。在金融科技行业从事技术研发,负责公司内Kubernetes集群
的运维。2020 容器云职业技能大赛百位专家委员会成员。
课程简介
高可用(HighAvailability),是设计分布式系统架构所必须要考虑的因素之一,它通常是指,通过设计
减少系统不能提供服务的时间。任何系统的中断,都会带来不同程度的损失。所以为了避免损失,我们需要
尽可能的提高平台中的可用性。
那么如何为容器云平台实现高可用呢?对于其中的应用又要如何保障其可用性呢?在这一章中您将会学
习容器云平台的系统架构,并且自底向上的为其中的每一个部分实现高可用
N.1 绪论
N.1.1 什么是高可用
高可用(HighAvailability),是设计分布式系统架构所必须要考虑的因素之一,它通常是指,通过设计
减少系统不能提供服务的时间。
虽然大部分情况下,我们的系统都能够很好的运行。但绝对不会故障的系统是不存在的。无论代码写
的多好,硬件如何的可靠,也无法保证整个系统的稳定。因为故障总是会发生在意想不到的地方。
比如 2015 年 5 月 27 日支付宝在杭州的一个机房光纤被挖断,导致部分用户无法正常使用支付宝,直
到当日晚上 7 点 20 分,支付宝才宣布用户服务恢复正常。
再以微信为例,在 2019 年 10 月 29 日微信发生了一场事故:与银联系统对接时发生网络故障,导致
支付系统宕机近 30 分钟,使得近两千万笔订单无法正常的完成。
阿里腾讯的技术水平不可谓不强,而支付业务又是其中的重中之重。尽管如此,还是会有一些揪心的
时刻。为了能够尽可能的减少服务的不可用时间,最大降低损失,我们可以通过架构设计的手段为服务提
供强的可用性,即高可用架构设计。
N.1.1.1 高可用架构的设计原则
◎ 冗余:单点永远是高可用的最大敌人。任何组件都有可能奔溃,哪怕只是简单的执行ping命令。单
点组件一旦奔溃,如果没有后备组件的话,那么这一组件就算是彻底不可用了。为了保证组件的故障不会
导致整个系统的故障,我们应当为每一个组件都留有一个到 N 个冗余后备。
◎ 故障转移:出了故障后,要是请求还在往故障的节点上发,那么有再多的冗余节点也是毫无意义。
所以需要自动的检查故障的产生,并将流量转发到冗余中的可用节点上。而想要及时的实现故障转移,我
们就需要有健康检查机制去检查服务的状态。
◎ 排查异常:如果高可用架构设计完美,那么即使发生故障,用户也很可能永远感知不到。但这并不
代表就不需要检查并解决故障了,定期的维护可以进一步提高系统的可用性。
N.1.1.2 其他未能提到的机制
对于高可用架构还应当考虑服务分级与降级,灾难恢复与异地多中心等等机制或场景。随着服务规模
的不断扩张,保持或提高可用性的成本会以一种夸张的方式上涨。由于篇幅有限,对于很多知识点都难以
面面俱到的深入探讨,在实际应用中还是需要更多的参考实际场景来进行规划。
N.1.2 容器云平台架构介绍
通过前一节的介绍我们可以知道,如果想要保证整个云平台的高可用,对于云平台各个部分自然是需
要一定程度了解的。接下来让我们看看常见的容器云平台架构:
◎ 基础设施:基础设施指的是位于架构中最底层的物理设备及相应的资源池。虽然基础设施通常都有
专门的团队进行维护,或者使用了诸如 AWS、阿里云之类的公有云服务。但是我们仍然需要能够为应用实
现对物理机的亲和与反亲和(如多个高 IO 应用避免部署在同一个物理机下的不同虚拟机中),对于部分应
用还需要做到分机房部署。
◎ 容器云平台:容器云本质上我们可以将其看作就是一个普通的 Web 应用(通常是无状态的,持久化
数据使用集群 etcd),通过将其部署到 Kubernetes 中,并配置好健康检查与副本集来为其实现高可用。
◎ 私有镜像仓库:如果我们的容器云平台是私有云,或者我们的 CI\CD 制品不想上传到公网上,那么
我们就会需要一个自己的私有镜像仓库。在容器云平台完成相关设置后,从此以后私有的镜像就可以都从
内网中拉取。当有 Pod 不可用,开始漂移时,这个 Pod 可能会飘到一个并没有去过的节点中,这时候就会
需要向私有镜像仓库拉取镜像。如果这个过程失败了,那么我们的 Pod 就会长时间处于不可用的状态,进
而可能会对业务的可用性造成影响。
◎ 容器云平台纳管的 Kubernetes 集群:这些集群是通过容器云平台进行创建和管理的集群。通常多云
架构中会包含这一层,这样通过在多个云厂商下部署集群来提高更多的冗余。当然,这一层并不是必要的。
◎ 容器云托管应用:在布置好容器云平台后,我们就可以在平台上部署应用了。这一部分除了容器云
平台的用户所部署的应用外,还包含诸如监控、日志收集和网格网络等由容器云管理者所创建的应用。这些
应用的可用性和状态则由容器云平台和 Kubernetes 共同维护, 而这部分内容会在后面的小节中具体描述。
N.2 Kubernetes集群高可用
在容器这一新秀刚刚发展起来的那段时间里,曾经出现过大量的容器编排系统。其中以Swarm、
Mesos和Kubernetes三个项目最受欢迎。在经过多年的竞争与发展后,如今,由 Kubernetes 构建的容器生
态体系已经成为了容器领域中的事实标准。
安装一个高可用的 Kubernetes 集群是保障容器云平台高可用的第一步。尽管我们可以通过诸如
Kubeadm、RKE或 Kops 之类的工具轻松的安装一个 Kubernetes 集群,但是了解一下常见的Kubernetes
高可用的实现方法才可以帮助我们更好的维护集群。
N.2.1 Kubernetes高可用架构
如图所示,我们将一个高可用的 Kubernetes 集群分为了由三种节点组成的集群。这三种节点分别是
Control Panel节点、Worker节点、Etcd节点。
N.2.2 Control Panel节点
ControlPanel 主要负责对集群进行全局决策,他们检测并响应集群事件。我们会将 Control 节点和etcd
节点部署在一起,因此Control节点个数会受etcd节点个数制约而保持一致,在后面我们会介绍etcd节点的
高可用部署方式。Control节点各组件描述如下:
ApiServer:ApiServer 是 Kubernetes 服务的入口,我们对于Kubernetes所做的任何操作都是通过 Api-
Server进行的。为了实现该组件的高可用,需要在在全部Control Panel节点上部署ApiServer,并为这些 Pod
都配置到同一个负载均衡器中。再将其他组件中的 Api 地址指向这个负载均衡器。
负载均衡的高可用有很多种方式可以选择,软件方式通常以HAproxy+Keepalived提供VIP来实现。这样
的话,只要还有一个 ApiServer Pod 可以使用,那么请求就可以通过负载均衡发送到 ApiServer 中去处理。
◎ Controller Manager:Controller Manager 当中包含了大量的控制器,这些控制器监听着 ApiServer
中全部资源的状态,并始终维护这些资源到正确的状态。通过这个组件,Kubernetes 实现了其自愈性与声
明式创建的功能。同样的,为了实现该组件的高可用,需要在在全部 Control Panel 节点上部署 Controller
Manager。
◎ Schedule:Schedule 负责在创建工作负载(Workload)时,为工作负载中各个 Pod 的部署提供推
荐的节点。如果我们在配置中加入了亲和性、反亲和性、选择器、资源配额等配置的话,Schedule 在调度
Pod 时会优先考虑这些设置,如果不存在符合用户要求的节点, 哪怕集群中计算资源足够多,这个 Pod 也
不会被部署到集群中。这个组件的高可用也是通过在全部 Control Panel 节点上部署即可。
N.2.3 Etcd节点
Etcd 是 Kubernetes 中的各类数据的存储中心,对于在 ApiServer 中我们可以看到的各种数据都是持久
化在 Etcd 中的,一旦 etcd 不可用那么 ApiServer 也会不可用。而维护 Kubernetes 中大部分组件状态所使
用的 Controller Manager 组件又依赖于 ApiServer 的可用。所以当 etcd 出现故障时,虽然各个工作负载可
能还是能正常运行,但是其可用性已经完全失去了保障,同时我们也将无法再对这个 Kubernetes 集群进行
任何变更。
所以说 Etcd 是 Kubernetes 中至关重要的一个组件。想要为这个组件实现更高的可用性和稳定性的话,
对于这个组件的一些特性有必要进行进一步的了解。
◎ Raft 选举:Etcd 的高可用方案类似与 Redis 的哨兵机制。首先我们创建一个 Etcd 集群,这时候每
一个 Etcd 节点都是 Follower 节点,而在经历一轮选举后,这个 Etcd 集群中就会产生一个 Leader 节点。
这时候我们所有的改动与新增只需要与 Leader 通信,Leader 再将数据同步到其他的 Etcd 节点中。这种同
步算法就是Raft算法。如果想要多了解一些 raft 算法的机制,可以看一下 http://thesecretlivesofdata.com
/raft/
在 Etcd 中,数据的同步和 Leader 的选举都需要大多数的节点达成共识,在这里,大多数指的是(n -
1)/2(向下取整)。由这个算式我们可以发现,偶数个节点并不能为集群提供更多的冗余,所以通常情况
下,我们都会部署一个 3 节点的集群,如果对于 Master 的安全性有更高的要求,可以布置 5 节点集群,但
是集群大小最好不要大于 7。更大的集群虽然可以提供更高容错能力,但是由于 raft 的同步机制原因,会对
写入能力造成很大的影响,所以不建议部署超过 7 个节点的 etcd 集群。
一个 Raft 集群只能有一个 Leader 存在,如果一旦发生网络分区,Leader 只会在多数派一边被选举出
来,而少数派则全部处于 Follower 或 Candidate 状态,所以一个长期运行的集群是不存在脑裂问题的。尽
管短期内的多个 Leader 并存的现象是可能存在的,但在 etcd 中也有 ReadIndex、Lease read 机制来解决
这种状态下的一致性语义问题。这样一来, 在出现网络分区后,少数派中的节点将不再支持读写,也就是
牺牲可用性换取了数据的强一致性。除此之外我们可以通过减少选举周期(election-timeout)的时长来尽可
能的减少多个 Leader 并存这一状况的产生时间。
虽然我们可以通过--consistency=s选项开启对于非一致性读请求的支持,以提高可用性。而且在某
些场景下,我们可能就是需要牺牲一致性换取更高的可用性。但在Kubernetes中,弱一致性可能会导
致集群中应用的状态出现异常,所以还是用默认设置比较好。
◎ 数据持久化:Etcd 的数据会时刻以日志的形式记录在内存中和硬盘的 wal 文件中。在这个过程中
etcd 对磁盘的延迟会非常敏感,如果有意外的磁盘活动导致fsync延迟变长,很可能会导致 etcd 错过心跳
包,从而产生请求超时或 Leader 丢失的结果。所以我们需要给etcd 配置更高的磁盘优先级来避免这种情况
的发生。
在 linux 上,我们可以通过 ionice 来配置磁盘优先级:
如果可以的话,给 etcd 挂载 ssd 盘可以显著的提升 etcd 的性能。
◎ 数据同步: Etcd 的 Leader 在收到变更时,会将变更发送到各个 Follower 节点。当集群中的大多数
节点返回了认同变更的响应后,Leader 才会 commit 这个变更,并将这个变更同步到各个 Follower 中,这种
行为就是二阶段提交(Two phase Commit)。如果说这个过程中存在较大的网络延迟,也会极大的影响
Etcd 的写入能力,产生请求超时甚至 Leader 丢失的结果。通过减少节点间的物理距离,减少网络拥塞,提
高设备吞吐能力等方式可以有效的减少网络延迟,在实际的高可用环境中,我们需要将etcd节点部署在低延
迟的网络里。
◎ 快照: Etcd 默认会记录 10000 条更改日志,当超过这个阈值的时候,etcd 将会生成一个快照记录
当前资源的状态,并删除当前内存和硬盘中过时的日志记录。如果这个值太高可能会导致 Etcd 占用过多的
内存,甚至达到触发 OOM 的程度,可能导致集群中出现 Pod 漂移以及 Control Panel 不可用。如果需要减
少 etcd 的内存和磁盘占用,我们可以通过下面的方法修改阈值为 5000:
由于Etcd集群中数据会保持一致,所以当我们发现某一个Etcd节点内存紧张的时候,往往整个Etcd集
群的节点内存都以及处于比较紧张的状态了。为Etcd的内存状态专门设置监控和告警是很有必要的。
N.2.4 Worker节点
worker 节点本质上不存在高可用问题,他是 Kubernetes 中工作负载(即 Deployment,DaemonSet,-
Job 等对象)的载体。我们关心的往往不是 Worker 节点是否可用,而是运行在其中的工作负载是否可用。
在 Worker 节点中通常有以下组件:
◎ Kubelet: kubelet 是一个 Agent,用来维护各个节点上的 Pod 与容器间的状态。kubelet 接收一组
通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs 中描述的容器处于运行状态且健康,而不会管理
非 Kubernetes 创建的容器。
◎ KubeProxy: KubeProxy 则是维护集群网络规则的代理。这个组件会维护节点上的网络规则,这些
网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
KubeProxy 可以配置为iptables代理模式、IPVS代理模式或User space代理模式。其中User space
模式 与 iptables模式 都会依赖于 iptables 来制定网络规则,随着 Iptables 中规则数量的增加,会导致
内核逐渐变得非常繁忙,而专为负载均衡设计的 IPVS 则并不存在这个问题,IPVS 几乎可以无限扩容。
同时 IPVS 模式也支持更高的网络吞吐量。但是我们需要在全部的节点中安装 IPVS 内核模块后才能正
常的使用 IPVS 模式。
◎ Container Runtime:容器运行时是负责运行容器的软件。Kubernetes 支持多种容器的运行时: Docker、
containerd、cri-o、 rktlet 以及任何实现 Kubernetes CRI (容器运行环境接口)。
在后面的内容中,我们会重点讲解如何为集群中的应用配置高可用性,使其能够更加灵活的面对诸如
worker 节点不可用、网络故障等种种问题
N.3 应用高可用保证
高可用性是 IT 基础架构中的一个核心原则,用于保持应用程序正常启动并运行,即便在站点的一部分
或整体发生故障后也不例外。高可用性的主要目的是消除 IT 基础架构中的潜在故障点。例如,可以通过添
加冗余并设置故障转移机制来准备好应对一个系统发生故障的情况。
虽然我们已经有了一个高可用的 Kubernetes 集群,但是这并不代表我们部署在上面的工作负载也会自
动变得高可用。为了能够让我们部署在上面的应用(包括容器云平台这个应用)能够实现高可用,需要了
解阻碍我们实现高可用的故障主要都存在于何处,以及如何为 Kubernetes 中的工作负载实现高可用。
N.3.1 Kubernetes集群中的潜在故障点
在这张图中我们罗列了常见的5个潜在的故障点。通过认识这些故障点,可以帮助我们通过冗余和反亲
和性的方式为应用带来更高的可用性。
N.3.1.1 容器或者Pod故障
根据设计,容器和pod的生存时间短,并且可能会意外发生故障。例如,如果应用程序中发生错误,那
么容器或pod可能会崩溃。为了使应用程序高度可用,必须确保应用程序有足够的实例来处理工作负载,另
外还有其他实例用于发生故障的情况。理想情况下,这些实例分布在多个工作程序节点上,用于保护应用
程序不受工作程序节点故障的影响。这样需要部署足够多的Pod,并分配在足够多的Worker节点上。
意外的异常、部署配置不正确、压力过大等情况可能会导致一个Pod中的容器处于不可用的状态。为了
能让应用不受到某些Pod的异常带来的影响,需要部署足够多的Pod,并分配在足够多的Worker节点上。
N.3.1.2 Worker节点故障
Worker节点通常都是运行在物理机上的虚拟机,其可用性可能会受到其宿主机的硬件故障,网络故障
等故障的影响。为了避免单个Worker节点故障带来的不可用,应该要创建更多的Worker节点。
不过尽管拥有了多个Worker节点,但是这些节点可能都是同一个物理机中的虚拟机。如果这一台物理
机出现了故障的话,带来的结果可能依然是全军覆没。为了避免这种情况的出现,应该与基础设施方面沟
通,让 worker 节点均匀分布在更多的物理机、机柜中。
N.3.1.3 集群故障
Kubernetes 主节点是用于保持集群正常启动并运行的主组件。Kubernetes API 服务器是从Worker节点
到主节点的所有集群管理请求的主入口点,或者想要与集群资源交互时的主入口点。如果主节点发生故障,
那么工作负载将继续在工作程序节点上运行,但是无法使用 kubectl 命令来使用集群资源或查看集群运行状
况,直至主节点中的 Kubernetes API 服务器恢复运行为止。如果在主节点停运期间某个 pod 停止运行,那
么在Worker节点可再次访问 Kubernetes API 服务器之前,将无法重新调度该 pod。
值得注意的是,在集群中的Control Panel或者etcd发生故障的时候,各个Worker节点中的Pod可能都
还是正常工作的。但是这个时候如果重新启动的worker节点,那么会导致这一节点上的Pod全部不可
用。因为Controller Manager已经无法工作了。
N.3.1.4 数据中心故障
机房故障会导致区域内所有的物理机和 NFS 存储不可用。其成因可能包括电源、网络、散热或者洪水
地震等非人为因素。为了避免单个机房故障所带来的不可用,我们可以跨多个机房来部署集群,并通过外
部负载均衡器对集群进行负载均衡,比如我们可以考虑采用两地三中心的方式来部署。
在多数据中心集群中,Worker节点池中的Worker节点可在一个区域内跨多个数据中心进行复制。多数
据中心集群旨在跨Worker节点和数据中心均匀安排pod,以确保可用性和故障恢复。如果Worker节点未跨
数据中心均匀分布,或者其中一个数据中心中的容量不足,那么 Kubernetes 调度程序控制器可能无法安排
所有请求的 pod。结果,pod 可能会进入暂挂状态,直到有足够的容量可用为止。如果要更改缺省行为,以
使Kubernetes调度程序控制器在多个数据中心中以最佳分布方式分布 pod,请使用 preferredDuringSched-
ulingIgnoredDuringExecution pod 亲缘关系策略。
我们也可以在一个区域的不同数据中心中创建多个集群,并通过全局负载均衡器将集群连接在一起。
要跨多个集群均衡工作负载,必须设置全局负载均衡器,并将应用程序负载均衡器 (ALB) 或 LoadBal-
ancer 服务的公共 IP 地址添加到域。通过添加这些 IP 地址,可以在集群之间路由入局流量。要使全局负载
均衡器检测其中一个集群是否不可用,请考虑向每个 IP 地址添加基于 ping 操作的运行状况检查。设置此检
查后,DNS 提供程序会定期对添加到域的 IP 地址执行 ping 操作。如果一个 IP 地址变为不可用,那么不会
再将流量发送到此 IP 地址。但是,Kubernetes不会在可用集群中工作程序节点上自动重新启动不可用集群
中的 pod。
有了多个集群就会存在多个集群打通的问题,直接的方法是通过手工或者工具将镜像拷贝实现交付物
流转,或者是引入CICD实现交付物在不同集群中的流转和发布。
N.3.1.5 存储故障
数据安全永远是重中之重的重中之重,其高可用性应该是各个部分中最优先的。在有状态应用程序中,
数据在保持应用程序正常启动并运行方面也起着重要作用,存储的可用主要受到散热(ssd)、断电(长时
间或者突然断电可能导致 ssd 数据丢失)、网络、震动(hdd)、磁盘寿命等因素的影响。我们可以通过使
用后端高可用架构的存储设备或者原生容器存储以pv/pvc的方式来获取持久性存储。
N.3.2 Kubernetes如何保障应用高可用
接下来我们从 Kubernetes 中提供的功能下手,了解如何通过 Kubernetes 集群创建一个在集群中高可
用的应用。
N.3.2.1 ReplicaSet
ReplicaSet(副本集)是Kubernets中的一个非常重要的概念。ReplicaSet是 Deployment 对象的子集,
其功能主要是维持某一个Pod的副本数量,通过这个功能我们可以实现 Pod 的水平扩展/ 收缩与滚动更新。
对于无状态的组件而已,ReplicaSet 是一种非常合适的载体:我们只需要Service 为多个 Pod 提供负载均衡
就可以轻松实现高可用。
但在通常情况下我们并不会直接的使用这个对象,而是通过使用 Deployment 对象的方式来实现Repli-
caSet 的效果。其中 Pod,ReplicaSet 和 Deployment 之间的关系如下图所示:
N.3.2.2 弹性伸缩(Horizontal Pod Autoscaler)
大部分工作负载的资源使用率都会随着业务需求的增长而不断增加。当 cpu 资源不足的时候,工作负
载中 Pod 的响应时间会不断变长直至超时;而当内存不足的时候 Pod 可能会出现一些异常, 甚至被系统杀
死。为了应对业务扩张的情况,我们可以为这个工作负载设置弹性伸缩。当这个工作负载的 Deployment、
ReplicaSet 或者 StatefulSet(状态集)的某项指标达到我们所设定的极限时,对应的 Controller 就会增加
其中 Pod 数量,反过来,指标低于我们设置的极限时,Pod 的数量就会减少。
通常情况下,我们只能通过 Pod 的 cpu 使用率为其设置弹性伸缩。但是我们也可以自行为弹性伸缩配
置更多的指标:比如内存的使用,磁盘 io 的使用。如果某些业务之间的流量压力存在相关性,也可以设置
为 A 业务的工作负载监听 B 业务工作负载的压力以自动扩展。
如果在一定时间内业务需求上下波动较多,可能会导致过多的扩展与收缩,这种行为被称为抖动
(thrashing)。为了消除抖动,我们可以为弹性伸缩设置冷却时间,和各类伸缩策略来实现更加稳定、高效的
伸缩方式,为工作负载带来更好的并发能力。
N.3.2.3 滚动更新
我们希望应用总是可用,又希望应用能始终维护到最新的版本。对于这种情况,我们就需要滚动更新
功能。滚动更新允许通过用新的 Pod 实例增量更新 Pod 实例。如果我们是通过 Service 的方式访问的该
Deployment,那么通过滚动更新就能做到无感知的更新服务。
滚动更新也有阻止故障扩散的作用。我们可以通过修改 rollingUpdate 的 maxSurge 指定一次滚动更新
中最多创建多少个新 Pod,修改 maxUnavailable 控制更新时最多可以删除多少个旧 Pod。这两个值都可以
设置为百分比,其具体数据需要依靠实际情况去做判断。假设我们有一个Deployment 的下一个版本出现了
导致不可用的异常,或者写了一个不存在镜像 tag。那么在更新时,旧的可用 Pod 仍然会保留一定的数量
(只要 maxUnavailable 不是写的 100%或者大于Deployment 的 replicas 设置),同时滚动更新将会停止,
服务始终保持可用状态。
这就要求我们做好关于这个 ReplicaSet 的健康检查,不能简单的依靠其 Running 状态来判断其是否可
用。比如像是一个启动时间比较长的服务,可能容器已经时 running 了,但是由于其服务还未启动,所以无
法正常使用这个服务。
N.3.2.4 健康检查
在很多时候,服务可能不可用了,但是进程仍然存在与机器中。或者服务可用,但是响应时间超出我
们的限制。又或者接口返回异常之类的其他我们认为服务以及不可用的场景。这些情况下我们都需要进行
故障转移。
所以我们需要一种检查服务是否健康的组件,这个组件可能需要预设一个目标操作,响应时间,响应
周期,正常响应内容等。然后这个组件就会周期性的检查服务是否可用,并为故障转移服务提供相应的结
果。在 Kubernetes 中,为健康状态的检查准备以下几种探针:
◎ 存活检测探针(Liveness Probes):存活检测探针用于检查容器中的应用是否处于存活与运行状态。
通过设置,Kubernets 可以定期的执行一些命令或者发送 TCP/HTTP/HTTPS 请求来判断容器的响应
是否符合预期。如果存活检测探针发现了与预期不符的结果,Kubernetes 会通过重启容器来尝试恢复
整个 Pod 的功能。
◎ 就绪检测探针(Readiness Probes):就绪检测探针在设置上与存活检测探针类似。但是这一个应
用是用来判断容器中的应用是否准备好了接收流量。有一些容器的启动流程需要初始化,或者重新启动
进程以应用新的配置,当就绪准备探针判定与预期不符后,Kubernetes 会暂停向这个Pod 发送流量。
这样 Pod 可以继续进行其初始化或重启任务,而不会杀死这个容器。
◎ 启动检测探针(Startup Probes):启动检测探针设置上也与前两者相似。其目的是为了保护启动时
间过于漫长的容器。通过为这个探针设置较长的检查间隔和检查次数可以保护容器在存活检测达到最
大失败尝试次数时不重启容器,而是在经历了启动检测探针尝试次数*启动检测探针检查间隔的时间之
后才会重启应用。通过这一点,我们不用将存活检测探针设置过大的值去等待应用启动,而是为启动
检测探针设置一个值来保护容器的启动过程。
通过灵活的配置探针,我们可以让应用及时的发现问题,并决定是否重启。这样由Kubernetes管理应
用的可用性与健康状态后,可以减少很多额外的运维工作。
N.3.2.5 Prometheus 监控
在 Kubernetes 集群中,我们通过 CMO(Cluster-Monitoring Operator)来一键部署高可用监控,并
且评估告警规则,触发告警后,会将告警推送到 Alert Manager 中。因此,原生的监控系统高可用体现在
Prometheus 的高可用和 Alert Manager 的高可用。
Prometheus高可用
◎ 服务高可用:Prometheus 通过 Pull 的模式去采集目标的监控数据,这样可以很容易的实现
Prometheus 的服务高可用,我们只需要部署多个 Prometheus 实例,配置相同的采集目标即可。
◎ 数据高可用:Prometheus 内置了时序数据库 TSDB,默认会将监控指标数据以自定义格式保存在以
PV 挂载的本地磁盘中。
Alert Manager高可用
一个Alert Manager会存在单点故障,在Kubernetes集群中,我们会启动多个Alert Manager实例来实现
高可用。多个Alert Manager之间会通过Gossip协议,保证在多个Alert Manager收到同一个告警信息的情况
下,也只有一个Alert Manager会将通知发送给Receiver。
在此基础上,我们可以继续进行改造以实现更高的高可用,比如,增加监控采集目标;改造原生监控系
统的缺陷,通过Remote Read/Write接口,提供第三方存储服务支持,确保数据的持久化和可扩展性;引入
多组Prometheus Server来采集不同类别的目标;和现有的监控系统集成,通过高可用的Redis或者Kafka集
群,或者对方调用Prometheus API,以实现监控数据提供给企业监控中心系统。
N.3.2.6 日志系统高可用
Kubernetes可以原生集成容器化的EFK组件,满足基本的日志采集,数据结构化和查询日志等功能。
EFK 是 ElasticSearch(ES)+ Fluentd+Kibana 的简称,ES 负责数据的存储和索引,Fluentd负责数据
的调整、过滤和传输,Kibana负责数据的展示。在 Kubernetes 集群中,我们可以通过 EFK operator 进行
高可用部署。
当然,在实际情况中,我们可以将 ES 集群和 Kibana 部署在 Kubernetes 集群之外,同时引入 Kafka
等高性能消息队列,可以有效避免采集端日志量过大,日志存储无法及时落盘,避免日志的延迟和丢失;
同时解耦采集端和存储端,增加架构的灵活性和扩展性;可以有多个消费者同时处理日志,提升日志处理
效率。
N.3.2.7 Service和Ingress
Kubernetes中Service是对一组提供相同功能的Pod的抽象,为他们提供一个统一的内部访问入口。主
要解决服务注册发现和负载均衡两部分问题。
每创建一个Service会分配一个Service IP,称为Cluster IP。每个service后端会对应一组多个Pod实例,
通过Service的负载均衡功能,可以很好的选择一个合适的后端进行处理,从而实现Service访问的高可用。
Service的负载均衡可以有多个方式实现,目前Kubernetes提供三种模式:userspace,iptables以及
ipvs。
我们就可以在集群内部通过Service轻松的建立起 Pods 之间的连接,但是在集群之外是无法访问这些
Service 的,为了能够从集群外访问集群内的服务,我们需要用 Ingress 将 Service 暴露出来。
不过,虽然 Ingress 对象在 Kubernetes 中是默认存在的,但是只是通过新增一个 Ingress 对象是无法
实现 Ingress 的效果的,我们需要 Ingress Controller,让 Ingress Controller 去监听Ingress 的变化,并实现
Ingress 声明的业务。
常见的 Ingress Controller 有:F5 Networks、HAProxy Ingress、Istio Controller 和 Nginx Controller 等。
在一个集群中可以同时运行一种到多种 Ingress Controller,但是往往我们只需要一个 Nginx Controller 就可
以满足常规的业务需求了。
Ingress Controller可以在更多节点上扩展使得具有更多副本,提供高可用性。
我们可以通过Ingress Operator(名为ingress的clusteroperator)供应了Kubernetes的Ingress Control-
ler对象(缺省名为default的ingresscontroller)并维护其生命周期,如果该default的ingresscontroller被删
掉,Ingress Operator会自动重建新的ingresscontroller实例对象。Ingress Operator可以供应一个或多个基
于HAProxy的Ingress Controller来实现外部入栈请求的流量路由。
在多个 ingress controller 的环境中,我们借助外部硬件或者软件负载均衡器来实现,采用负载均衡的
方式,和前面提到的 API Server 接入的高可用方式一致,在实际的生产环境中,我们建议将南北向的
Ingress LB 和东西向的 API LB 进行分离,以保证高可用和性能的实现。
某些时候,为了提升Ingress Controller的可用性,在一组 Ingress Controller 之间平衡传入的流量负载
时,以及在将流量隔离到特定 Ingress Controller 时,Ingress Controller 分片会很有用处。例如,A 公司的
流量使用一个 Ingress Controller,B 公司的流量则使用另外一个 Ingress Controller。
Kubernetes的网络相关的高可用是个复杂的话题,其通过OVS以及CNI plugin对于网络的增强,我们后
续可以详细介绍。
N.3.2.8 StatefulSet
StatefulSet 控制器是针对有状态应用的,管理 pod 的部署和扩容,并为这些 pod 提供顺序和唯一性保
证。StatefulSet中的各个 Pod 会被顺序地创建出来,每个 Pod 都有一个唯一的 ID,在创建后续 Pod 之前,
首先要等前面的 Pod 运行成功并进入到就绪状态。删除会销毁StatefulSet 中的每个 Pod,并且按照创建顺
序的反序来执行,只有在成功终结后面一个之后,才会继续下一个删除操作。
◎ Pod 具有唯一网络名称:Pod 具有唯一的名称,而且在重启后会保持不变。通过 Headless 服务,
基于主机名,每个 Pod 都有独立的网络地址,这个网域由一个 Headless 服务所控制。这样每个 Pod
会保持稳定的唯一的域名,使得集群就不会将重新创建出的 Pod 作为新成员。
◎ Pod 能有稳定的持久存储:StatefulSet 中的每个 Pod 可以有其自己独立的 PersistentVolumeClaim
对象。即使 Pod 被重新调度到其它节点上以后,原有的持久磁盘也会被挂载到该 Pod。
◎ 有序的,优雅的部署和缩放
◎ 有序的,自动的滚动更新
可以通过 StatefulSet 来进行 Pod 的扩容和缩容,以保证应用的运行可靠性,但 StatefulSet 有自身的
局限性,StatefulSet 无法解决有状态应用的所有问题,它只是一个抽象层,负责给每个 Pod 打上不同的 ID,
并支持每个 Pod 使用自己的 PVC 卷。但有状态应用的维护非常复杂,通过 StatefulSet 实例的操作,也只
能做到创建集群、删除集群、扩缩容等基础操作,但比如备份、恢复等增强有状态应用可靠性的操作,则
无法实现。
如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用由一组无状态的副本
控制器提供的工作负载来部署应用程序,比如 Deployment、DaemonSet 等。
N.3.2.9 Operator
CoreOS 团队提出了 K8S Operator 概念。Operator 是一个自动化的软件管理程序,负责处理部署在
K8S 上的软件的安装和生命周期管理。它包含一个 Controller 和 CRD(Custom Resource Definition),
CRD 扩展了 K8S API。其基本模式如下图所示:
自 CoreOS 被红帽收购以后,红帽在 OpenShift 的 V4 中发布了全新的 OperatorHub,集成了原厂商的
或第三方的或 RedHat 开发的各种 Operator,用来部署和维护相应的服务。可以说,有状态应用上云,
Operator 是最适合的一种方式。
Operator 可以很简单,比如只负责软件安装,也可以很复杂,比如软件更新、完整生命周期管理、监
控告警甚至自动伸缩等等。
N.4 容器云平台高可用设计
在前面几节中,我们学习了如何为 Kubernetes 集群实现高可用,以及在 Kubernetes 中如何创建高可
用应用。现在让我们结合这些知识,为容器云平台设计高可用方案。而这些方案也适用于种种运行在容器
云平台中的应用,对于不同的应用,只要稍加改动就是很好的高可用方案了。
N.4.1 容器云平台高可用(无状态组件高可用)
容器云平台本质上可以视为一个无状态的组件,平台中的数据存储在 Kubernetes 的 Etcd 中。
我们将容器云平台的 Pod 通过 Deployment 的方式部署在 Kubernetes 集群中,并将其 Replicas 设置
为一个不小于 1 的数。在通过一个选择器,将这几个 Pod 暴露在同一个 Service 中。这样一来,当我们的
任意一个 Pod 如果不再健康了,Controller Manager 就会删除这个 Pod,并部署一个新的 Pod。
对于外界而言,由于这些 Pod 是通过 Service 的形式暴露出来的,当有 Pod 没有通过就绪状态检查指
针(Readiness Probes)的时候,就无法在通过 Service 访问到这个 Pod,流量会走向其他的 Pod 中。整
个过程中,用户并不会察觉到引用的不可用,对于他们来说,只需要访问同一个域名,请求就总是会发送
到可用的 Pod 中。其容错能力为 N - 1(N 为 Pod 数量)。
N.4.2 镜像仓高可用(有状态组件高可用)
镜像仓是 Kubernetes 集群重要的组成部分,在这一节中 , 我们以 “Harbor” 和” Quay” 两种镜像仓库
为例 , 详细讲解一下镜像仓高可用。镜像仓的高可用方案与容器云平台不同在于,镜像仓中存在多个有状态
组件,而且还需要一个网络存储。
N.4.2.1 Harbor 镜像仓库
作为一个镜像仓库,持久化镜像文件自然是其中最基本的功能。为了不让镜像文件随着容器的关闭而
被删除且多个仓库的镜像都是一致的,这时候需要挂载外部存储到仓库中,并且这个外部存储需要支持多
机读写。外部存储的选用视环境而定,我们可以选择挂载 NFS、Nas、oss 和种种云存储。选好了外部存储
后,我们只用把同一个外部存储通过 Storage Class 或者其他配置方式挂载多个镜像仓库的 Pod 上,就可
以实现数据一致性与数据持久化。
需要注意的是,镜像仓库、数据库这些应用可能会产生较大的网络开销,IO 开销,Cpu 开销与内存开
销。为了防止这类应用对其他应用产生影响,更也为了避免其他应用抢占系统资源,我们需要为该组
件中的各个 Pod 设定好配额(limits),如果有必要的话,还需要在相关节点所在的 node 上划上污点
与标签,以避免我们将资源开销大的 Pod 部署在该 node 上。
当业务规模较大或者机房分布较广时,单个镜像仓的设计很可能就不再适用了。视情况而定,可能需
要在每一个机房中都部署一个时刻与主镜像仓同步的镜像仓副本。也可能需要为多地开发团队都部署一个
可以读写的主镜像仓,并通过 push 事件保持一致。在此处我们只讨论单个镜像仓的高可用设计。
Redis 高可用设计
镜像扫描也是企业级镜像仓库必不可少的的功能之一。但由于镜像数量往往非常的多,不论是 jfrog 的
xray 还是 coreos 的 clair 都会将需要扫描的镜像层压入一个队列中。这里我们以 Redis 为例,讲解如何为
非关系型数据库实现高可用。
很多数据库为了实现高可用与数据一致性,都会给出相关同步方案和主从机制。Redis 官方给出的方式
就是哨兵机制:在每一个 redis 进程所在的机器上再部署一个 sentinel 进程,这个进程会不断的检查集群中
其他 redis 的健康状态,并在当前集群中的 Master 不可用时,从剩余的 Slave 中选举新的 Master 节点。
我们都知道,Deployment 中的 Pod 一旦重启,其 Pod 名称和 ip 都会发生改变。而在哨兵机制中,各
个 Pod 之间始终需要互相通信,进而就需要为每一个 Pod 配置服务发现。然而要为每一个 Pod 配置
Service,我们需要用到其 Pod name。所以我们需要有一组稳定的标识符(Label)来帮助我们发现 Redis
Pod。
这时候我们就可以用 StatefulSet 来部署包含有 redis 和 sentinel 的 Pod。这样一来,当我们的 Pod 出
现故障而需要重启的时候,重启后这个 Pod 会保持此前的 Pod name 和 Pod ip。这样我们就可以通过为每
一个 Pod 配置 Service 来为 sentinel 进程提供彼此之间的通信。
在 Redis 集群中,只有 Master 节点可以写入数据,而其他 Slave 只能读取数据。如果有写入请求被发
送到 Slave 上,就会发生异常。为了保证能够通过一个地址始终访问到可用的 Master 节点, 我们可以在
redis 上面套一层高可用的 Haproxy(无状态),并在其中配置健康检查。这样一来我们只需要通过 haproxy
的 4 层负载就可以总是访问到可用的 Redis Master 了。
所有的 Pod,不管是 Haproxy 还是 Redis,在启动时,都需要配置相关集群信息。而这部分信息往往
会因为需要配置的集群规模,命名规则等原因产生变化。但我们不可能每次部署 Redis 集群时都为了这些
配置文件重新打包镜像,所以我们可以通过 Kubernetes 将 configmap 以 volume 的形式挂载在容器中。这
样,容器在读取文件时,就会读到我们在 configmap 中的配置文件了。
N.4.2.2 Quay 镜像仓
Quay 是一个企业级的 registry,存储,构建和部署容器的镜像仓库。它分析您镜像中的安全漏洞,可
帮助您减轻潜在的安全风险问题。此外,它提供地理复制和 BitTorrent 分发,以提高分布式开发站点之间
的性能,并提高灾难恢复的弹性和冗余性。你可以使用在线的 Quay.io,也可以使用 on-premise 的
Enterprise Quay。
Quay 作为企业级镜像仓库,可以提供充足的功能,在高可用相关方面,包括了:
◎ 时间机器:Red Hat Quay 提供了存储库中所有标签的两周可配置历史记录,并能够通过图像回滚将
标签还原到以前的状态。
◎ 地域复制:连续的地理分布可提高性能,确保您的内容始终在最需要的地方可用。
◎ 安全漏洞检测集成:Red Hat Quay 漏洞检测器(例如 Clair)集成在一起,并扫描您的容器镜像识
别已知漏洞。
◎ 持久存储:支持多种存储后端来存储您的容器。
◎ 高可用性:可以运行 Red Hat Quay 的多个实例以实现冗余,多个实例可以同时运行,互相备份以
共同分担负载,提高可用性,防止单点故障。
◎ 企业授权和认证:使用 Red Hat Quay,您可以集成现有的身份基础结构,包括访问协议(LDAP),
开放式授权(OAuth)和 开放式 ID 连接(OIDC)和 Keystone,并使用细粒度的权限系统映射到您的
组织并授予整个团队访问权限以管理特定的存储库。
◎ 指标:内置的 Prometheus 指标导出可在每个实例上启用临时和批处理作业指标,以便于监视和警
报。
◎ 洪流分布:Red Hat Quay 支持使用 BitTorrent 提取容器镜像。其结果是减少了下载和部署时间,并
通过让多台计算机提供二进制数据来提高了稳定性。
N.4.2.3 Quay Operator 设计和实现
我们可以通过 Quay Operator 来 Kubernetes 上轻松部署高可用镜像仓库,拉取 Quay 以及 Clair 的镜
像并自动安装部署,Quay Setup 会自动进行数据库和 Redis 等有状态组件的部署,而不需要手工进行,如
下所示:
短短几分钟以后,我们就可以访问 Quay 镜像仓库,并通过 Clair 对其中的镜像进行安全扫描,并对潜
在风险提出修复建议。
高可用设置:HA 与异地复制
Quay 配置 HA 的前提是:
◎ Postgres 或 MySQL 数 据 库,具 有 自 动 备 份 和 故 障 转 移 功 能。 比 如:应 用 Crunchy Data
PostgreSQL Operator 来在 K8S 集群部署高可用的 PostgreSQL 数据库
◎ 高可用性分布式存储引擎,比如通过 NooBaa operator 支持容器原生存储。
◎ Redis 服务器。
◎ 支持 TCP 直通的负载均衡器。
◎ 至少有三个集群节点。
配置异地复制时,是对应于一个共享的全球化的镜像仓库,将后端的数据进行完全复制,用来加速近
端访问。容器先 push 到本地或者位置最近的 repo。然后 image 将在后台被复制到其他 repo。复制的规则
是可配置的。
Repository Mirroring 功能:增加了 Repository Mirroring,可以将不同的镜像库同步到 Quay,方便的
进行镜像分发。可以作为 Geography Replication(地理复制)的一个有效补充。
N.5 总结
通过这一章,我们初步认识了容器云平台以及其中应用的高可用架构设计。我们可以看到,不论是有
状态的无状态的,还是平台或者应用的高可用架构,本质上其实都是通过增加冗余与故障转移来实现的。
而实际上,不同的应用对于可用性的要求不同,甚至会为了性能牺牲可用性,这些都是在这一章中所无法
顾及的,希望大家能够从业务场景出发,深刻理解需求后,再为应用设计架构。
架构师岗岗位学习教程
评论