docker compose 是 docker 官方编排(orchestration)项目之一,负责快速在集群中部署分布式应用。
本章将介绍 compose 项目情况以及安装和使用。
简介
compose 项目目前在 github 上进行维护。
compose 定位是“defining and running complex applications with docker”,前身是 fig,兼容 fig 的模板文件。
dockerfile 可以让用户管理一个单独的应用容器;而 compose 则允许用户在一个模板(yaml 格式)中定义一组相关联的应用容器(被称为一个 project,即项目),例如一个 web 服务容器再加上后端的数据库服务容器等。
该项目由 python 编写,实际上调用了 docker 提供的 api 来实现。
安装
安装 compose 之前,要先安装 docker,在此不再赘述。
2.1 pip 安装
这种方式最为推荐。
执行命令。
$ sudo pip install -u docker-compose
安装成功后,可以查看 docker-compose 命令的用法。
$ docker-compose -h
fast, isolated development environments using docker.
usage:
docker-compose [options] [command] [args...]
docker-compose -h|--help
options:
--verbose show more output
--version print version and exit
-f, --file file specify an alternate compose file (default: docker-compose.yml)
-p, --project-name name specify an alternate project name (default: directory name)
commands:
build build or rebuild services
help get help on a command
kill kill containers
logs view output from containers
port print the public port for a port binding
ps list containers
pull pulls service images
rm remove stopped containers
run run a one-off command
scale set number of containers for a service
start start services
stop stop services
restart restart services
up create and start containers
之后,可以添加 bash 补全命令。
$ curl -l https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
2.2 二进制包
发布的二进制包可以在 https://github.com/docker/compose/releases 找到。
下载后直接放到执行路径即可。
例如,在常见的 linux 平台上。
$ sudo curl -l https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ sudo chmod a x /usr/local/bin/docker-compose
3、使用
3.1 术语
首先介绍几个术语。
服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。
项目(project):由一组关联的应用容器组成的一个完整业务单元。
可见,一个项目可以由多个服务(容器)关联而成,compose 面向项目进行管理。
3.2 场景
下面,我们创建一个经典的 web 项目:一个 haproxy,挂载三个 web 容器。
创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录:haproxy 和web。
3.3 web 子目录
这里用 python 程序来提供一个简单的 http 服务,打印出访问者的 ip 和 实际的本地 ip。
3.3.1 index.py
编写一个 index.py 作为服务器文件,代码为
#!/usr/bin/python
#authors: yeasy.github.com
#date: 2013-07-05
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(" ");
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("#" str(request[pair][1]) ": " str(request[pair][0]) " requests " "from <" guest "> to webserver <" pair[1] ">
")
else:
file.write("#" str(request[pair][1]) ": " str(request[pair][0]) " requests " "from <" guest "> to webserver <" pair[1] ">
")
file.write(" ");
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.3.2 index.html
生成一个临时的 index.html 文件,其内容会被 index.py 更新
$ touch index.html
3.3.3 dockerfile
生成一个 dockerfile,内容为
from python:2.7
workdir /code
add . /code
expose 80
cmd python index.py
3.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
3.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"
3.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 容器了,更为灵活。
4、compose 命令说明
大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。
执行 docker-compose [command] --help
查看具体某个命令的使用说明。
基本的使用格式是
docker-compose [options] [command] [args...]
4.1 选项
--verbose
输出更多调试信息。--version
打印版本并退出。-f, --file file
使用特定的 compose 模板文件,默认为docker-compose.yml
。-p, --project-name name
指定项目名称,默认使用目录名称。
4.2 命令
build
构建或重新构建服务。
服务一旦构建后,将会带上一个标记名,例如 web_db。
可以随时在项目目录下运行 docker-compose build
来重新构建服务。
help
获得一个命令的帮助。
kill
通过发送 sigkill
信号来强制停止服务容器。支持通过参数来指定发送的信号,例如
$ docker-compose kill -s sigint
logs
查看服务的输出。
port
打印绑定的公共端口。
ps
列出所有容器。
pull
拉取服务镜像。
rm
删除停止的服务容器。
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 容器所关联的其它容器。
scale
设置同一个服务运行的容器个数。
通过 service=num
的参数来设置数量。例如:
$ docker-compose scale web=2 worker=3
start
启动一个已经存在的服务容器。
stop
停止一个已经运行的容器,但不删除它。通过 docker-compose start
可以再次启动这些容器。
up
构建,(重新)创建,启动,链接一个服务相关的容器。
链接的服务都将会启动,除非他们已经运行。
默认情况, docker-compose up
将会整合所有容器的输出,并且退出时,所有容器将会停止。
如果使用 docker-compose up -d
,将会在后台启动并运行所有的容器。
默认情况,如果该服务的容器已经存在, docker-compose up
将会停止并尝试重新创建他们(保持使用volumes-from
挂载的卷),以保证 docker-compose.yml
的修改生效。如果你不想容器被停止并重新创建,可以使用 docker-compose up --no-recreate
。如果需要的话,这样将会启动已经停止的容器。
4.3 环境变量
环境变量可以用来配置 compose 的行为。
以docker_
开头的变量和用来配置 docker 命令行客户端的使用一样。如果使用 boot2docker , $(boot2docker shellinit)
将会设置它们为正确的值。
compose_project_name
设置通过 compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
compose_file
设置要使用的 docker-compose.yml
的路径。默认路径是当前工作目录。
docker_host
设置 docker daemon 的地址。默认使用 unix:///var/run/docker.sock
,与 docker 客户端采用的默认值一致。
docker_tls_verify
如果设置不为空,则与 docker daemon 交互通过 tls 进行。
docker_cert_path
配置 tls 通信所需要的验证(ca.pem
、cert.pem
和 key.pem
)文件的路径,默认是 ~/.docker
。
5、yaml 模板文件
默认的模板文件是 docker-compose.yml
,其中定义的每个服务都必须通过 image
指令指定镜像或 build
指令(需要 dockerfile)来自动构建。
其它大部分指令都跟 docker run
中的类似。
如果使用 build
指令,在 dockerfile
中设置的选项(例如:cmd
, expose
, volume
, env
等) 将会自动被获取,无需在 docker-compose.yml
中再次设置。
image
指定为镜像名称或镜像 id。如果镜像在本地不存在,compose
将会尝试拉去这个镜像。
例如:
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
build
指定 dockerfile
所在文件夹的路径。 compose
将会利用它自动构建这个镜像,然后使用这个镜像。
build: /path/to/build/dir
command
覆盖容器启动后默认执行的命令。
command: bundle exec thin -p 3000
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
相应的环境变量也将被创建。
external_links
链接到 docker-compose.yml 外部的容器,甚至 并非 compose
管理的容器。参数格式跟 links
类似。
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
ports
暴露端口信息。
使用宿主:容器 (host:container)
格式或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注:当使用 host:container
格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 yaml
将会解析 xx:yy
这种数字格式为 60 进制。所以建议采用字符串格式。
expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数
expose:
- "3000"
- "8000"
volumes
卷挂载路径设置。可以设置宿主机路径 (host:container
) 或加上访问模式 (host:container:ro
)。
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro
volumes_from
从另一个服务或容器挂载它的所有卷。
volumes_from:
- service_name
- container_name
environment
设置环境变量。你可以使用数组或字典两种格式。
只给定名称的变量会自动获取它在 compose 主机上的值,可以用来防止泄露不必要的数据。
environment:
rack_env: development
session_secret:
environment:
- rack_env=development
- session_secret
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
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 服务及相关环节变量。
net
设置网络模式。使用和 docker client
的 --net
参数一样的值。
net: "bridge"
net: "none"
net: "container:[name or id]"
net: "host"
pid
跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 id 来访问和操作。
pid: "host"
dns
配置 dns 服务器。可以是一个值,也可以是一个列表。
dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9
cap_add, cap_drop
添加或放弃容器的 linux 能力(capabiliity)。
cap_add:
- all
cap_drop:
- net_admin
- sys_admin
dns_search
配置 dns 搜索域。可以是一个值,也可以是一个列表。
dns_search: example.com
dns_search:
- domain1.example.com
- domain2.example.com
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