前言
在 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:8080 或 ip:3000 访问这两个项目了 ( 注意防火墙开放这两个端口哦 )
说明:
- 上面的两个项目分别展示了 docker 的两种使用方式:
- 环境和应用打包到一个镜像中作为一个整体
- 环境和应用独立,可以自由组合
- 关于 docker-compose 的 build 操作网上的介绍比较少,有几点要注意的:
- 配置文件中 build 参数内容指定 Dockerfile 文件的路径, 貌似在高版本中可以是 map 类型
- build 操作需要使用 sudo 提权,否则会报一些奇怪的错误
将日志文件映射到宿主机目录上方便查看,如果源码不想打包到镜像中也可以做映射。还有一个小技巧,用空目录映射镜像已有目录达到删除的效果,如
tomcat/webapps
下的自带应用是没用的,可以用这个方法清除。JVM 参数
可以配置在环境变量JAVA_OPTS
中。使用 nodejs 的全局安装 (-g
) 时别忘了设置NODE_PATH
command
只能运行一条命令,如果要运行多条就使用sh -c "..."
, 实际命令用&&
隔开做参数传入websocket 的 demo源码 有不少同学反映项目配置不起来,研究下上面的
Dockerfile
和docker-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