使用 Docker Compose 构建容器

前言


Docker 常用指令详解 一文中介绍过使用 docker run 命令配合各种复杂的选项完成对 容器 的构建和运行,但是这么长串的命令真的不是很好记也容易敲错。Docker Compose 是一个用来定义和运行复杂应用的 Docker 工具,以 yaml 格式的数据来保存 容器配置,使用更简单的命令完成对 容器 的管理。此外 docker-compose.yml 还起到一个说明文档的作用, 一切配置在里面显得一目了然,就不用另外单独去写部署文档了。

安装与卸载


1. 安装 Docker Compose ( 官方文档 )
# curl方式安装(推荐)
# 源站(https://github.com/docker/compose/releases)太慢了,这里用daocloud上的镜像,版本号可自行修改
sudo curl -L https://get.daocloud.io/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /tmp/docker-compose
chmod +x /tmp/docker-compose
sudo mv /tmp/docker-compose /usr/local/bin/docker-compose

# pip方式安装(需要python,[pip安装方法](http://www.cnblogs.com/lzj0218/p/5753675.html))
sudo pip install docker-compose
2. 卸载 Docker Compose
# 对于curl安装方式
sudo rm /usr/local/bin/docker-compose

# 对于pip安装方式
sudo pip uninstall docker-compose

使用方法 ( 参考 )


docker-compose [选项] [子命令]
命令选项列表
选项 说明
-f 指定配置文件, 默认为 ./docker-compose.yml
-p 设置项目名, 默认为配置文件上级目录名
--verbose 输出详细信息
-H 指定docker服务器, 相当于 docker -H
子命令列表
子命令 说明
build 构建或重建服务依赖的镜像 ( 配置文件指定 build 而不是 image )
config 校验文件并显示解析后的配置
images 列出容器使用的镜像
events 监控服务下容器的事件
logs 显示容器的输出内容 相当于 docker logs
port 打印绑定的开放端口
ps 显示当前项目下的容器,加 -p 选项来指定项目名 相当于 docker ps
help 命令帮助
pull 拉取服务用到的镜像 相当于 docker pull
up 项目下创建服务并启动容器,如果指定了项目名,其他操作也要带上项目名参数。容器名格式:[ 项目名 ]_[ 服务名 ]_[ 序号 ]
down 移除 up 命令创建的容器、网络、挂载点、镜像
pause 暂停服务下所的容器
unpause 恢复服务下所有暂停的容器
rm 删除服务下停止的容器
exec 在服务下启动的容器中运行命令 相当于 docker exec,
run 服务下创建并运行容器 相当于 docker run ,与 up 命令的区别在于端口要另外映射,且不受 start/stop/restart/kill 等命令影响,容器名格式:[ 项目名 ]_[ 服务名 ]_run_[ 序号 ]
scale 设置服务的容器数目, 变多就新增, 变少就删除
start 开启服务 ( up 命令创建的所有容器 ) 相当于 docker start
stop 停止服务 ( up 命令创建的所有容器 ) 相当于 docker stop
restart 重启服务 ( up 命令创建的所有容器 ) 相当于 docker restart
kill 向服务发送信号 ( up 命令创建的所有容器 ) 相当于 docker kill

docker-compose.yml 语法 ( 参考进阶用法 )


  • 示例结构:
networks: {}
services:
  [service-name-1]:
    image: ...
    network_mode: bridge
    ports:
      \- 8080:8080/tcp
    ...
  ...
  [service-name-N]:
    image: ...
    network_mode: bridge
    ports:
      \- 8080:8080/tcp
    ...
# 指定 docker-compose 语法的版本
version: '2.1'
volumes: {}

一般只写第二层 [services] 的内容即可, 如果要指定语法版本则要从最外层开始写。

示例


以我去年写的一篇 websocket 文章 中的项目作为示例

在当前目录下创建 docker-compose.yml

