Docker 操作指南之 Compose 管理篇
一、简介
Compose 定位是“defining and running complex applications with Docker”,前身是 Fig,兼容 Fig 的模板文件。
Dockerfile 可以让用户管理一个单独的应用容器;而 Compose 则允许用户在一个模板(YAML 格式)中定义一组相关联的应用容器(被称为一个 project,即项目),例如一个 Web 服务容器再加上后端的数据库服务容器等。
该项目由 Python 编写,实际上调用了 Docker 提供的 API 来实现。
二、安装
安装 Compose 之前,要先安装 Docker,在此不再赘述。
1、pip安装
这种方式最为推荐。
执行命令。
sudo pip install -U docker-compose
安装成功后,可以查看 docker-compose 命令的用法。
2、二进制包
发布的二进制包可以在 https://github.com/docker/compose/releases 找到。
下载后直接放到执行路径即可。
#下载docker-compose-Linux-x86_64
cp docker-compose-Linux-x86_64 /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
三、使用
1、术语
首先介绍几个术语。
- 服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。
- 项目(project):由一组关联的应用容器组成的一个完整业务单元。
可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。
2、场景
下面,我们创建一个经典的 Web 项目:一个 Haproxy,挂载三个Web容器。
创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录:haproxy 和 web。
3、Web 子目录
这里用 Python 程序来提供一个简单的 HTTP 服务,打印出访问者的 IP 和 实际的本地 IP。
3.1、index.py
编写一个 index.py
作为服务器文件,代码为
#!/usr/bin/python
#authors: 7d
import sys
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
import socket
import fcntl
import struct
import pickle
from datetime import datetime
from collections import OrderedDict
class HandlerClass(SimpleHTTPRequestHandler):
def get_ip_address(self,ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
def log_message(self, format, *args):
if len(args) < 3 or "200" not in args[1]:
return
try:
request = pickle.load(open("pickle_data.txt","r"))
except:
request=OrderedDict()
time_now = datetime.now()
ts = time_now.strftime('%Y-%m-%d %H:%M:%S')
server = self.get_ip_address('eth0')
host=self.address_string()
addr_pair = (host,server)
if addr_pair not in request:
request[addr_pair]=[1,ts]
else:
num = request[addr_pair][0]+1
del request[addr_pair]
request[addr_pair]=[num,ts]
file=open("index.html", "w")
file.write("<!DOCTYPE html> <html> <body><center><h1><font color=\"blue\" face=\"Georgia, Arial\" size=8><em>HA</em></font> Webpage Visit Results</h1></center>");
for pair in request:
if pair[0] == host:
guest = "LOCAL: "+pair[0]
else:
guest = pair[0]
if (time_now-datetime.strptime(request[pair][1],'%Y-%m-%d %H:%M:%S')).seconds < 3:
file.write("<p style=\"font-size:150%\" >#"+ str(request[pair][1]) +": <font color=\"red\">"+str(request[pair][0])+ "</font> requests " + "from <<font color=\"blue\">"+guest+"</font>> to WebServer <<font color=\"blue\">"+pair[1]+"</font>></p>")
else:
file.write("<p style=\"font-size:150%\" >#"+ str(request[pair][1]) +": <font color=\"maroon\">"+str(request[pair][0])+ "</font> requests " + "from <<font color=\"navy\">"+guest+"</font>> to WebServer <<font color=\"navy\">"+pair[1]+"</font>></p>")
file.write("</body> </html>");
file.close()
pickle.dump(request,open("pickle_data.txt","w"))
if __name__ == '__main__':
try:
ServerClass = BaseHTTPServer.HTTPServer
Protocol = "HTTP/1.0"
addr = len(sys.argv) < 2 and "0.0.0.0" or sys.argv[1]
port = len(sys.argv) < 3 and 80 or int(sys.argv[2])
HandlerClass.protocol_version = Protocol
httpd = ServerClass((addr, port), HandlerClass)
sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
httpd.serve_forever()
except:
exit()
3.2、index.html
生成一个临时的 index.html
文件,其内容会被 index.py
更新。
touch index.html
3.3、Dockerfile
生成一个 Dockerfile,内容为
FROM python:2.7
WORKDIR /code
ADD . /code
EXPOSE 80
CMD python index.py
4、haproxy 目录
在其中生成一个 haproxy.cfg
文件,内容为
global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
listen stats :70
stats enable
stats uri /
frontend balancer
bind 0.0.0.0:80
mode http
default_backend web_backends
backend web_backends
mode http
option forwardfor
balance roundrobin
server weba weba:80 check
server webb webb:80 check
server webc webc:80 check
option httpchk GET /
http-check expect status 200
5、docker-compose.yml
编写 docker-compose.yml
文件,这个是 Compose 使用的主模板文件。内容十分简单,指定 3 个 web 容器,以及 1 个 haproxy 容器。
weba:
build: ./web
expose:
- 80
webb:
build: ./web
expose:
- 80
webc:
build: ./web
expose:
- 80
haproxy:
image: haproxy:latest
volumes:
- haproxy:/haproxy-override
- haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
links:
- weba
- webb
- webc
ports:
- "80:80"
- "70:70"
expose:
- "80"
- "70"
6、运行 compose 项目
现在 compose-haproxy-web
目录长成下面的样子。
compose-haproxy-web
├── docker-compose.yml
├── haproxy
│ └── haproxy.cfg
└── web
├── Dockerfile
├── index.html
└── index.py
在该目录下执行 docker-compose up 命令,会整合输出所有容器的输出。
$sudo docker-compose up
Recreating composehaproxyweb_webb_1...
Recreating composehaproxyweb_webc_1...
Recreating composehaproxyweb_weba_1...
Recreating composehaproxyweb_haproxy_1...
Attaching to composehaproxyweb_webb_1, composehaproxyweb_webc_1, composehaproxyweb_weba_1, composehaproxyweb_haproxy_1
此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。
访问本地 70 端口,可以查看到 haproxy 的统计信息。
当然,还可以使用 consul、etcd 等实现服务发现,这样就可以避免手动指定后端的 web 容器了,更为灵活。
四、Compose 命令说明
大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。
执行 docker-compose [COMMAND] --help
查看具体某个命令的使用说明。
基本的使用格式是:
docker-compose [options] [COMMAND] [ARGS...]
1、选项
• --verbose 输出更多调试信息。
• --version 打印版本并退出。
• -f, --file FILE 使用特定的 compose 模板文件,默认为 docker-compose.yml
。
• -p, --project-name NAME 指定项目名称,默认使用目录名称。
2、命令
2.1、build
构建或重新构建服务。
服务一旦构建后,将会带上一个标记名,例如 web_db。
可以随时在项目目录下运行 docker-compose build
来重新构建服务。
2.2、config
查看当前文件夹下的 compose yml
配置文件
2.3、create
创建服务(容器),但不启动,需要start才能启动。
2.4、down
停止和删除容器,包括网络、镜像和磁盘。
2.5、 events
Receive real time events from containers
2.6、exec
在容器中执行命令。
在容器中创建一个文件:
#docker-compose exec memcached touch /root/test
连接到容器:
2.7、help
获得一个命令的帮助。
2.8、kill
通过发送 SIGKILL 信号来强制停止服务容器。支持通过参数来指定发送的信号,例如:
docker-compose kill -s SIGINT
2.9、logs
查看服务的输出。
-f 可以跟踪查看。
2.10、pause
暂停一个服务。使用 unpause 再次启动。
2.11、port
打印容器中的私有端口绑定后的公共端口。
2.12、ps
列出所有容器。
2.13、pull
拉取服务镜像。
2.14、push
推送服务镜像。
2.15、restart
重启服务容器。
2.16、rm
删除停止的服务容器。
2.17、run
在一个服务上执行一个命令。
例如:
docker-compose run ubuntu ping docker.com
将会启动一个 ubuntu 服务,执行 ping docker.com
命令。
默认情况下,所有关联的服务将会自动被启动,除非这些服务已经在运行中。
该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照期望创建。
两个不同点:
- 给定命令将会覆盖原有的自动运行命令;
- 不会自动创建端口,以避免冲突。
如果不希望自动启动关联的容器,可以使用 --no-deps 选项,例如
docker-compose run --no-deps web python manage.py shell
将不会启动 web 容器所关联的其它容器。
2.18、scale
设置同一个服务运行的容器个数。
通过 service=num
的参数来设置数量。例如:
docker-compose scale web=2 worker=3
2.19、start
启动一个已经存在的服务容器。
2.20、stop
停止一个已经运行的容器,但不删除它。通过 docker-compose start
可以再次启动这些容器。
2.21、top
显示所有正在运行的进程。
2.22、up
构建,(重新)创建,启动,链接一个服务相关的容器。
链接的服务都将会启动,除非他们已经运行。
默认情况, docker-compose up
将会整合所有容器的输出,并且退出时,所有容器将会停止。
如果使用 docker-compose up -d
,将会在后台启动并运行所有的容器。
默认情况,如果该服务的容器已经存在, docker-compose up
将会停止并尝试重新创建他们(保持使用 volumes-from
挂载的卷),以保证 docker-compose.yml
的修改生效。如果你不想容器被停止并重新创建,可以使用 docker-compose up --no-recreate
。
如果需要的话,这样将会启动已经停止的容器。
2.23、unpause
启动暂停的服务容器。
2.24、version
显示 docker 信息。
五、环境变量
环境变量可以用来配置 Compose 的行为。
以 DOCKER_
开头的变量和用来配置 Docker 命令行客户端的使用一样。如果使用 boot2docker
, $(boot2docker shellinit)
将会设置它们为正确的值。
1、COMPOSE_PROJECT_NAME
设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
2、COMPOSE_FILE
设置要使用的 docker-compose.yml
的路径。默认路径是当前工作目录。
3、DOCKER_HOST
设置 Docker daemon
的地址。默认使用 unix:///var/run/docker.sock
,与 Docker 客户端采用的默认值一致。
4、DOCKER_TLS_VERIFY
如果设置不为空,则与 Docker daemon
交互通过 TLS 进行。
5、DOCKER_CERT_PATH
配置 TLS 通信所需要的验证(ca.pem、cert.pem 和 key.pem)文件的路径,默认是 ~/.docker
六、YAML 模板文件
默认的模板文件是 docker-compose.yml
,其中定义的每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)来自动构建。
其它大部分指令都跟 docker run
中的类似。
如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml
中再次设置。
1、image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉去这个镜像。
例如:
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
2、build
指定 Dockerfile 所在文件夹的路径。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。
build: /path/to/build/dir
3、command
覆盖容器启动后默认执行的命令。
command: bundle exec thin -p 3000
4、links
链接到其它服务中的容器。使用服务名称(同时作为别名)或服务名称:服务别名 (SERVICE:ALIAS) 格式都可以。
links:
- db
- db:database
- redis
使用的别名将会自动在服务容器中的 /etc/hosts
里创建。例如:
172.17.2.186 db
172.17.2.186 database
172.17.2.187 redis
相应的环境变量也将被创建。
5、external_links
链接到 docker-compose.yml
外部的容器,甚至并非 Compose 管理的容器。参数格式跟 links 类似。
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
6、ports
暴露端口信息。
使用宿主:容器 (HOST:CONTAINER)格式或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注:当使用 HOST:CONTAINER
格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。
7、expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数
expose:
- "3000"
- "8000"
8、volumes
卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER
) 或加上访问模式 (HOST:CONTAINER:ro
)。
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro
9、volumes_from
从另一个服务或容器挂载它的所有卷。
volumes_from:
- service_name
- container_name
10、environment
设置环境变量。你可以使用数组或字典两种格式。
只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。
environment:
RACK_ENV: development
SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
11、env_file
从文件中获取环境变量,可以为单独的文件路径或列表。
如果通过 docker-compose -f FILE
指定了模板文件,则 env_file 中路径会基于模板文件路径。
如果有变量名称与 environment 指令冲突,则以后者为准。
env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
环境变量文件中每一行必须符合格式,支持 # 开头的注释行。
# common.env: Set Rails/Rack environment
RACK_ENV=development
12、extends
基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml
。
# common.yml
webapp:
build: ./webapp
environment:
- DEBUG=false
- SEND_EMAILS=false
编写一个新的 development.yml
文件,使用 common.yml
中的 webapp 服务进行扩展。
# development.yml
web:
extends:
file: common.yml
service: webapp
ports:
- "8000:8000"
links:
- db
environment:
- DEBUG=true
db:
image: postgres
后者会自动继承 common.yml
中的 webapp 服务及相关环节变量。
12、net
设置网络模式。使用和 docker client 的 --net 参数一样的值。
net: "bridge"
net: "none"
net: "container:[name or id]"
net: "host"
13、pid
跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。
pid: "host"
14、dns
配置 DNS 服务器。可以是一个值,也可以是一个列表。
dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9
15、cap_add, cap_drop
添加或放弃容器的 Linux 能力(Capabiliity)。
cap_add:
- ALL
cap_drop:
- NET_ADMIN
- SYS_ADMIN
16、dns_search
配置 DNS 搜索域。可以是一个值,也可以是一个列表。
dns_search: example.com
dns_search:
- domain1.example.com
- domain2.example.com
17、working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares
这些都是和 docker run 支持的选项类似。
cpu_shares: 73
working_dir: /code
entrypoint: /code/entrypoint.sh
user: postgresql
hostname: foo
domainname: foo.com
mem_limit: 1000000000
privileged: true
restart: always
stdin_open: true
tty: true
七、compose 使用实例
1、管理一个 tomcat 容器
前提,需要准备好一个 tomcat image
下面,开始准备 ym 文件
mkdir tomcat8880
vi docker-compose.yml
tomcat:
image: tomcat7:v0.1
container_name: tomcat8880
restart: always
ports:
- 8880:8080
volumes:
- /data/container_ap/:/data/ap/
构建一个容器
cd tomcat8880
docker-compose up –d
启动完成后,可以看到该容器正在运行:
查看容器日志
停止该容器
启动该容器
2、管理一个java系统
该系统为本人在生产环境中的维护的一个系统,大致架构如下:
- 前端:apache+tomcat 通过 jk 的方式整合在一起,应用程序发布到 tomcat
- 中间层:memcached
- 数据库:oracle
现在,将该系统放到 docker 中运行:
- 容器1:apache、tomcat、应用程序
- 容器2:memcached
- 另一物理机:oracle
2.1、准备所需软件:
- 新建存放数据的目录
#mkdir –p /data/saba
- 将如下软件放至该目录
apache
tomcat(已整合完成)
jdk1.7.0_11
SabaWeb(应用系统)
2.2、准备 saba 启动脚本
cd /data/saba
vi run.sh
#!/bin/bash
#启用环境变量
source /root/.bash_profile
sleep 1
#启动apache
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/saba/nls/apache/lib
/saba/nls/apache/bin/httpd -f /saba/nls/apache/conf/httpd.conf
sleep 1
#启动tomcat
export LC_ALL=en_US.UTF-8
cd /saba/nls/tomcat/bin
sh startServer.sh start
2.3、准备 saba 镜像
mkdir –p /data/images/saba
vi Dockerfile
FROM centos:centos6
MAINTAINER 7d
RUN mkdir -p /saba/nls
RUN mkdir -p /home/ap/nls
RUN mkdir -p /saba/nls/contentserver/prod_content
RUN mkdir -p /saba/nls/contentserver/prod_assets
RUN echo 'export JAVA_HOME=/saba/nls/jdk1.7.0_11' >> /root/.bash_profile
RUN echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /root/.bash_profile
VOLUME ["/saba/nls"]
EXPOSE 80
EXPOSE 8080
docker build -t="saba:v0.1" .
得到镜像:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
saba v0.1 106dd7af0587 28 seconds ago 194.6 MB
2.4、准备 compose yml 文件
通过 compose 管理 memcached、saba7.2 两个容器(服务)
cd /data/compose-saba
vi docker-compose.yml
memcached:
image: memcached:v0.1
container_name: memcached
ports:
- "11211:11211"
restart: always
saba7.2:
image: saba:v0.1
container_name: saba7.2
ports:
- "8088:8080"
- "1080:80"
volumes:
- /data/saba:/saba/nls
links:
- memcached
command: /saba/nls/run.sh
2.5、启动服务
创建完 yml 配置文件后,开始启动服务器:
cd /data/compose-saba
docker-compose up –d
Creating memcached ...
Creating memcached ... done
Creating saba7.2 ...
Creating saba7.2 ... done
2.6、相关操作
启动完成后,即可进行如下操作(所有操作需要在 yml 文件所在目录下进行):
cd /data/compose-saba
查看启动的服务(容器):
查看容器的日志:
启停所有(或单个)容器
docker-compse stop|start
docker-compse stop|start memcached
通过宿主机连接到容器
docker-compose exec memcached /bin/bash
八、常见问题
1、Docker容器启动后退出
- 原因:docker 容器的主线程(dockfile 中 CMD 执行的命令)结束,容器会退出。
- 解决方案:
- 在脚本最后一行添加
tail -f /dev/null
,这个命令永远完成不了,所以该容器永远不会退出。 - 使用 supervisor 管理容器中程序。
- 在脚本最后一行添加
2、容器时间不同步
容器启动时加入如下参数:
-v /etc/localtime:/etc/localtime:ro
3、容器中找不到 vi、ifconfig 命令
进入容器后,执行如下命令:
apt-get update
apt-get install net-tools vim
Dockerfile 中也可加入该内容,其他命令找不到亦然。
其他常用工具:
install iputils-ping net-tools netcat telnet unzip lrzsz vim
4、容器不支持中文
- locale 查看当前语言环境
POSIX 不支持中文,C.UTF-8
支持中文 locale -a
查看支持的语言环境- 临时修改
echo 'LANG=C.UTF-8' >> /etc/profile
source /etc/profile
- 永久修改
Dockerfile 中加入:ENV LANG C.UTF-8
- 点赞
- 收藏
- 关注作者
评论(0)