容器生来适合的是以单进程为主的独立的微服务架构,而很多传统的组件则是体积庞大,多个进程(组件)之间难以拆分到不同的容器中,所以在单个容器内部署多个组件便成了一种 暂时的折衷方案。这便引入了一个问题:如何在容器内管理多个进程?

总的来说,至少有三种方案可选:

  1. 通过romote api 进行远程attach。通过http的方式来远程执行命令
  2. 使用一个init程序作为容器的主进程,所有组件都作为此程序的子进程
  3. 不虚拟PIDnamespace

这几种方式各有优劣,下面将分别进行详述。

Remote Attach

Docker Remote API提供了两种进行Remote Attach的方式,一是通过POST的方式,另 一种是通过Websocket协议。Websocket是构建于HTTP之上的一种协议,比较适合于双向通信。可惜的是,二者的文档都很少,很难完整地实现一个可靠的客户端,不过相对来说,Websocket要比POST方便很多,这里有一个简单的客户端实现 docker-ws-client,可以远程发送命令并执行。

这个程序只能用来进行简单的演示,在生产环境几乎是不可用的。主要面临的问题如下:

  1. 要想命令被执行,容器内必须有一个Bash程序存在,生产环境用的容器很少会需要在 里面放一个Bash程序
  2. 对于一些命令输出为交互式的来说(比如top),在执行了这些程序之后,会干扰到后续 命令的执行,一般只能通过重启容器解决。
  3. 远程发送命令的执行结果会和其他容器内程序的输出相混淆。这就意味着docker logs的输出里会混杂每次远程命令执行的结果,远程命令的结果里也会混杂着其他的程 序的输出,导致无法获取精确的信息
  4. 返回结果里附带了一行命令提示符,这是Bash的正常表现,但在远程执行时经常不需 要这个功能。

Docker自身在实现Websocket Server时选用的是一个比较简陋的库 : code.google.com/p/go.net/websocket/, 所提供的API很少,无法对双向通信提供精确的控制。而且按照Docker自身的规划来说,这个功能并不是需要重点支持的,因为Docker适用的是微服务(单进程),常用的远程API都 是针对容器级别的,对容器内部的操纵只能算是一些“小众”需求,社区不太会花太多时间在这块的改进上,所以不建议花时间在这方面构建什么东西。

多进程管理程序

这也是Docker官方比较推荐的一种方式,有两篇官方博文介绍:Process Management with CFEngineUsing Supervisor with Docker。 思路其实很明确 : 将多进程转为单进程(管理程序,容器的init程序),这个init程序的 生命周期和容器是一样的,由它来管理其他进程。就符合了单进程的模型,虽然可能不是那么轻量级。

这也是最适合生产环境中的一种方式。当然长期来说,能将各种传统服务转为微服务架构最 好,但在这个过渡期,或者对一些转换代价比较大的服务来说,这是最为稳定和可控的一种 方式。其主要优点如下:

  1. 不用再写脚本。脚本是容器内启动多个进程最为简单也是问题最多的一种方式。信号传 递、进程监控、容易引起僵尸进程、后台程序的处理……引发的问题非常多。
  2. 管理进程的通用性。Supervisor这类开源组件已经说明管理进程完全可以做的非常通 用,通过配置文件的定制,这个程序完全可以跑在所有需要多进程的容器内。
  3. 复用性。如果要自己开发这个程序,完全可以用到很多已有的经验,毕竟容器内的环境 非常类似于物理机上。已有平台的监控方式,在容器里几乎是一样的,这能大大减少开 发难度。

总之,对于简单的使用案例,可以尝试Supervisor这类开源组件。而对于有能力的公司,则推荐在已有经验上开发一个新的管理程序。

Host PID Namespace

这种情况不再对容器内的PID进行虚拟,即docker top 和在容器内执行top命令看到的 都是进程的实际PID。这样做的好处是原有的监控方案可以直接拿过来用,部门功能如状态 查看、杀死进程等能正常工作,不好的地方就是如果需要拉起的话则是在容器外做不到的,因为拉起一般都是 通过可执行文件的路径来做的,而容器的文件系统是虚拟的,所以基本上无法实现。

这种方式也是不推荐的。我们使用容器,最终也是为了虚拟化,所以在不影响性能的情况下, 应该进行将各部分都虚拟出来,这样最不容易出问题。如果虚拟网络性能不好,我们可以先 不用虚拟网络,但是其他的部分如果不是因为性能原因,还是尽量通过其他可能的途径去解 决问题,而不是破坏虚拟化环境的一致性。