前言
在 Docker 常用指令詳解 一文中介紹過使用 docker run 命令配合各種復(fù)雜的選項(xiàng)完成對(duì) 容器 的構(gòu)建和運(yùn)行,但是這么長串的命令真的不是很好記也容易敲錯(cuò)。Docker Compose 是一個(gè)用來定義和運(yùn)行復(fù)雜應(yīng)用的 Docker 工具,以 yaml 格式的數(shù)據(jù)來保存 容器配置,使用更簡單的命令完成對(duì) 容器 的管理。此外 docker-compose.yml 還起到一個(gè)說明文檔的作用, 一切配置在里面顯得一目了然,就不用另外單獨(dú)去寫部署文檔了。
安裝與卸載
1. 安裝 Docker Compose ( 官方文檔 )
# curl方式安裝(推薦)
# 源站(https://github.com/docker/compose/releases)太慢了,這里用daocloud上的鏡像,版本號(hào)可自行修改
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
# 對(duì)于curl安裝方式
sudo rm /usr/local/bin/docker-compose
# 對(duì)于pip安裝方式
sudo pip uninstall docker-compose
使用方法 ( 參考 )
docker-compose [選項(xiàng)] [子命令]
命令選項(xiàng)列表
| 選項(xiàng) | 說明 |
|---|---|
| -f | 指定配置文件, 默認(rèn)為 ./docker-compose.yml |
| -p | 設(shè)置項(xiàng)目名, 默認(rèn)為配置文件上級(jí)目錄名 |
| --verbose | 輸出詳細(xì)信息 |
| -H | 指定docker服務(wù)器, 相當(dāng)于 docker -H |
子命令列表
| 子命令 | 說明 |
|---|---|
| build | 構(gòu)建或重建服務(wù)依賴的鏡像 ( 配置文件指定 build 而不是 image ) |
| config | 校驗(yàn)文件并顯示解析后的配置 |
| images | 列出容器使用的鏡像 |
| events | 監(jiān)控服務(wù)下容器的事件 |
| logs | 顯示容器的輸出內(nèi)容 相當(dāng)于 docker logs |
| port | 打印綁定的開放端口 |
| ps | 顯示當(dāng)前項(xiàng)目下的容器,加 -p 選項(xiàng)來指定項(xiàng)目名 相當(dāng)于 docker ps |
| help | 命令幫助 |
| pull | 拉取服務(wù)用到的鏡像 相當(dāng)于 docker pull |
| up | 項(xiàng)目下創(chuàng)建服務(wù)并啟動(dòng)容器,如果指定了項(xiàng)目名,其他操作也要帶上項(xiàng)目名參數(shù)。容器名格式:[ 項(xiàng)目名 ]_[ 服務(wù)名 ]_[ 序號(hào) ] |
| down | 移除 up 命令創(chuàng)建的容器、網(wǎng)絡(luò)、掛載點(diǎn)、鏡像 |
| pause | 暫停服務(wù)下所的容器 |
| unpause | 恢復(fù)服務(wù)下所有暫停的容器 |
| rm | 刪除服務(wù)下停止的容器 |
| exec | 在服務(wù)下啟動(dòng)的容器中運(yùn)行命令 相當(dāng)于 docker exec, |
| run | 服務(wù)下創(chuàng)建并運(yùn)行容器 相當(dāng)于 docker run ,與 up 命令的區(qū)別在于端口要另外映射,且不受 start/stop/restart/kill 等命令影響,容器名格式:[ 項(xiàng)目名 ]_[ 服務(wù)名 ]_run_[ 序號(hào) ] |
| scale | 設(shè)置服務(wù)的容器數(shù)目, 變多就新增, 變少就刪除 |
start |
開啟服務(wù) ( up 命令創(chuàng)建的所有容器 ) 相當(dāng)于 docker start |
stop |
停止服務(wù) ( up 命令創(chuàng)建的所有容器 ) 相當(dāng)于 docker stop |
restart |
重啟服務(wù) ( up 命令創(chuàng)建的所有容器 ) 相當(dāng)于 docker restart |
| kill | 向服務(wù)發(fā)送信號(hào) ( up 命令創(chuàng)建的所有容器 ) 相當(dāng)于 docker kill |
docker-compose.yml 語法 ( 參考、進(jìn)階用法 )
- 示例結(jié)構(gòu):
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] 的內(nèi)容即可, 如果要指定語法版本則要從最外層開始寫。
示例
以我去年寫的一篇 websocket 文章 中的項(xiàng)目作為示例
在當(dāng)前目錄下創(chuàng)建 docker-compose.yml
# tomcat版
demo-websocket-tomcat:
# 指定用于構(gòu)建鏡像的Dockerfile路徑, 值為字符串
build: '.'
# 設(shè)置容器用戶名(鏡像中已創(chuàng)建),默認(rèn)root
user: user_docker
# 設(shè)置容器主機(jī)名
hostname: docker-anyesu
# 容器內(nèi)root賬戶是否擁有宿主機(jī)root賬戶的所有權(quán)限 [參考](http://blog.csdn.net/halcyonbaby/article/details/43499409)
privileged: false
# always (當(dāng)容器退出時(shí)docker自動(dòng)重啟它)
# on-failure:10 (當(dāng)容器非正常退出, 最多自動(dòng)重啟10次, 10之后不再重啟)
restart: always
# 容器的網(wǎng)絡(luò)連接類型,anyesu_net是創(chuàng)建的自定義網(wǎng)橋
net: anyesu_net
# 掛載點(diǎn),設(shè)置與宿主機(jī)之間的路徑映射
volumes:
- /usr/anyesu/docker/tomcat-logs:/usr/anyesu/tomcat/logs
# :ro表示只讀,默認(rèn)為:rw
#- /usr/anyesu/docker/tomcat-logs:/usr/anyesu/tomcat/logs:ro
# 與宿主機(jī)之間的端口映射
ports:
- 8080:8080
# 設(shè)置容器dns, 如果設(shè)置了net項(xiàng)則此項(xiàng)失效, 暫不清楚原因, 不過可以使用本地文件映射為容器的/etc/resolv.conf文件。
dns: 8.8.8.8
# 設(shè)置容器環(huán)境變量
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
# 覆蓋容器啟動(dòng)后默認(rèn)執(zhí)行的命令
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
再創(chuàng)建 Dockerfile 文件, 用于構(gòu)建鏡像
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 && \
# 設(shè)置時(shí)區(qū)
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下自帶項(xiàng)目
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
# 設(shè)置環(huán)境變量
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
# 啟動(dòng)命令(前臺(tái)程序)
CMD ["catalina.sh", "run"]
接著就可以構(gòu)建并運(yùn)行了
# 創(chuàng)建一個(gè)名為anyesu_net、網(wǎng)段為172.18.0.0的網(wǎng)橋(docker默認(rèn)創(chuàng)建的網(wǎng)段為172.17.0.0)
docker network create --subnet=172.18.0.0/16 anyesu_net
# 子命令 up 選項(xiàng):
# -d 后臺(tái)運(yùn)行
# --build 重新構(gòu)建依賴的鏡像(如果docker-compose.yml中指定了build項(xiàng)的話)
# --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 訪問這兩個(gè)項(xiàng)目了 ( 注意防火墻開放這兩個(gè)端口哦 )
說明:
- 上面的兩個(gè)項(xiàng)目分別展示了 docker 的兩種使用方式:
- 環(huán)境和應(yīng)用打包到一個(gè)鏡像中作為一個(gè)整體
- 環(huán)境和應(yīng)用獨(dú)立,可以自由組合
- 關(guān)于 docker-compose 的 build 操作網(wǎng)上的介紹比較少,有幾點(diǎn)要注意的:
- 配置文件中 build 參數(shù)內(nèi)容指定 Dockerfile 文件的路徑, 貌似在高版本中可以是 map 類型
- build 操作需要使用 sudo 提權(quán),否則會(huì)報(bào)一些奇怪的錯(cuò)誤
將日志文件映射到宿主機(jī)目錄上方便查看,如果源碼不想打包到鏡像中也可以做映射。還有一個(gè)小技巧,用空目錄映射鏡像已有目錄達(dá)到刪除的效果,如
tomcat/webapps下的自帶應(yīng)用是沒用的,可以用這個(gè)方法清除。JVM 參數(shù)可以配置在環(huán)境變量JAVA_OPTS中。使用 nodejs 的全局安裝 (-g) 時(shí)別忘了設(shè)置NODE_PATHcommand只能運(yùn)行一條命令,如果要運(yùn)行多條就使用sh -c "...", 實(shí)際命令用&&隔開做參數(shù)傳入websocket 的 demo源碼 有不少同學(xué)反映項(xiàng)目配置不起來,研究下上面的
Dockerfile和docker-compose.yml應(yīng)該就能對(duì)項(xiàng)目的結(jié)構(gòu)和依賴有更清晰的了解了。在
demo-websocket-tomcat項(xiàng)目的配置中, 出于 安全 考慮,使用普通用戶登錄。宿主機(jī)的/usr/anyesu/tomcat/logs目錄要設(shè)置777權(quán)限,否則tomcat無法寫日志。容器的配置信息,
hosts,dns配置文件等可以在/var/lib/docker/containers/[容器id]/下查看。
遇到的坑:
之前一個(gè)原本啟動(dòng)只要 10 秒的小項(xiàng)目在容器中有時(shí)候重啟要好幾分鐘甚至可能會(huì)一直啟不來,在日志中找到下面這段內(nèi)容:
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
關(guān)鍵的一句就是: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [624,301] milliseconds
這個(gè)步驟竟然用了10分鐘!查了一下發(fā)現(xiàn)是 docker 容器下 隨機(jī)數(shù)與熵池策略 有問題。
解決方法如下,個(gè)人更推薦第二種:
- 打開 $JAVA_PATH/jre/lib/security/java.security,將
securerandom.source=file:/dev/random 替換為 securerandom.source=file:/dev/urandom - 啟動(dòng)容器時(shí),使用 JVM 參數(shù):
JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom