本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

分布式系统工具包:复合容器的模式

我有幸在 DockerCon 2015 上介绍了 Kubernetes 的一些想法,我想写一篇博客文章来分享这些想法,以飨那些未能到场的朋友。

在过去的两年里,容器已成为打包和部署代码越来越流行的方式。容器镜像解决了现有打包和部署工具的许多实际问题,但除了这些显著的优势之外,容器还为我们提供了一个从根本上重新思考分布式应用程序构建方式的机会。正如面向服务架构 (SOA) 鼓励将应用程序分解为模块化、专注的服务一样,容器应该鼓励将这些服务进一步分解为紧密协作的模块化容器。通过建立边界,容器使用户能够使用模块化、可重用的组件构建其服务,这反过来使得服务比由单体容器构建的应用程序更可靠、更具可伸缩性且构建速度更快。

在许多方面,从虚拟机到容器的转变就像从 20 世纪 70 年代和 80 年代初期的单体程序到 20 世纪 80 年代后期及以后的模块化面向对象程序的转变。容器镜像提供的抽象层与面向对象编程中类的抽象边界有很大的共同之处,它提供了相同的机会来提高开发人员的生产力和应用程序的质量。正如正确的编码方式是将关注点分离到模块化对象中一样,在容器中打包应用程序的正确方式是将关注点分离到模块化容器中。从根本上说,这意味着不仅要分解整个应用程序,还要将任何一台服务器中的各个部分分解为多个易于参数化和重用的模块化容器。通过这种方式,就像现代语言中无处不在的标准库一样,大多数应用程序开发人员可以将其他人编写的模块化容器组合在一起,并以更高质量的组件更快地构建他们的应用程序。

模块化容器思维的好处是巨大的,特别是,模块化容器提供了以下优势:

  • 加速应用程序开发,因为容器可以在团队之间甚至更广泛的社区中重用
  • 将专家知识编纂成文,因为每个人都在一个反映最佳实践的单一容器化实现上协作,而不是无数具有大致相同功能的自制容器
  • 赋能敏捷团队,因为容器边界是团队职责的天然边界和契约
  • 提供关注点分离,并专注于特定功能,从而减少意大利面式依赖和不可测试的组件

从模块化容器构建应用程序意味着要考虑协同工作的容器组来提供服务,而不是每个服务一个容器。在 Kubernetes 中,这种模块化容器服务的体现是 Pod。Pod 是一组共享文件系统、内核命名空间和 IP 地址等资源的容器。Pod 是 Kubernetes 集群中调度的原子单元,正是因为 Pod 中容器的共生性质要求它们必须共同调度到同一台机器上,而可靠实现这一点的唯一方法是使容器组成为原子调度单元。

当您开始以 Pod 为单位思考时,自然会出现一些通用的模块化应用程序开发模式。我相信随着 Kubernetes 的发展,更多这样的模式将被识别出来,但这里有三个我们常见的模式:

示例 #1:Sidecar 容器

Sidecar 容器扩展并增强“主”容器,它们获取现有容器并使其变得更好。例如,考虑一个运行 Nginx Web 服务器的容器。添加一个与 Git 仓库同步文件系统的不同容器,在容器之间共享文件系统,你就构建了 Git 推送到部署。但是你以模块化的方式完成了它,其中 Git 同步器可以由不同的团队构建,并且可以在许多不同的 Web 服务器(Apache、Python、Tomcat 等)中重用。由于这种模块化,你只需编写和测试你的 Git 同步器一次,并在众多应用程序中重用它。如果其他人编写它,你甚至不需要这样做。

Sidecar Containers

示例 #2:大使容器

大使容器将本地连接代理到外部世界。例如,考虑一个带有读副本和单个写主节点的 Redis 集群。您可以创建一个 Pod,将您的主应用程序与一个 Redis 大使容器组合在一起。大使是一个代理,负责拆分读写请求并将它们发送到适当的服务器。由于这两个容器共享一个网络命名空间,它们共享一个 IP 地址,并且您的应用程序可以在“localhost”上打开连接并找到代理,而无需任何服务发现。就您的主应用程序而言,它只是连接到 localhost 上运行的 Redis 服务器。这非常强大,不仅因为关注点分离和不同团队可以轻松拥有组件的事实,还因为在开发环境中,您可以简单地跳过代理并直接连接到 localhost 上运行的 Redis 服务器。

Ambassador Containers

示例 #3:适配器容器

适配器容器标准化和规范化输出。考虑监控 N 个不同应用程序的任务。每个应用程序可能以不同的方式导出监控数据。(例如 JMX、StatsD、应用程序特定统计信息),但每个监控系统都期望其收集的监控数据具有一致且统一的数据模型。通过使用复合容器的适配器模式,您可以将来自不同系统的异构监控数据转换为统一的表示形式,方法是创建将应用程序容器与知道如何进行转换的适配器分组的 Pod。同样,由于这些 Pod 共享命名空间和文件系统,这两个容器的协调简单明了。

Adapter Containers

在所有这些情况下,我们都将容器边界用作封装/抽象边界,这使我们能够构建模块化、可重用的组件,然后将它们组合起来构建应用程序。这种重用使我们能够更有效地在不同开发人员之间共享容器,在多个应用程序中重用我们的代码,并通常更快地构建更可靠、更健壮的分布式系统。我希望您已经了解 Pod 和复合容器模式如何使您能够更快地构建健壮的分布式系统,并实现容器代码重用。要在您自己的应用程序中尝试这些模式。我鼓励您去查看开源的 Kubernetes 或 Google Container Engine。