在 Kubernetes 中通过容器的命令以及生命周期钩子,可以将 PaaS 启动应用以及检查应用启动状态的流程内置在了 pod 中;另外,通过创建 service 对象,可以将容器和对应的服务发现机制关联起来,从而实现容器、应用、服务生命周期的统一。容器平台不再仅仅生产资源,而是交付可以直接为业务使用的服务。这极大地简化了上云之后故障自愈以及自动弹性扩缩容能力的建设,
真正地发挥了云的弹性能力。
另外,在宿主机发生故障的情况下,PaaS 传统上需要先对应用进行扩容,然后才删除宿主机上的容器。然而在大规模的集群下,我们发现往往会卡在应用扩容这步。应用资源额度可能不够,集群内满足应用调度限制的空闲资源也可能不够,无法扩容就无法对宿主机上的容器进行驱逐,进而也无法对异常的宿主机进行送修,久而久之,整个集群很容易就陷入故障机器一大堆,想修修不了、想腾腾不动的困境之中。
在 Kubernetes 中对于故障机的处理要“简单和粗暴”得多,不再要求对应用先扩容,而是直接把故障机上的容器进行删除,删除后才由负载控制器进行扩容。这种方案乍一听简直胆大妄为,落地 Kubernetes 的时候很多 PaaS 的同学都非常排斥这种方法,认为这会严重影响业务的稳定性。事实上,绝大多数核心的业务应用都维护着一定的冗余容量,以便全局的流量切换或者应对突发的业务流量,临时删除一定量的容器根本不会造成业务的容量不足。
我们所面临的关键问题是如何确定业务的可用容量,当然这是个更难的问题,但对于自愈的场景完全不需要准确的容量评估,只需要一个可以推动自愈运转的悲观估计就可以。在 Kubernetes 中可以通过 PodDisruptionBudget 来定量地描述对应用的可迁移量,例如可以设置对应用进行驱逐的并发数量或者比例。这个值可以参考发布时的每批数量占比来设置。假如应用发布一般分 10 批,那么可以设置 PodDisruptionBudget 中的 maxUnavailable 为 10%(对于比例,如果应用只有 10 个以内的实例,Kubernetes 还是认为可以驱逐 1 个实例)。万一应用真的一个实例都不允许驱逐呢?那么对不起,这样的应用是需要改造之后才能享受上云的收益的。一般应用可以通过改造自身架构,或者通过 operator 来自动化应用的运维操作,从而允许实例的迁移。<
不可变基础设施改造Docker 的出现提供了一种统一的应用交付形式,通过把应用的二进制、配置、依赖统一在构建过程中打到了镜像中,
通过使用新的镜像创建容器,并删除掉旧容器就完成了应用的变更。Docker 在交付应用时和传统基于软件包或者脚本的交付方式有一个重大区别,就是强制了容器的不可变,想要变更容器只能通过新创建容器来完成,而每个新容器都是从应用同一个镜像创建而来,确保了一致性,从而避免了配置漂移,或者雪花服务器的问题。
Kubernetes 进一步强化了不可变基础设施的理念,在默认的滚动升级过程中不但不会变更容器,而且还不会变更pod。每次发布,都是通过创建新的 pod,并删除旧的 pod 来完成,这不仅保证了应用的镜像统一,还保证了数据卷、资源规格以及系统参数配置都是和应用模板的 spec 保持一致。
另外,不少应用都有比较复杂的结构,一个应用实例可能同时包含多个团队独立开发的组件。 比如一个应用可能包括了业务相关的应用程序服务器,也包括了基础设施团队开发的日志采集进程,甚至还包括了第三方的中间件组件。这些进程、组件如果想要独立发布就不能放在一个应用镜像中,为此 Kubernetes 提供了多容器 pod 的能力,可以在一个 pod 中编排多个容器,想要发布单个组件,只需要修改对应容器的镜像即可。
不过,阿里巴巴传统的容器形态是富容器,即应用程序服务器,以及日志采集进程等相关的组件都部署在一个大的系统容器中,这造成了日志采集等组件的资源消耗无法单独限制,也无法方便地进行独立升级。因此,在阿里巴巴这次上云中,开始把系统容器中除业务应用外的其他组件都拆分到独立的 sidecar 容器,我们称之为轻量化容器改造。改造后,一个 pod 内会包括一个运行业务的主容器、一个运行着各种基础设施 agent 的运维容器,以及服务网格等的sidecar容器。轻量化容器之后, 业务的主容器就能以比较低的开销运行业务服务,从而更方便进行 serverless 的相关改造。