docker容器内多进程的管理方案
主题 Docker 多进程
容器生来适合的是以单进程为主的独立的微服务架构,而很多传统的组件则是体积庞大,多 个进程(组件)之间难以拆分到不同的容器中,所以在单个容器内部署多个组件便成了一种 暂时的折衷方案。这便引入了一个问题:如何在容器内管理多个进程?
综述
总的来说,至少有三种方式可选:
- 通过
romote api
进行远程attach
。通过http
的方式来远程执行命令 - 使用一个
init
程序作为容器的主进程,所有组件都作为此程序的子进程 - 不虚拟
PID
的namespace
这几种方式各有优劣,下面将分别进行详述。
Remote Attach
Docker Remote API
提供了两种进行 Remote Attach
的方式,一是通过 POST
的方式,另 一种是通过 Websocket
协议。 Websocket
是构建于 HTTP
之上的一种协议,比较适合于双 向通信。可惜的是,二者的文档都很少,很难完整地实现一个简单,可靠的客户端,不过总 的来说, websocket 要比 POST 方便很多,这里有一个简单的客户端实现 docker-ws-client ,可以远程发送命令 并执行。
这个程序只能用来进行简单的演示,在生产环境几乎是不可用的。主要问题如下:
- 要想命令被执行,容器内必须有一个 bash 程序存在,生产环境用的容器很少会需要在 里面放一个 bash 程序
- 对于一些命令输出为交互式的来说(比如 top ),在执行了这些程序之后,会干扰到后续 命令的执行,一般只能通过重启容器解决。
- 远程发送命令的执行结果会和其他容器内程序的输出相混淆。这就意味着 docker logs 的输出里会混杂每次远程命令执行的结果,远程命令的结果里也会混杂着其他的程 序的输出,导致无法获取精确的信息
- 返回结果里附带了一行命令提示符,这是 bash 的正常表现,但在远程执行时经常不需 要这个功能。
Docker 自身在实现 Websocket Server 时选用的是一个比较简陋的库 : code.google.com/p/go.net/websocket/ , 所提供的API很少,无法对双向通信提供精确的控制。而且按照 Docker
自身的规划来说,这 个功能并不是需要重点支持的,因为Docker适用的是微服务(单进程),常用的远程API都 是针对容器级别的,对容器内部的操纵只能算是一些“小众”需求,社会不太会花太多时间 在这块的改进上,所以不建议花时间在这方面构建什么东西。
多进程管理程序
这也是Docker官方比较推荐的一种方式,有两篇官方博文介绍: Process Management with CFEngine 和 Using Supervisor with Docker 。 思路其实很明确 : 将多进程转为单进程(管理程序,容器的 init 程序),这个 init 程序的 生命周期和容器是一样的,就符合了单进程的模型,虽然可能不是那么轻量级。
这也是最适合生产环境中的一种方式。当然长期来说,能将各种传统服务转为微服务架构最 好,但在这个过渡期,或者对一些转换代价比较大的服务来说,这是最为稳定和可控的一种 方式。其主要优点如下:
- 不用再写脚本。脚本是容器内启动多个进程最为简单也是问题最多的一种方式。信号传 递、进程监控、容易引起僵尸进程、后台程序的处理……引发的问题非常多。
- 管理进程的通用性。
Supervisor
这类开源组件已经说明管理进程完全可以做的非常通 用,通过配置文件的定制,这个程序完全可以跑在所有需要多进程的容器内。 - 复用性。如果要自己开发这个程序,完全可以用到很多已有的经验,毕竟容器内的环境 非常类似于物理机上。已有平台的监控方式,在容器里几乎是一样的,这能大大减少开 发难度。
总之,对于简单的使用案例,可以尝试 Supervisor
这类开源组件。而对于有能力的公司, 则推荐在已有经验上开发一个新的管理程序。
Host PID Namespace
这种情况不再对容器内的PID进行虚拟,即 docker top
和在容器内执行 top
命令看到的 都是进程的实际PID。这样做的好处是原有的监控方案可以直接拿过来用,比如状态查看, 杀死进程等,不好的地方就是如果需要拉起的话则是在容器外做不到的,因为拉起一般都是 通过可执行文件的路径来做的,而容器的文件系统是虚拟的,所以基本上无法实现。
这种方式也是不推荐的。我们使用容器,最终也是为了虚拟化,所以在不影响性能的情况下, 应该进行将各部分都虚拟出来,这样最不容易出问题。如果虚拟网络性能不好,我们可以先 不用虚拟网络,但是其他的部分如果不是因为性能原因,还是尽量通过其他可能的途径去解 决问题,而不是破坏环境的一致性。