3.4 面向应用的高可用特性
需要澄清的一点:服务百分之百可用是不可能的。要想提高服务的高可用水平,在Kubernetes 上运行容器是当前的最佳方法。基于谷歌多年运行成千上万个具有数千个应用程序的容器的经验、Kubernetes 的一些技术或特性,能够轻松帮助应用提高弹性。例如,kubelet 支持多种重启策略,配合Liveness Probe 可以保证应用出现故障时异常退出或不响应时能够自动重启;ReplicaSet 和Deployment 控制器能够确保节点出现故障导致运行的Pod 被驱逐时,Pod 能被重建;调度器可以保证新建Pod 能调到其他就绪节点,确保故障及时转移,等等。下面从Pod、Node 和Cluster 三个层级来考量Kubernetes 面向应用提供了哪些高可用性和可靠性的技术保证。
1.Pod 层面的高可用保证
在现代应用开发部署中,Kubernetes 无疑使事情变得更容易,因为实现了数据和配置与容器分离。 将配置保留在 ConfigMap 和 Secret 资源对象中, 数据则可存于PersistentVolumes 上。因此,每当容器由于某种原因而崩溃时,Kubernetes 都会从不变的容器镜像中重新启动一个新的容器或者重新创建一个新的Pod,并将所有数据都传递给新的容器,而不管容器在哪个节点上启动。也就是说,任何一个Pod 在故障发生时都能够被重启或替换。
当容器发生故障时,默认情况下容器将被自动重新启动,以解决间歇性故障问题。例如应用程序正在运行,但是无法响应用户的请求,在这种状态下,重新启动容器可以帮助存在错误的应用程序恢复可用。这得益于 Kubernetes 的 Liveness Probe,通过 Exec、TCPSocket 或HTTPGet 定期检查容器的存活状态,Kubernetes 根据检查结果进行主动重启。
与Liveness Probe 类似,Readiness Probe 亦是通过上面三种方法之一定期执行健康检查,判定容器是否准备就绪可以接收流量。Pod 的所有容器都准备就绪时,即视为Pod 准备就绪。当Pod 准备就绪后,此Pod 才会被添加到Service 的负载均衡列表中;当Pod 尚未就绪时,会将其从Service 的负载平衡列表中删除。
因此,在Pod 级别上,只需要设置合理的Probe 参数,即可准确控制容器的宕机时间,最长的宕机时间为:检测周期×失败次数阈值+容器启动就绪时间。对启动缓慢的容器进行Probe 时,应合理设置初始探针检查的延时(通过参数 “initialDelaySeconds” 设置),避免它们在启动和就绪之前被kubelet 杀死。
2.Node 层面的高可用保证
Kubernetes 的多节点架构和可伸缩性功能确保了应用在Node 级别的高可用性。一个应用程序作为一个实例运行在一个Pod 中。当运行该Pod 的主机节点崩溃时,Kubernetes将会在满足其要求(CPU/内存资源、Toleration 和Affinity 等条件)的其他可用的健康节点上创建新的容器。这样对于那些单实例应用程序,也可以从节点崩溃中恢复。当然,单实例应用程序是一种不推荐的使用方式,应尽量将工作负载分散于多个节点的多个实例中。如果你的应用程序只需要单个实例,也切勿直接从 Pod 启动应用程序,建议使用ReplicaSet 或Deployment 对象启动应用程序。Kubernetes 将会在整个群集中为它们管理并维护指定数量的Pod 实例(即使只有一个)。
为了减少恢复所需的时间,还应该减小容器镜像的大小,以便在冷启动时(即节点首次从该镜像启动容器)最大程度地减少下载镜像所需的时间。在大型的生产环境中,网络带宽是一种宝贵的资源。在这些节点上,有数百个不同的容器正在运行。容器镜像越大,产生的宕机时间就越长。
对于多实例的服务,Kubernetes 调度器根据相应的调度策略(SelectorSpreadPriority、ServiceSpreadingPriority 等)能够尽量将相同Service、StatefulSet 和ReplicaSet 的Pod 都运行在不同的节点上。这使得服务能够更好地容忍主机节点的单点故障。同时,可以使用诸如Affinity 和Anti-Affinity 的功能,根据具体的集群拓扑自定义服务的Pod 分布,尤其是在非公有云环境中运行Kubernetes 时。我们可以根据服务器类型、机架、服务器机房和数据中心定义自己的故障域,利用Kubernetes 调度器将Pod 分布在不同的可用的故障域中。这样可以减轻一个节点、多个节点甚至整个数据中心的崩溃所带来的影响。
Kubernetes 对Deployment 和DaemonSet 等资源对象都支持通过RollingUpdate 来完成Pod 实例的更新。RollingUpdate,也可以称为渐变,它通过缓慢增加新的Pod 实例来替换老的Pod 实例,从而使部署的更新可以在零停机时间内进行。表3-2 总结了Kubernetes 可支持的升级策略及其优劣势。对于蓝绿部署、金丝雀、A/B 测试和影子升级策略,在Kubernetes 中并不是开箱即用的,需要通过额外的组件来构建更高级的基础架构(例如Istio、Traefix 和自定义的Nginx 等)。
表3-2 升级策略对比
在生产环境中,渐变和蓝绿部署是一个很好的选择,但是前提是新版本经过了合理正确的测试。蓝绿和影子策略需要双倍的资源。在新版本信息不足或者带来的影响未知的情况下,金丝雀、A/B 测试或者影子策略是个不错的选择。如果你的新版本需要在特定的客户群中测试,那么只能选择A/B 测试的部署策略了。
3.Cluster 层面的高可用保证
在Cluster 级别,可以通过在多个集群上部署应用程序来实现。利用Kubernetes 的Federation 机制,建立集群联邦,通过集群联邦部署跨多云的服务。对于多云的混合解决方案,不仅可以获得更高级别的可用性,而且还可以获得更大的自由度和独立性,但同时也具有挑战性。特别是对于有状态的应用程序,数据就是最大的挑战,需要找到多个集群中实例的同步数据的解决方案。相应地,需要设计自己的持续集成和持续部署流程,自定义部署策略,无缝地将新的版本发布到所有集群中。
针对自定义的部署逻辑,可以充分利用 Kubernetes 的 API,构建管理和服务于Kubernetes 之上的应用程序的管理员,即Operator。通过Operator 使应用程序更智能地适应各种云平台,更高效地处理应用程序实例的状态变化。Operator Framework 是一个CoreOs的开源项目,打包了一组部署和管理Kubernetes 上应用程序的方法,例如扩展、升级、备份数据等,将应用程序的业务逻辑与Kubernetes API 结合起来执行这些操作。其提供的SDK中包含Operator 所需要的主要实践和代码模式,能够加速Operator 的编写,以防止重复造轮子。CoreOs 已经开源了两个例子:etcd Operator 和Prometheus Operator。可以通过学习这两个例子,加深对Operator 的实现方式的理解。