# tomcat版
demo-websocket-tomcat:
  # 指定用于构建镜像的Dockerfile路径, 值为字符串
  build: '.'
  # 设置容器用户名(镜像中已创建),默认root
  user: user_docker
  # 设置容器主机名
  hostname: docker-anyesu
  # 容器内root账户是否拥有宿主机root账户的所有权限 [参考](http://blog.csdn.net/halcyonbaby/article/details/43499409)
  privileged: false
  # always (当容器退出时docker自动重启它)
  # on-failure:10 (当容器非正常退出, 最多自动重启10次, 10之后不再重启)
  restart: always
  # 容器的网络连接类型,anyesu_net是创建的自定义网桥
  net: anyesu_net
  # 挂载点,设置与宿主机之间的路径映射
  volumes:
  - /usr/anyesu/docker/tomcat-logs:/usr/anyesu/tomcat/logs
  # :ro表示只读,默认为:rw
  #- /usr/anyesu/docker/tomcat-logs:/usr/anyesu/tomcat/logs:ro
  # 与宿主机之间的端口映射
  ports:
  - 8080:8080
  # 设置容器dns, 如果设置了net项则此项失效, 暂不清楚原因, 不过可以使用本地文件映射为容器的/etc/resolv.conf文件。
  dns: 8.8.8.8
  # 设置容器环境变量
  environment:
    JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom

# nodejs版
demo-websocket-nodejs:
  # 使用镜像
  image: node:alpine
  privileged: false
  restart: always
  volumes:
  - /usr/anyesu/docker/websocket-master/Nodejs-Websocket:/usr/anyesu/node
  ports:
  - 3000:8080
  - 3002:3002
  # 覆盖容器启动后默认执行的命令
  command: sh -c "npm install ws@1.1.0 express -g && node /usr/anyesu/node/server.js"
  environment:
    NODE_PATH: /usr/local/lib/node_modules

再创建 Dockerfile 文件, 用于构建镜像

FROM alpine:latest
MAINTAINER anyesu

