这篇文章是受 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 时指定进程命令,比如:

 
docker run ubuntu echo "hello world"


另外一种方法,你可以利用 CMD 指令,在 Dockerfile 里指定 docker run 命令的默认参数。比如,如果你目录下的 Dockerfile 包含以下内容:

FROM ubuntu
CMD echo "hello world"

再使用下面的指令构造 hello_world_printer 镜像:

docker build -t "hello_world_printer" .

使用下面的命令,你可以得到和之前 run 命令相同的执行结果。

docker run hello_world_printer

要注意,因为你可以覆盖掉 CMD 指定的命令行参数,这个只是个运行时的指令。有趣的事情是,在 Linux container 里,你可以只调用 upstart 命令然后得到和普通虚拟机大致相同的行为。
运行多个命令

运行多个进程是个很正常的想法。比如,一个 ssh 服务(这样就能登录到正在运行的 container)和实际的应用。你可以用下面的方法运行 container:

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 :

[supervisord]
nodaemon=true
[include]
files = /etc/supervisor/conf.d/*.conf

这个配置让 supervisor 本身以前台进程运行,这样可以让我们的 container 启动后持续运行。第二,这个配置将包含所有在 /etc/supervisor/conf.d/ 目录下的配置文件,启动任何在这里定义的程序。
扩展基础镜像

是的,想法很简单。所有的子 container 通过将特定的 service.sv.conf 放到特定的目录的方式,将其自己的服务加入到 supervisor 的管理里。之后,使用如下命令启动 container:

docker run child_image_name "supervisor -c /etc/supervisor.conf"

会自动启动所有指定的进程。你可以对镜像做多层扩展,每层扩展加入一个或者多个服务到配置目录。在 Docker 里使用 supervisor 启动命令代替 upstart 也更有效和有范。
作为例子,让我们看看之前 blog 提到的 Tomcat 工作栈,是如何使用这种改进后的方法的。

  • 首先,和之前讨论的一样,我们使用从 ubuntu 扩展而来的 supervisor 基础镜像
  • 之后,我们使用在 supervisor 上安装了 Java 的 JDK 镜像 。Java 只是其他服务使用的库,所以我们在这层不指定任何启动服务。这层要做一些类似设置 JAVA_HOME 环境变量的通常任务
  • Tomcat 镜像在工作栈上安装 Tomcat 并暴露 8080 端口。这层包括一个名字是 Tomcat 的服务,定义在 tomcat.sv.conf :
  • [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 镜像,并安装入真正的应用程序。当启动 supervisor 的时候,会自动启动 Tomcat。

一个 Tomcat 网络程序的 Dockerfile 例子

如何安装实际的网络应用超出了本文的范畴,不过,作为结束,我给出了个 Dockerfile 例子,演示如何使用这个工作栈。这个例子完全基于 Java Tomcat,所以如果你对这个不感兴趣,别读了,玩别的去吧:)
假设,我们有一个使用 Elasticsearch 的网络应用:

FROM quintenk/tomcat:
# 安装一些项目的依赖,这些依赖在每次更新时不会改变
# 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"


这样就可以将配置暴露给你的应用程序。如果你是个 Java 开发者,并且也阅读了前一篇文章,希望这让你能开始一段愉快的代码之旅。

在Docker里使用(支持镜像继承的)supervisor管理进程(转)的更多相关文章

  1. 【云计算】使用supervisor管理Docker多进程-ntpd+uwsgi+nginx示例最佳实践

    supervisor安装启动: apt-get install supervisor -y # start supervisord nodaemon /usr/bin/supervisord --no ...

  2. Docker 最常用的镜像命令和容器命令

    本文列出了 Docker 使用过程中最常用的镜像命令和容器命令,以及教大家如何操作容器数据卷,实现容器数据的备份.熟练练习这些命令以后,再来一些简单的应用部署练习,大家就可以学习 Docker 的镜像 ...

  3. LindDotNetCore~docker里图像上生成中文乱码问题

    回到目录 因为docker上的大部分镜像都是基于linux系统的,所以在向图像中写中文时需要考虑中文字体问题,例如在microsoft/aspnetcore2.0这个镜像,它是基于debian系统的, ...

  4. 在 Docker 里跑 Java,你必须知道的那些事儿!(转)

    原文 https://www.jianshu.com/p/0897d0581872 背景:众所周知,当我们执行没有任何调优参数(如“java-jar mypplication-fat.jar”)的 J ...

  5. MySQL 到底能不能放到 Docker 里跑?

    https://weibo.com/ttarticle/p/show?id=2309404296528549285581 前言 前几月经常看到有 MySQL 到底能不能放到 Docker 里跑的各种讨 ...

  6. Docker学习笔记之使用 Docker Hub 中的镜像

    0x00 概述 自己编写 Dockerfile 能够很好的实现我们想要的程序运行环境,不过如果装有我们想要环境的镜像已经由热心的开发者构建好并共享在 Docker Hub 上,直接使用它们就会远比自己 ...

  7. Docker基础修炼2--Docker镜像原理及常用命令

    通过前文的讲解对Docker有了基本认识之后,我们开始进入实战操作,本文先演示Docker三要素之镜像原理和相关命令. 本文的演示环境仍然沿用上一篇文章在本地Centos7中安装的环境,如果你本地没有 ...

  8. Docker学习笔记:镜像、容器、数据卷

    核心概念 镜像:一个只读的模板,类似虚拟机的镜像. 容器:可以理解为镜像的一个运行实例.运行时类似于沙箱,多个容器互相独立. 仓库:存放镜像文件的地方. 镜像 命令表格 命令 解释 选项 docker ...

  9. Docker最常用的镜像命令和容器命令

    一.镜像相关命令 官方文档:https://docs.docker.com/referenc 1.1查看镜像 [root@localhost ~]# docker images REPOSITORY ...

随机推荐

  1. FTP具有两种模式

    FTP具有两种模式,分别是port模式(也叫主动模式)和pasv模式(也叫被动模式),怎么来理解这两种模式呢?我来打个比喻吧,在主动模式下:客户端给服务器端的21端口发命令说,我要下载什么什么,并且还 ...

  2. ubuntu12.04国内软件源

    手动修改方式: 163源 deb http://mirrors.163.com/ubuntu/ precise main restricted deb-src http://mirrors.163.c ...

  3. win32 打印机api

    ? 4. API之打印函数 AbortDoc 取消一份文档的打印 AbortPrinter 删除与一台打印机关联在一起的缓冲文件 AddForm 为打印机的表单列表添加一个新表单 AddJob 用于获 ...

  4. Traefik访问master节点不通的问题定位

    问题 部署traefik到客户节点的对外访问节点后,发现日志里面报错 类似于 E0122 :: reflector.go:] k8s.io/dns/vendor/k8s.io/client-go/to ...

  5. python 协程的学习记录

    协程是个子程序,执行过程中,内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行 从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常 ...

  6. cpu gpu数据同步

    https://developer.apple.com/documentation/metal/advanced_command_setup/cpu_and_gpu_synchronization d ...

  7. 一些数据 bandwidth之类

    33ms  2436x1125 full resolution write bandwidth  300MB/s memory bandwidth snapdragon 630 10GB/3   // ...

  8. [Python爬虫] 之十六:Selenium +phantomjs 利用 pyquery抓取一点咨询数据

    本篇主要是利用 pyquery来定位抓取数据,而不用xpath,通过和xpath比较,pyquery效率要高. 主要代码: # coding=utf-8 import os import re fro ...

  9. 《深入理解Java虚拟机》笔记6

    class文件由无符号数和表两种类型数据构成.表其实相当于一种结构体,内部又嵌套无符号数或者表. 用u1,u2,u4,u8分别代表一个字节,两个字节,四个字节,八个字节的无符号数. 如图中所示,cla ...

  10. Java 根据IP获取地址

    用淘宝接口:(源码:java 根据IP地址获取地理位置) pom.xml: <!-- https://mvnrepository.com/artifact/net.sourceforge.jre ...