最近有幸拜读了 Google 在 2016 年 发表的一篇论文,《Design patterns for container-based distributed systems》,这篇文章中描述了几种在容器编排零碎中应用容器的形式,即如何将不同服务的容器进行有机组合,并使得这些模式成为最佳实际形式,让整个分布式系统更加牢靠和可保护,尽管曾经过来 5 年之久,但文章中的思维依然影响着对容器技术和分布式系统的倒退。
文章中将设计模式分成了三个大类:
- 用于容器治理的单个容器应该如何实现
- 单个节点中,多个容器怎么单干共存
- 多个节点情境下的分布式算法
有趣味的话欢送来我的公众号交换:packy 的技术杂货铺
单容器管理模式
其实这种模式只是阐明繁多的容器应该怎么与上下游进行交互,边界应该如何定义。文章中不止一次将容器比作面向对象思维中的对象。阐明咱们应该依照面向对象的思路来对待这个问题,一个容器应该与一个对象一样有一个边界。对下层要提供接口来裸露利用信息,比方监控的 metrics(QPS,衰弱信息等);对上层应该定义一个原生的生命周期,从而管理系统更容易对容器进行管制,同时易开发一些相干组件。
对下层的对立接口这个比拟好了解,就像 K8s 中曾经应用就绪探针 (readness probe
) 和存活探针 (liveness probe
) 监测服务。对上层的生命周期呢,文章举了一个例子:集群中有多个工作在执行,这些工作都领有不同的优先级,有的优先级高,有的优先级低。当集群资源缓和时,就须要让优先级高的工作先执行,然而优先级低的工作曾经在执行了,零碎该怎么办呢,总不能间接把优先级低的工作 kill -9
吧。这个时候就须要生命周期来规定容器如何进行。也就是说生命周期能够让编排零碎更容易不出错的治理容器,生命周期就是开发者、管理系统以及容器的一纸契约。上述例子中,K8s 中通过 graceful deletion
来敞开和驱赶低优先级的工作,K8s 先向容器发送一个 SIGTERM
信号,示意你要马上进行运行,该保留的货色保留,文件该敞开的敞开,通过肯定工夫后才发送 SIGKILL
信号来终结工作。
单节点,多容器利用模式
对于多容器利用,即多个容器须要独特合作实现对外的服务,因而多个容器是被视为一个整体调度到一个节点上的,在 K8s 中通过 Pod 的机制来应答这种场景,即多个容器运行在同一个 pod 中,有一个主容器提供业务服务,其余容器帮助。这种场景下,文章中又总结出三种设计模式:sidecar(边车模式)、Ambassador(大使 / 代理模式)、Adapter(适配器模式)。
Sidecar 模式
理解过 istio 的童鞋对这个词必定不生疏,然而此处的 sidecar 非 istio 中的 sidecar 概念。这里的 sidecar 只是 扩大并加强 主容器。sidecar 能够与主容器共享磁盘空间,因而把日志收集性能作为 sidecar 容器就是一个很好的抉择(例如阿里开源的 log-pilot)。
把加强性能和主业务性能通过容器拆散,有以下几个劣势:
- 容器作为资源分配的单元,能够更好的预估 / 划分所占用资源,
- 不同容器能够由不同团队独立保护,划分开发责任。
- sidecar 容器容易被复用
- 容器提供了一个故障遏制边界,即便 sidecar 容器呈现问题,也不会影响主容器的业务服务
- 能够独立的保护,包含降级、回滚操作。
Ambassador 模式
这种模式其实是 istio 的根底,劫持代理内部与主容器的交互流量。如图
这样做的益处不言而喻,主容器无需关注外界的环境是简单还是简略,只须要晓得要服务提供方的标识即可,具体如何连贯;服务提供方是集群还是单点;甚至应用什么协定连贯都不须要关注,这些都由 ambassador 容器来实现。istio 中应用 envoy 来代理流量时,提供更简单的操作来帮忙 istio 形成整个管制面,包含路由、熔断、服务注册发现等。
Adapter 模式
适配器模式与 Ambassador 模式正相反,Ambassador 简化了主容器对外部环境的认知,而适配器模式则是简化了利用对外的出现模式。举个例子,当初服务的监控是一个必选项,然而不同的服务利用应用的监控计划则是形形色色,有用 Prometheus 的,有 JMX 的。如果咱们须要构建一个对立的监控平台,面对这么多不同技术栈的 metrics 着实头疼。这个时候就须要一个容器,将主容器的 metrics 依照对立的规范进行翻译,监控平台就只须要对接一种规范即可。
多节点利用模式
除了在单个机器上合作容器之外,模块化容器能够更轻松地构建合作的多节点分布式应用程序。前面的几种设计模式也都是基于 Pod 的概念构建的。
leader 选举
这是一个在分布式系统中很常见的问题,通常 leader 选举的实现是由不同的编程语言来实现。文章中给出了一种模式,Pod 中的主容器运行着须要进行 leader 选举的应用服务,另外由独自的一个容器仅执行 leader 选举算法(临时称为 leader 选举容器),leader 选举容器对主容器裸露一个通用的 HTTP 或其余类型的接口,主容器通过调用 localhost 接口即可获知本人以后的身份了。如图
这样做就能够把简单的实现局部放在独立的容器中,更容易对主容器的进行开发与保护。
工作队列
工作队列所实现的工作相似于任务调度零碎(例如 Python 中的 Celery)。与 leader 选举的模式相似,文章中提出的设计模式,仍是为了将分布式系统中常见的工作队列逻辑从具体的业务逻辑中剥离进去,并使其平台化、语言无关化。容器通过实现 run()
和 mount()
接口来构建通用的工作队列,即开发者只须要针对工作队列中某个具体阶段的逻辑进行开发,最终的代码打包等动作都能够由此框架来解决。
最近 CNCF 孵化的我的项目 argoproj/argo-workflows: Workflow engine for Kubernetes (github.com) 的架构设计也借鉴于此。
Scatter/gather 模式
这种模式能够艰深的了解为云原生的 MapReduce。一台根服务器申请大量的分区服务器去并行的解决数据,每个分区服务器返回局部数据,并由根服务器进行汇总返回。这个流程能够归结为一个样板:1. 扩散申请;2. 收集响应数据;3. 与客户端交互。因而这种模式只须要两种容器的反对:一种容器做分区数据处理;一种容器做数据聚合(如下图所示)。
总结
总的来说,这篇文章中形容了分布式状况下,容器应该怎么实现、繁多利用多容器服务该如何单干共存。相较于 SOA 架构零碎来说,面向容器和 SOA 都重视于通过网络通信的接口实现组件的复用,然而 SOA 拆分的组件粒度更大,同时耦合度比上文中提到的共生容器更加的涣散。另一方面,SOA 更重视与业务,本篇文章形容的面向容器的思维则更重视于构建通用的中间件,从而更容易构建分布式系统。
在近几年落地计划中,其实很多都是由文章中的设计模式倒退而来。比方 istio 应用的 sidecar,正如这里的 Ambassador 模式,通过代理劫持流量实现流量的治理,数据面与管制面的拆散;以前应用过的阿里开源的用于采集容器日志的工具 log-pilot(AliyunContainerService/log-pilot: Collect logs for docker containers (github.com)),则是应用的 sidecar 模式;以及用来定义容器相干的生命周期的 CRI CNI 这种标准接口。将来必定还会有更多的利用情景须要思考分布式的容器服务该怎么落地,这篇文章都会成为一种领导和启发。正如论文中总结写到的
We believe that the set of container patterns will only grow, and that in the coming years they will revolutionize distributed systems programming much as object-oriented programming did in earlier decades, in this case by enabling a standardization and regularization of distributed system development.
咱们置信容器模式集只会增长,并且在将来几年它们将彻底改变分布式系统编程,就像前几十年面向对象编程所做的那样,从而标准化和规范化分布式系统的开发.