本文已过时一年以上。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
分布式系统工具包:复合容器的模式
我有幸在 DockerCon 2015 上展示了 Kubernetes 的一些想法,我想写一篇博客文章来与那些未能到场的读者分享这些想法。
在过去的两年里,容器已成为打包和部署代码的越来越流行的方式。容器镜像解决了现有打包和部署工具的许多实际问题,但除了这些显著的好处之外,容器还为我们提供了一个从根本上重新思考构建分布式应用程序方式的机会。正如面向服务的架构 (SOA) 鼓励将应用程序分解为模块化的、重点突出的服务一样,容器应该鼓励将这些服务进一步分解为紧密合作的模块化容器。通过建立边界,容器使用户能够使用模块化、可重用的组件构建其服务,这反过来又使服务比从单体容器构建的应用程序更可靠、更可扩展且构建速度更快。
在许多方面,从虚拟机切换到容器就像从 1970 年代和 80 年代早期的单体程序切换到 1980 年代后期及以后的模块化面向对象程序一样。容器镜像提供的抽象层与面向对象编程中类的抽象边界有很多共同之处,它为提高开发人员的工作效率和应用程序质量提供了相同的机会。就像正确的编码方式是将关注点分离到模块化对象一样,正确的容器化应用程序方式是将关注点分离到模块化容器中。从根本上讲,这意味着不仅要分解整个应用程序,还要将任何一台服务器中的部分分解为多个易于参数化和重用的模块化容器。这样,就像现代语言中普遍存在的标准库一样,大多数应用程序开发人员可以组合由其他人编写的模块化容器,并以更高的质量组件更快地构建他们的应用程序。
以模块化容器为中心的思考方式的好处是巨大的,特别是,模块化容器提供以下好处
- 加速应用程序开发,因为容器可以在团队之间甚至更大的社区之间重用
- 将专家知识编入代码,因为每个人都在反映最佳实践的单个容器化实现上进行协作,而不是使用大量具有大致相同功能的不同的本土容器
- 支持敏捷团队,因为容器边界是团队职责的自然边界和约定
- 提供关注点分离和专注于特定的功能,从而减少意大利面条式的依赖关系和不可测试的组件
从模块化容器构建应用程序意味着思考协同工作以提供服务的容器的共生组,而不是每个服务一个容器。在 Kubernetes 中,这种模块化容器服务的体现是 Pod。Pod 是一组共享文件系统、内核命名空间和 IP 地址等资源的容器。Pod 是 Kubernetes 集群中调度的原子单位,正是因为 Pod 中容器的共生特性要求它们被共同调度到同一台机器上,而可靠地实现这一点的唯一方法是将容器组作为原子调度单位。
当你开始从 Pod 的角度思考时,自然会出现一些模块化应用程序开发的常见模式,这些模式会多次重复出现。我相信,随着我们不断推进 Kubernetes 的开发,将识别出更多的这些模式,但这里有我们常见的三个模式
示例 #1:Sidecar 容器
Sidecar 容器扩展和增强“主”容器,它们使现有容器变得更好。例如,考虑一个运行 Nginx Web 服务器的容器。添加一个与 git 存储库同步文件系统的不同容器,在容器之间共享文件系统,你就构建了 Git push-to-deploy。但是,你以模块化的方式完成了它,git 同步器可以由不同的团队构建,并且可以在许多不同的 Web 服务器(Apache、Python、Tomcat 等)之间重用。由于这种模块化,你只需要编写和测试你的 git 同步器一次,就可以在众多应用程序中重用它。如果其他人编写了它,你甚至不需要这样做。
示例 #2:大使容器
大使容器将本地连接代理到世界。例如,考虑一个具有读副本和单个写主节点的 Redis 集群。你可以创建一个将主应用程序与 Redis 大使容器分组的 Pod。大使是一个代理,负责拆分读写并将它们发送到相应的服务器。因为这两个容器共享一个网络命名空间,它们共享一个 IP 地址,你的应用程序可以在“localhost”上打开一个连接,而无需任何服务发现即可找到代理。就你的主应用程序而言,它只是连接到在 localhost 上运行的 Redis 服务器。这非常强大,不仅因为它关注点分离以及不同的团队可以轻松拥有这些组件,而且还因为在开发环境中,你可以简单地跳过代理并直接连接到在 localhost 上运行的 Redis 服务器。
示例 #3:适配器容器
适配器容器标准化和规范化输出。考虑监视 N 个不同应用程序的任务。每个应用程序可能使用不同的方式导出监视数据。(例如,JMX、StatsD、应用程序特定统计信息),但每个监视系统都期望监视数据具有一致且统一的数据模型。通过使用复合容器的适配器模式,你可以通过创建将应用程序容器与知道如何进行转换的适配器分组的 Pod,将来自不同系统的异构监视数据转换为单一的统一表示形式。同样,由于这些 Pod 共享命名空间和文件系统,因此这两个容器的协调非常简单直接。
在所有这些情况下,我们都使用容器边界作为封装/抽象边界,使我们能够构建模块化、可重用的组件,并将它们组合起来构建应用程序。这种重用使我们能够更有效地在不同的开发人员之间共享容器,在多个应用程序中重用我们的代码,并通常更快地构建更可靠、更健壮的分布式系统。我希望你已经看到 Pod 和复合容器模式如何使你能够更快地构建健壮的分布式系统,并实现容器代码重用。为了在你自己的应用程序中尝试这些模式,我鼓励你查看开源 Kubernetes 或 Google Container Engine。