这篇文章是受 dockboard 之托帮忙翻译的与 docker 有关的技术文章。译自 Using Supervisor with Docker to manage processes (supporting image inheritance) ,做者 Quinten Krijger。
在八月份,我写了一篇关于如何建立 tomcat 镜像的 blog 。从那之后,docker 又改进了不少,我对 docker 的了解也增长了不少。我很高兴和你分们享我找到的关于管理 container 进程的好办法。在读完这篇文章后,我但愿你能善加利用我 github 仓库 里的 supervisor 镜像。
Docker 命令
在以前的文章里,我提到 Docker(只能)支持运行一个前台进程。咱们一般习惯使用相似 upstart 这种管理服务来初始化启动流程,可是 Docker 默认没有这些服务的支持。刚开始使用 Docker 时会很不习惯,你必须指定你想要运行的进程。这种行为和虚拟机相比有个优势,会尽量的保持轻量的 container。你能够经过 run 命令最后的参数,在启动 container 时指定进程命令,好比:git
docker run ubuntu echo "hello world"
另一种方法,你能够利用 CMD 指令,在 Dockerfile 里指定 docker run 命令的默认参数。好比,若是你目录下的 Dockerfile 包含如下内容: github
FROM ubuntu CMD echo "hello world"
再使用下面的指令构造 hello_world_printer 镜像: web
docker build -t "hello_world_printer" .
使用下面的命令,你能够获得和以前 run 命令相同的执行结果。docker
docker run hello_world_printer
要注意,由于你能够覆盖掉 CMD 指定的命令行参数,这个只是个运行时的指令。有趣的事情是,在 Linux container 里,你能够只调用 upstart 命令而后获得和普通虚拟机大体相同的行为。
运行多个命令
运行多个进程是个很正常的想法。好比,一个 ssh 服务(这样就能登陆到正在运行的 container)和实际的应用。你能够用下面的方法运行 container:ubuntu
docker run ... /usr/sbin/sshd && run_your_app_in_foreground
这在开发时很方便。这样,当应用进程退出后,由于惟一的前台程序退出了,container 会自动关闭。固然你可使用 using /usr/bin/sshd -D 保证 container 不会退出,可是这里真正的问题是,这种使用 run 命令设置初始程序的方式不够简洁。并且,随着你的 container 变复杂,run 命令会愈来愈长。
因此,在运行更复杂的 container 的时候,不少人使用复杂的 bash 脚本。典型的 bash 脚本会执行一个前台进程,并开启一个或者多个(renegade)守护进程。与只是用 Docker 命令行的方式相比,这种方法最重要的改进在于,bash 脚本是能够作版本控制的:启动脚本在你的 Docker 镜像里,新的改动能够和软件项目一块儿分发。不过,使用 bash 脚本管理进程依旧简陋枯燥,并且容易出错。缓存
……使用 supervisor
更好的方法是使用 supervisor 。supervisor 能够更好的管理进程:使用更加简洁的代码管理进程;在崩溃时能够重启进程;容许重启一组进程而且有命令行工具和网页界面来管理进程。固然,越大的能力要求 越大的责任:大量使用 supervisor 特性的代码,预示着你应该将整个服务更好的拆分红多个小的 supervisor 来管理。
我的来说,我喜欢 supervisor 让我用更清晰的代码管理启动的进程。我见过最简洁的使用例子,是子镜像扩展出一个进程组。好比,若是你常用 SSH,使用一个 SSH 镜像做为基础镜像就是很合理的。这种状况,在全部基于这个镜像的扩展镜像上实现启动 SSH 进程的代码,形式少就是一种重复代码。我来给大家展现下我找到的解决这个问题的好办法。
supervisor 基础镜像
首先,由于我默认使用 supervisor,因此我全部的镜像都扩展自一个只包含 supervisor 和最新版本 ubuntu 的基础镜像。你能够在 这里 找到这个 Dockerfile。这个基础镜像包括一个配置文件 /etc/supervisor.conf : tomcat
[supervisord] nodaemon=true [include] files = /etc/supervisor/conf.d/*.conf
这个配置让 supervisor 自己之前台进程运行,这样可让咱们的 container 启动后持续运行。第二,这个配置将包含全部在 /etc/supervisor/conf.d/ 目录下的配置文件,启动任何在这里定义的程序。
扩展基础镜像
是的,想法很简单。全部的子 container 经过将特定的 service.sv.conf 放到特定的目录的方式,将其本身的服务加入到 supervisor 的管理里。以后,使用以下命令启动 container:bash
docker run child_image_name "supervisor -c /etc/supervisor.conf"
会自动启动全部指定的进程。你能够对镜像作多层扩展,每层扩展加入一个或者多个服务到配置目录。在 Docker 里使用 supervisor 启动命令代替 upstart 也更有效和有范。
做为例子,让咱们看看以前 blog 提到的 Tomcat 工做栈,是如何使用这种改进后的方法的。
网络
[program:webapp] command=/bin/bash -c "env > /tmp/tomcat.env && cat /etc/default/tomcat7 >> /tmp/tomcat.env && mv /tmp/tomcat.env /etc/default/tomcat7 && service tomcat7 start" redirect_stderr=true
执行 Tomcat 服务的命令并不像我喜欢的那样简洁,将其放到一个专门的脚本里会更好。命令先添加了一些环境变量,好比 container 的关联参数 ,到 /etc/default/tomcat7 ,这样咱们能够在以后的配置中使用这些参数,后面的例子会展现这种用法。也许使用相似 etcd 的键值存储会更好,不过这超出了本文的范畴。
一个 Tomcat 网络程序的 Dockerfile 例子
如何安装实际的网络应用超出了本文的范畴,不过,做为结束,我给出了个 Dockerfile 例子,演示如何使用这个工做栈。这个例子彻底基于 Java Tomcat,因此若是你对这个不感兴趣,别读了,玩别的去吧:)
假设,咱们有一个使用 Elasticsearch 的网络应用:app
FROM quintenk/tomcat:7 # 安装一些项目的依赖,这些依赖在每次更新时不会改变 # RUN apt-get -y install ... RUN rm -rf /var/lib/tomcat7/webapps/* # 将配置加入 /etc/default/tomcat7,好比: ... RUN echo 'DOCKER_OPTS="-DELASTICSEARCH_SERVER_URL=${ELASTICSEARCH_PORT_9200_TCP_ADDR}"' >> /etc/default/tomcat7 RUN echo 'CATALINA_OPTS="... ${DOCKER_OPTS}"' >> /etc/default/tomcat7 # 加入相似 log4j.properties 的配置文件,并将其 chown root:tomcat7 # 假设项目已经构建好了,并且 ROOT.war 在你构建 Docker 的目录(包含 Dockerfile 的目录)。基于缓存的考虑,这个做为最后的步骤 ADD ROOT.war /var/lib/tomcat7/webapps/ RUN chown root:tomcat7 /var/lib/tomcat7/webapps/ROOT.war CMD supervisord -c /etc/supervisor.conf
In this code, the variables for elasticsearch (a search index), are set because the Supervisor configuration for Tomcat prepends all variables to the /etc/default/tomcat7 file at start-up time. Of course, we would need to start the webapp with a link to the elasticsearch container: e.g. 这段代码里,elasticsearch 的相关环境变量(搜索索引)已经被设置了,由于 supervisor 关于 Tomcat 的配置,会在启动时将全部环境变量添加到 /etc/default/tomcat7。固然,咱们在启动网络应用镜像时须要关联到 elasticsearch containter,好比:
docker run -link name_of_elasticsearch_instance:elasticsearch -d name_of_webapp_image "supervisor -c /etc/supervisor.conf"
你如今的网络应用能够去访问 ELASTICSEARCH_SERVER_URL 路径了。你能够在配置文件里使用这个变量,像这样:
FROM ubuntu CMD echo "hello world" 0
这样就能够将配置暴露给你的应用程序。若是你是个 Java 开发者,而且也阅读了前一篇文章,但愿这让你能开始一段愉快的代码之旅。