前言
在docker常用指令详解一文中介绍过使用 docker run
命令配合各种复杂的选项完成对容器的构建和运行,但是这么长串的命令真的不是很好记也容易敲错。Docker Compose 是一个用来定义和运行复杂应用的 Docker 工具,以 yaml
格式的数据来保存容器配置,使用更简单的命令完成对容器的管理。此外 docker-compose.yml
还起到一个说明文档的作用, 一切配置在里面显得一目了然,就不用另外单独写部署文档了。
1. 安装 Docker Compose(官方文档)
# curl方式安装(推荐)
# 如果权限不够,先执行 sudo -i 切换到root帐号
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安装方法]())
sudo pip install docker-compose
2. 卸载 Docker Compose
# 对于curl安装方式
sudo rm /usr/local/bin/docker-compose
# 对于pip安装方式
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: {}
一般只写第二层内容即可, 如果要指定语法版本则要从最外层开始写。
示例
以我去年写的一篇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.78/bin/apache-tomcat-7.0.78.tar.gz && \
tar -zxvf apache-tomcat-7.0.78.tar.gz && \
mv apache-tomcat-7.0.78 tomcat && \
# 清空webapps下自带项目
rm -r tomcat/webapps/* && \
rm apache-tomcat-7.0.78.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
网友评论