RUN echo -e "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main\n\
https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/community" > /etc/apk/repositories && \
    # 设置时区
    apk --update add ca-certificates && \
    apk add tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone && \
    # 安装jdk
    apk add openjdk7=7.121.2.6.8-r0 && \
    # 安装wget
    apk add wget=1.18-r1 && \
    tmp=/usr/anyesu/tmp && \
    mkdir -p $tmp && \
    cd /usr/anyesu && \
    # 下载tomcat
    wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-7/v7.0.94/bin/apache-tomcat-7.0.94.tar.gz && \
    tar -zxvf apache-tomcat-7.0.94.tar.gz && \
    mv apache-tomcat-7.0.94 tomcat && \
    # 清空webapps下自带项目
    rm -r tomcat/webapps/* && \
    rm apache-tomcat-7.0.94.tar.gz && \
    cd $tmp && \
    # 下载websocket-demo源码
    wget https://github.com/anyesu/websocket/archive/master.zip && \
    unzip master.zip && \
    proj=$tmp/websocket-master/Tomcat-Websocket && \
    src=$proj/src && \
    tomcatBase=/usr/anyesu/tomcat && \
    classpath="$tomcatBase/lib/servlet-api.jar:$tomcatBase/lib/websocket-api.jar:$proj/WebRoot/WEB-INF/lib/fastjson-1.1.41.jar" && \
    output=$proj/WebRoot/WEB-INF/classes && \
    mkdir -p $output && \
    # 编译java代码
    /usr/lib/jvm/java-1.7-openjdk/bin/javac -sourcepath $src -classpath $classpath -d $output `find $src -name "*.java"` && \
    # 拷贝到tomcat
    mv $proj/WebRoot $tomcatBase/webapps/ROOT && \
    rm -rf $tmp && \
    apk del wget && \
    # 清除apk缓存
    rm -rf /var/cache/apk/* && \
    # 添加普通用户
    addgroup -S group_docker && adduser -S -G group_docker user_docker && \
    # 修改目录所有者
    chown user_docker:group_docker -R /usr/anyesu

# 设置环境变量
ENV JAVA_HOME /usr/lib/jvm/java-1.7-openjdk
ENV CATALINA_HOME /usr/anyesu/tomcat
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin

# 暴露端口
EXPOSE 8080

# 启动命令(前台程序)
CMD ["catalina.sh", "run"]

接着就可以构建并运行了

# 创建一个名为anyesu_net、网段为172.18.0.0的网桥(docker默认创建的网段为172.17.0.0)
docker network create --subnet=172.18.0.0/16 anyesu_net

# 子命令 up 选项:
# -d 后台运行
# --build 重新构建依赖的镜像(如果docker-compose.yml中指定了build项的话)
# --force-recreate 重启容器, 即使配置和镜像都没有改变
wget https://github.com/anyesu/websocket/archive/master.zip
unzip master.zip
sed -i 's$8082$3002$' ./websocket-master/Nodejs-Websocket/server.js
sed -i 's$8082$3002$' ./websocket-master/Nodejs-Websocket/js/chat.js
sudo docker-compose up -d --build --force-recreate

之后就可以通过 ip:8080ip:3000 访问这两个项目了 ( 注意防火墙开放这两个端口哦 )

说明:
  • 上面的两个项目分别展示了 docker 的两种使用方式:
  1. 环境和应用打包到一个镜像中作为一个整体
  2. 环境和应用独立,可以自由组合
  • 关于 docker-compose 的 build 操作网上的介绍比较少,有几点要注意的:
  1. 配置文件中 build 参数内容指定 Dockerfile 文件的路径, 貌似在高版本中可以是 map 类型
  2. build 操作需要使用 sudo 提权,否则会报一些奇怪的错误
  • 将日志文件映射到宿主机目录上方便查看,如果源码不想打包到镜像中也可以做映射。还有一个小技巧,用空目录映射镜像已有目录达到删除的效果,如 tomcat/webapps 下的自带应用是没用的,可以用这个方法清除。

  • JVM 参数 可以配置在环境变量 JAVA_OPTS 中。使用 nodejs 的全局安装 ( -g ) 时别忘了设置 NODE_PATH

  • command 只能运行一条命令,如果要运行多条就使用 sh -c "...", 实际命令用 && 隔开做参数传入

  • websocket 的 demo源码 有不少同学反映项目配置不起来,研究下上面的 Dockerfiledocker-compose.yml 应该就能对项目的结构和依赖有更清晰的了解了。

  • demo-websocket-tomcat 项目的配置中, 出于 安全 考虑,使用普通用户登录。宿主机的 /usr/anyesu/tomcat/logs 目录要设置 777 权限,否则 tomcat 无法写日志。

  • 容器的配置信息, hosts , dns 配置文件等可以在 /var/lib/docker/containers/[容器id]/ 下查看。

遇到的坑:

之前一个原本启动只要 10 秒的小项目在容器中有时候重启要好几分钟甚至可能会一直启不来,在日志中找到下面这段内容:

INFO: Deploying web application directory /usr/anyesu/tomcat/webapps/ROOT
Jun 10, 2017 3:03:28 PM org.apache.catalina.startup.TldConfig execute
INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
Jun 10, 2017 3:14:01 PM org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [624,301] milliseconds.
Jun 10, 2017 3:14:05 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deployment of web application directory /usr/fuyou/tomcat/webapps/ROOT has finished in 644,588 ms
Jun 10, 2017 3:14:05 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Jun 10, 2017 3:14:05 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Jun 10, 2017 3:14:05 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 644776 ms

关键的一句就是: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [624,301] milliseconds
这个步骤竟然用了10分钟!查了一下发现是 docker 容器下 随机数与熵池策略 有问题。
解决方法如下,个人更推荐第二种:

  • 打开 $JAVA_PATH/jre/lib/security/java.security,将
    securerandom.source=file:/dev/random 替换为 securerandom.source=file:/dev/urandom
  • 启动容器时,使用 JVM 参数JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom

系列文章


Docker 学习总结

Docker 常用指令详解

使用 Dockerfile 构建镜像

Docker Daemon 连接方式详解

Docker 下的网络模式


转载请注明出处:http://www.jianshu.com/p/ee8e7d2eb645

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容