你的应用什么时候该拆分到多个容器中?

Docker的最佳实践提出了“一个容器内只运行一个进程”之后,是否需要将应用拆分到多个容器中就成了一个热门话题。本文以一个普通的Java应用作为讨论,从软件设计的角度来介绍为什么要将其拆分到多个容器中。

假设一个标准的Java Web应用包含以下两部分:

  1. 基于Struts框架的前端应用;
  2. 基于Java EE的后端REST API服务;

这两部分通常运行在同一个容器(如Tomcat)中,相互之间基于REST接口进行交互。类似这种应用,我们应该将其拆分到不同的容器中运行吗?

简单的回答:我们应该将其拆分到不同的容器中,但这需要经过谨慎的考虑。

抛开一些“普适”的原则(如“一个容器只运行一个进程”),我们主要从应用本身和部署策略上进行考虑。

首先,我们抛开具体业务场景,做一些设计思考(Design Thinking):

  1. JVM本身支持多线程,因此通常的Java应用程序都运行在一个进程中,以多线程的方式进行并行处理。类似Tomcat这样的容器本身也支持将多个Java应用程序运行在一个Java进程中。
  2. 事实上,很多已经容器化的应用,在容器中实际运行时也是多进程的,例如Apache的prefork模块。同时,当前流行的网络应用(如Nginx)大量使用事件驱动和反应器模式,这些设计的方向,都是将IO操作交给内核,将业务逻辑处理交给子进程(线程)。Linux内核非常善于调度子进程,但是这不是诸如Kubernetes、Swarm等容器调度工具擅长的地方。进程(线程)侧重于内核资源的划分,而容器侧重于集群资源的划分。
  3. “一个容器只运行一个进程”的原则,更像一种哲学。作为工程师,我们应该从实际出发考虑技术架构和应用逻辑。同时这个“最佳实践”甚至没有获得广泛的赞同,它的传播可能是因为对Unix运作原理缺乏了解导致的。
  4. Linux容器(LXC)历史上有很多种形式,其中很多是建议在一个容器中运行多个进程的。Linux容器实质上是clone系统调用、SELinux、cgroup等技术的组合,上层使用LXC或者Docker(libcontainer)对于这些底层技术是不相关的,因为最终都依赖于内核的隔离技术。
  5. 进程间交互有多种形式,如套接字、文件、网络等,每种方式都有自己的优缺点。如果考虑将应用拆分到不同容器中,对应用程序内部组件之间的通信方式会有很大影响。
  6. 代码、配置和数据的独立性,也对是否能够拆分到不同容器的重要考量。如果应用程序的代码、配置和数据相互独立性比较强,就能够方便的将其拆分成独立的组件,放置到独立的容器中;反之,如果应用内部分层不清,拆分成本会非常高。值得注意的是,我们没有必要为了容器化而对应用做过多的改造,只要能够按照Docker镜像的格式制作成镜像,我们仍然能够享受到Docker带来的便利:方便的分发(利用Docker注册中心)和运行(使用docker run命令)。

上面总结了一些从系统层面看是否需要拆分到多个容器,回到我们的例子:

  1. 这两个Java组件功能上相互独立。一个是web前端,一个是API服务。由于它们是完全不同的服务,是否运行在一个JVM(Java进程)中对性能损耗非常小(当然,这只是相对的,因为它们将无法公用堆内存和垃圾回收)。
  2. 这两个组件之间使用REST API进行交互,没有使用传统的进程间交互方式(套接字、共享内存等)。
  3. web前端和API服务的使用场景不同,针对不同的场景,在运维层面可能需要独立的进行扩容。例如除了web前端之外,API服务还能提供给其他组件使用,因此API服务可能会因为接入方的变化需要独立扩容和缩容。这时候就是充分利用容器编排框架(如Kubernetes、Mesos、Swarm等)能力的时候了。

基于上述三点考虑,我会将这个Java应用的两个组件拆分到独立的容器中。同时建议配合使用诸如Kubernetes的容器编排框架,将两个服务关联到一起。

总结一下我们的“最佳实践”:

如果你的应用代码、配置和数据相对独立,并且有清晰的交互形式,将应用组件拆分到多个容器中是有意义的。

感谢郭蕾对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至[email protected]。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群InfoQ好读者(已满),InfoQ读者交流群(#2)InfoQ好读者)。

你可能感兴趣的:(你的应用什么时候该拆分到多个容器中?)