Docker Compose官方文档:https://docs.docker.com/compose/
编排(orchestration),指自动配置、协作和管理服务的过程,在 Docker 中,编排用来描述一组实践过程,这个过程会管理运行在多个 Docker 里的应用,这些 Docker 容器也可能运行在不同的宿主机上。Docker Compose 由 Python 编写。使用 Docker Compose ,可以用一个 YAML 文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose 称这些容器为“服务”:
容器通过某些方法并制定一些运行时的属性来和其他容器产生交互。
安装 Docker Compose
Docker Compose 目前可以安装在 Linux、Windows 和 OS X上。可以从 Github 直接下载可执行安装包,也可以通过 pip 安装 pip install docker-compose, 这里采用前者: 从 Github 下载 docker-compose 可执行程序并到 /usr/local/bin 目录中,并赋予可执行权限。 sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-(uname−s)−
(uname -m) -o /usr/local/bin/docker-compose chmod x /usr/local/bin/docker-compose 测试 Docker Compose 是否工作 docker-compose -v docker-compose version 1.8.1, build 878cff1
Docker Compose 应用示例
这里通过一个 Python Flask 应用来演示 Docker Compose 的使用,需要用到两个容器:
应用容器:运行 Python 示例程序 Redis容器:运行 Redis 数据库
1.创建项目目录 mkdir composeapp cd composeapp 2.创建 Python Flask 应用 app.py
from flask import Flask from redis import Redis
app = Flask(name) redis = Redis(host=’redis’, port=6379)
@app.route(‘/’) def hello(): redis.incr(‘hits’) return ‘Hello World! I have been seen %s times.’ % redis.get(‘hits’)
if name == “main“: app.run(host=”0.0.0.0”, debug=True) 这个简单的 Flask 应用追踪保存在 Redis 里的计数器。每次访问根路径/时,计数器将自增。 3.创建 requirements.txt 保存程序依赖 flask redis 4.创建 Docker image 这一步骤创建 Docker image,该镜像包含Python应用程序需要的所有依赖关系,包括Python本身。 在项目中创建 Dockerfile 文件如下: FROM python:2.7 MAINTAINER Loya Chen qingkang1993@163.com ADD . /composeapp WORKDIR /composeapp RUN pip install -r requirements.txt -i https://pypi.douban.com/simple CMD python app.py 构建镜像 docker build -t web . 这里通过 Dockerfile 的方式构建了 web 镜像,Redis 容器的镜像则直接从 Docker Hub 拉取(我这里提前pull下来了)。 docker pull redis 5.通过 docker-compose.yml 定义服务 在项目目录下创建 docker-compose.yml 文件来定义一组服务。docker-compose.yml 是 YAML 格式的文件,每个要启用的服务都使用一个 YAML 的散列键定义,服务以 Docker 容器的形式表现,同时定义服务启动时的运行属性。
代码语言:javascript复制version: ‘2’
services:
web:
image: web
ports:
- “5000:5000”
volumes:
- .:/composeapp
depends_on:
- redis
redis:
image: redis
这个 Compose 文件定义了两组服务, web 和 redis。web 服务: 指定使用的镜像 web web容器内的 5000 端口映射到主机的 5000 端口。 将当前项目目录挂载到容器的 /composeapp 目录,一遍无需重新构建镜像即可修改代码。 将 web 服务连接至 Redis 服务。 Redis 服务直接从 Docker Hub 拉取最新的 Redis 镜像使用,这个镜像默认会在标准端口上启动一个 Redis 数据库。 Compose 可以像 web 服务中那样指定要使用的镜像,也可以构建 Docker 镜像,使用 build 指令,并提供 Dockerfile 所在路径,例如使用当前目录下的 Dockerfile 构建镜像并使用:
web: build: . 如果使用相同的配置,在命令行中使用 docker run 执行服务,需要执行以下命令: docker run -d -p 5000:5000 -v .:/composeapp –link redis:redis –name web 6.运行 Compose docker-compose up 在项目目录中,通过 docker-compose up启动应用 docker-compose up
docker-compose up
Creating composeapp_redis_1
Creating composeapp_web_1
Attaching to composeapp_redis_1, composeapp_web_1
redis_1 | 1:C 30 Oct 09:17:47.220 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 | .
redis_1 | _.-__ ''-._
redis_1 | _.-
.
. ”-. Redis 3.2.4 (00000000/0) 64 bit
redis_1 | .-.-```. ```/ _.,_ ''-._
redis_1 | ( ' , .-` | `, ) Running in standalone mode
redis_1 | |`-._`-...-` __...-.
-._|’_.-'| Port: 6379
redis_1 | |
-._ ._ / _.-' | PID: 1
redis_1 |
-._ -._
-./ .-’ .-’
redis_1 | |-._
-._ -.__.-' _.-'_.-'|
redis_1 | |
-._-._ _.-'_.-' | http://redis.io
redis_1 |
-._ -._
-._.-‘.-’ _.-’
redis_1 | |-._
-._ -.__.-' _.-'_.-'|
redis_1 | |
-._-._ _.-'_.-' |
redis_1 |
-._ -._
-._.-‘.-’ _.-’
redis_1 | -._
-._.-’ .-’
redis_1 | -._ _.-'
redis_1 |
-.__.-’
redis_1 |
redis_1 | 1:M 30 Oct 09:17:47.222 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1 | 1:M 30 Oct 09:17:47.222 # Server started, Redis version 3.2.4
redis_1 | 1:M 30 Oct 09:17:47.222 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.
redis_1 | 1:M 30 Oct 09:17:47.222 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command ‘echo never > /sys/kernel/mm/transparent_hugepage/enabled’ as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
redis_1 | 1:M 30 Oct 09:17:47.222 * The server is now ready to accept connections on port 6379
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL C to quit)
web_1 | * Restarting with stat
web_1 | * Debugger is active!
web_1 | * Debugger pin code: 147-498-872
大多数的 Compose 命令必须在 docker-compose.yml 文件所在目录下执行
可以看到 Compose 创建了 composeapp_redis_1 和 composeapp_web_1 两个服务。为保证服务唯一,Compose 将 docker-compose.yml 文件中指定的服务名字加上目录作为前缀,并分别使用数字作为后缀。
Compose 接管了每个服务输出的日志,输出的日志每一行都使用缩短的服务名作为前缀,并交替输出在一起。
redis_1 | 1:M 30 Oct 09:17:47.222 # Server started, Redis version 3.2.4
docker-compose up -d
使用 Ctrl C 停止 Compose 的同时,也会停止运行的服务,可以在运行 Compose 时执行 -d ,以守护进程的模式来运行服务(类似于 docker run -d )
docker-compose up -d
Creating composeapp_redis_1 Creating composeapp_web_1 验证服务
curl http://127.0.0.1:5000
Hello World! I have been seen 1 times.
每次请求,Redis中保存的计数器就会加1 7.Compose其他命令 docker-compose ps 列出本地 docker-compose.yml 文件定义的正在运行的所有服务,查看服务运行状态
docker-compose ps
Name Command State Ports
composeapp_redis_1 docker-entrypoint.sh redis … Up 6379/tcp composeapp_web_1 /bin/sh -c python app.py Up 0.0.0.0:5000->5000/tcp docker-compose logs 显示 Docker Compose 服务日志
docker-compose logs
Attaching to composeapp_web_1, composeapp_redis_1 web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL C to quit) web_1 | * Restarting with stat web_1 | * Debugger is active! web_1 | * Debugger pin code: 147-498-872 docker-compose stop 停止正在运行的服务
docker-compose stop
Stopping composeapp_web_1 … done Stopping composeapp_redis_1 … done 该命令会同时停止两个服务,如果服务没有停止,可以使用docker-compose kill强制杀死 docker-compose start 如果使用docker-compose stop或docker-compose kill停止服务,还可以使用docker-compose重启这些服务。 docker-compose rm 删除 Docker Compose 服务,两个服务均会被删除
docker-compose rm
Going to remove composeapp_web_1, composeapp_redis_1 Are you sure? [yN] y Removing composeapp_web_1 … done Removing composeapp_redis_1 … done 以上就是 Docker Compose的简单使用,更多内容可以参考官网:http://www.docker.com/products/docker-compose
docker compose文件详解
先来看一份 docker-compose.yml 文件,不用管这是干嘛的,只是有个格式方便后文解说:
代码语言:javascript复制version: '2'
services:
web:
image: dockercloud/hello-world
ports:
- 8080
networks:
- front-tier
- back-tier
redis:
image: redis
links:
- web
networks:
- back-tier
lb:
image: dockercloud/haproxy
ports:
- 80:80
links:
- web
networks:
- front-tier
- back-tier
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
front-tier:
driver: bridge
back-tier:
driver: bridge
可以看到一份标准配置文件应该包含 version、services、networks 三大部分,其中最关键的就是 services 和 networks 两个部分,下面先来看 services 的书写规则。
1. image
代码语言:javascript复制services:
web:
image: hello-world
在 services 标签下的第二级标签是 web,这个名字是用户自己自定义,它就是服务名称。 image 则是指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。 例如下面这些格式都是可以的:
代码语言:javascript复制image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd
2. build
服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。
代码语言:javascript复制build: /path/to/build/dir
也可以是相对路径,只要上下文确定就可以读取到 Dockerfile。
代码语言:javascript复制build: ./dir
设定上下文根目录,然后以该目录为准指定 Dockerfile。
代码语言:javascript复制build:
context: ../
dockerfile: path/of/Dockerfile
注意 build 都是一个目录,如果你要指定 Dockerfile 文件需要在 build 标签的子级标签中使用 dockerfile 标签指定,如上面的例子。 如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。
代码语言:javascript复制build: ./dir
image: webapp:tag
既然可以在 docker-compose.yml 中定义构建任务,那么一定少不了 arg 这个标签,就像 Dockerfile 中的 ARG 指令,它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:
代码语言:javascript复制build:
context: .
args:
buildno: 1
password: secret
下面这种写法也是支持的,一般来说下面的写法更适合阅读。
代码语言:javascript复制build:
context: .
args:
- buildno=1
- password=secret
与 ENV 不同的是,ARG 是允许空值的。例如:
代码语言:javascript复制args:
- buildno
- password
这样构建过程可以向它们赋值。
注意:YAML 的布尔值(true, false, yes, no, on, off)必须要使用引号引起来(单引号、双引号均可),否则会当成字符串解析。
3. command
使用 command 可以覆盖容器启动后默认执行的命令。
代码语言:javascript复制command: bundle exec thin -p 3000
也可以写成类似 Dockerfile 中的格式:
代码语言:javascript复制command: [bundle, exec, thin, -p, 3000]
4.container_name
前面说过 Compose 的容器名称格式是:<项目名称><服务名称><序号> 虽然可以自定义项目名称、服务名称,但是如果你想完全控制容器的命名,可以使用这个标签指定:
代码语言:javascript复制container_name: app
这样容器的名字就指定为 app 了。
5.depends_on
在使用 Compose 时,最大的好处就是少打启动命令,但是一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。 例如在没启动数据库容器的时候启动了应用容器,这时候应用容器会因为找不到数据库而退出,为了避免这种情况我们需要加入一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。 例如下面容器会先启动 redis 和 db 两个服务,最后才启动 web 服务:
代码语言:javascript复制version: '2'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
注意的是,默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redis 和 db 两个服务,因为在配置文件中定义了依赖关系。
6.dns
和 --dns 参数一样用途,格式如下:
代码语言:javascript复制dns: 8.8.8.8
也可以是一个列表:
代码语言:javascript复制dns:
- 8.8.8.8
- 9.9.9.9
此外 dns_search 的配置也类似:
代码语言:javascript复制dns_search: example.com
dns_search:
- dc1.example.com
- dc2.example.com
7. tmpfs
挂载临时目录到容器内部,与 run 的参数一样效果:
代码语言:javascript复制tmpfs: /run
tmpfs:
- /run
- /tmp
8. entrypoint
在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点,第四章有对比过与 CMD 的区别。 在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义:
代码语言:javascript复制entrypoint: /code/entrypoint.sh
格式和 Docker 类似,不过还可以写成这样:
代码语言:javascript复制entrypoint:
- php
- -d
- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory_limit=-1
- vendor/bin/phpunit
9.env_file
还记得前面提到的 .env 文件吧,这个文件可以设置 Compose 的变量。而在 docker-compose.yml 中可以定义一个专门存放变量的文件。 如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中路径会使用配置文件路径。
如果有变量名称与 environment 指令冲突,则以后者为准。格式如下:
代码语言:javascript复制env_file: .env
或者根据 docker-compose.yml 设置多个:
代码语言:javascript复制env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
注意的是这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中使用变量还是首选前面刚讲的 arg 标签。
10. environment
与上面的 env_file 标签完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同。 一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果。
代码语言:javascript复制environment:
RACK_ENV: development
SHOW: 'true'
SESSION_SECRET:
environment:
- RACK_ENV=development
- SHOW=true
- SESSION_SECRET
11. expose
这个标签与Dockerfile中的EXPOSE指令一样,用于指定暴露的端口,但是只是作为一种参考,实际上docker-compose.yml的端口映射还得ports这样的标签。
代码语言:javascript复制expose:
- "3000"
- "8000"
12. external_links
在使用Docker过程中,我们会有许多单独使用docker run启动的容器,为了使Compose能够连接这些不在docker-compose.yml中定义的容器,我们需要一个特殊的标签,就是external_links,它可以让Compose项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。 格式如下:
代码语言:javascript复制external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
13. extra_hosts
添加主机名的标签,就是往/etc/hosts文件中添加一些记录,与Docker client的--add-host类似:
代码语言:javascript复制extra_hosts:
- "somehost:162.242.195.82"
- "otherhost:50.31.209.229"
启动之后查看容器内部hosts:
代码语言:javascript复制162.242.195.82 somehost
50.31.209.229 otherhost
14. labels
向容器添加元数据,和Dockerfile的LABEL指令一个意思,格式如下:
代码语言:javascript复制labels:
com.example.description: "Accounting webapp"
com.example.department: "Finance"
com.example.label-with-empty-value: ""
labels:
- "com.example.description=Accounting webapp"
- "com.example.department=Finance"
- "com.example.label-with-empty-value"
15. links
还记得上面的depends_on吧,那个标签解决的是启动顺序问题,这个标签解决的是容器连接问题,与Docker client的--link一样效果,会连接到其它服务中的容器。 格式如下:
代码语言:javascript复制links:
- db
- db:database
- redis
使用的别名将会自动在服务容器中的/etc/hosts里创建。例如:
代码语言:javascript复制172.12.2.186 db
172.12.2.186 database
172.12.2.187 redis
相应的环境变量也将被创建。
16. logging
这个标签用于配置日志服务。格式如下:
代码语言:javascript复制logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
默认的driver是json-file。只有json-file和journald可以通过docker-compose logs显示日志,其他方式有其他日志查看方式,但目前Compose不支持。对于可选值可以使用options指定。 有关更多这方面的信息可以阅读官方文档: https://docs.docker.com/engine/admin/logging/overview/
17. pid
代码语言:javascript复制pid: "host"
将PID模式设置为主机PID模式,跟主机系统共享进程命名空间。容器使用这个标签将能够访问和操纵其他容器和宿主机的名称空间。
18. ports
映射端口的标签。 使用HOST:CONTAINER格式或者只是指定容器的端口,宿主机会随机映射端口。
代码语言:javascript复制ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注意:当使用HOST:CONTAINER格式来映射端口时,如果你使用的容器端口小于60你可能会得到错误得结果,因为YAML将会解析xx:yy这种数字格式为60进制。所以建议采用字符串格式。
19. security_opt
为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签。比如设置全部服务的user标签值为USER。
代码语言:javascript复制security_opt:
- label:user:USER
- label:role:ROLE
20. stop_signal
设置另一个信号来停止容器。在默认情况下使用的是SIGTERM停止容器。设置另一个信号可以使用stop_signal标签。
代码语言:javascript复制stop_signal: SIGUSR1
21. volumes
挂载一个目录或者一个已存在的数据卷容器,可以直接使用 [HOST:CONTAINER] 这样的格式,或者使用 [HOST:CONTAINER:ro] 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。 Compose的数据卷指定路径可以是相对路径,使用 . 或者 .. 来指定相对目录。 数据卷的格式可以是下面多种形式:
代码语言:javascript复制volumes:
// 只是指定一个路径,Docker 会自动在创建一个数据卷(这个路径是容器内部的)。
- /var/lib/mysql
// 使用绝对路径挂载数据卷
- /opt/data:/var/lib/mysql
// 以 Compose 配置文件为中心的相对路径作为数据卷挂载到容器。
- ./cache:/tmp/cache
// 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
- ~/configs:/etc/configs/:ro
// 已经存在的命名的数据卷。
- datavolume:/var/lib/mysql
如果你不使用宿主机的路径,你可以指定一个volume_driver。
代码语言:javascript复制volume_driver: mydriver
22. volumes_from
从其它容器或者服务挂载数据卷,可选的参数是 :ro或者 :rw,前者表示容器只读,后者表示容器对数据卷是可读可写的。默认情况下是可读可写的。
代码语言:javascript复制volumes_from:
- service_name
- service_name:ro
- container:container_name
- container:container_name:rw
23. cap_add, cap_drop
添加或删除容器的内核功能。详细信息在前面容器章节有讲解,此处不再赘述。
代码语言:javascript复制cap_add:
- ALL
cap_drop:
- NET_ADMIN
- SYS_ADMIN
24. cgroup_parent
指定一个容器的父级cgroup。
代码语言:javascript复制cgroup_parent: m-executor-abcd
25. devices
设备映射列表。与Docker client的--device参数类似。
代码语言:javascript复制devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
26. extends
这个标签可以扩展另一个服务,扩展内容可以是来自在当前文件,也可以是来自其他文件,相同服务的情况下,后来者会有选择地覆盖原有配置。
代码语言:javascript复制extends:
file: common.yml
service: webapp
用户可以在任何地方使用这个标签,只要标签内容包含file和service两个值就可以了。file的值可以是相对或者绝对路径,如果不指定file的值,那么Compose会读取当前YML文件的信息。 更多的操作细节在后面的12.3.4小节有介绍。
27. network_mode
网络模式,与Docker client的--net参数类似,只是相对多了一个service:[service name] 的格式。 例如:
代码语言:javascript复制network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
可以指定使用服务或者容器的网络。
28. networks
加入指定网络,格式如下:
代码语言:javascript复制services:
some-service:
networks:
- some-network
- other-network
关于这个标签还有一个特别的子标签aliases,这是一个用来设置服务别名的标签,例如:
代码语言:javascript复制services:
some-service:
networks:
some-network:
aliases:
- alias1
- alias3
other-network:
aliases:
- alias2
相同的服务可以在不同的网络有不同的别名。
29. 其它
还有这些标签:cpu_shares, cpu_quota, cpuset, domainname, hostname, ipc, mac_address, mem_limit, memswap_limit, privileged, read_only, restart, shm_size, stdin_open, tty, user, working_dir 上面这些都是一个单值的标签,类似于使用docker run的效果。
代码语言:javascript复制cpu_shares: 73
cpu_quota: 50000
cpuset: 0,1
user: postgresql
working_dir: /code
domainname: foo.com
hostname: foo
ipc: host
mac_address: 02:42:ac:11:65:43
mem_limit: 1000000000
memswap_limit: 2000000000
privileged: true
restart: always
read_only: true
shm_size: 64M
stdin_open: true
tty: true