相关链接:Docker Compose | 菜鸟教程
前面学习了一些Docker容器的构建和应用,实际项目中通常不止用到一个Docker,往往会有多个Docker组建网络,这个时候就需要用到Docker Compose了。
Docker Compose用于定义和运行多容器。通过docker-compose up
能够启动在docker-compose.yml
中定义的整个应用程序。
Compose的三个步骤
- 使用Dockerfile定义容器环境。
- 使用
docker-compose.yaml
定义构成应用的服务。
- 使用
docker-compose up
来启动服务。
示例配置
以下提供了一个docker-compose.yml
的示例配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| version: "3"
services:
redis: image: redis:alpine ports: - "6379" networks: - frontend db: image: postgres:9.4 volumes: - db-data:/var/lib/postgresql/data networks: - backend
vote: build: context: ./vote dockerfile: Dockerfile ports: - 5000:80 networks: - frontend depends_on: - redis
result: build: context: ./result dockerfile: Dockerfile ports: - 5001:80 networks: - backend depends_on: - db
worker: image: dockersamples/examplevotingapp_worker networks: - frontend - backend
visualizer: image: dockersamples/visualizer:stable ports: - "8080:8080" stop_grace_period: 1m30s volumes: - "/var/run/docker.sock:/var/run/docker.sock"
networks: frontend: backend:
volumes: db-data:
|
上面这个示例展示了compose的基础使用方法。我们拉取了Docker Hub的官方镜像作为我们服务中的基础镜像,并在images
选项中定义镜像的版本。除此之外,enviroment
,ports
都比较好理解,也就是在这个服务中对应容器对外拿的环境变量,以及端口。
利用自己构建的镜像定义compose
上面的例子中,我们的服务使用的是官方镜像,这种情况下我们无需写Dockerfile。而如果我们需要自己定义镜像,应该怎么处理呢?以下是一个示例:
定义应用
首先,我们定义一个应用,它基于Flask,使用Redis监听等待时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import time
import redis from flask import Flask
app = Flask(__name__) cache = redis.Redis(host='redis', port=6379)
def get_hit_count(): retries = 5 while True: try: return cache.incr('hits') except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5)
@app.route('/') def hello(): count = get_hit_count() return 'Hello World! I have been seen {} times.\n'.format(count)
|
随后我们需要写一个requirements.txt
。这里只要两个依赖项:flask和redis。
编写Dockerfile
下一步是写Dockerfile,我们以python 3.7作为基础镜像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
COPY requirements.txt requirements.txt RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]
|
编写Docker Compose
接下来定义docker-compose.yml
。我们在目录路径下新建文件,内容如下:
1 2 3 4 5 6 7 8 9 10
| version: '3' services: web: build: . ports: - "5000:5000" redis: image: "redis:alpine"
|
接下来就是docker-compose up
。这将会构建web镜像并拉取redis镜像(也可以先docker-compose build
再up
)。此时访问localhost:5000
即可观察到前面写的页面服务。
设置挂载路径
对应flask服务,能够实时修改代码并更新,这时可以把宿主机的代码挂载到容器内,这样就可以随时修改应用程序,而无需重新构建镜像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: '3' services: web: build: . ports: - "5000:5000" volumes: - .:/code environment: FLASK_ENV: development redis: image: "redis:alpine"
|
Docker Compose命令行
Docker Compose的命令行参数有以下几个常用参数:
docker compose
1 2 3 4 5 6 7
| docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
-f --file FILE指定Compose模板文件,默认为docker-compose.yml -p --project-name NAME 指定项目名称,默认使用当前所在目录为项目名 --verbose 输出更多调试信息 -v,-version 打印版本并退出 --log-level LEVEL 定义日志等级(DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
docker compose up
1 2 3 4 5 6 7 8 9 10 11 12
| docker-compose up [options] [--scale SERVICE=NUM...] [SERVICE...]
-d 在后台运行服务容器 -no-color 不是有颜色来区分不同的服务的控制输出 -no-deps 不启动服务所链接的容器 --force-recreate 强制重新创建容器,不能与-no-recreate同时使用 –no-recreate 如果容器已经存在,则不重新创建,不能与–force-recreate同时使用 –no-build 不自动构建缺失的服务镜像 –build 在启动容器前构建服务镜像 –abort-on-container-exit 停止所有容器,如果任何一个容器被停止,不能与-d同时使用 -t, –timeout TIMEOUT 停止容器时候的超时(默认为10秒) –remove-orphans 删除服务中没有在compose文件中定义的容器
|
docker compose build
1 2 3 4 5 6 7 8 9 10
| docker-compose build [options] [--build-arg key=val...] [SERVICE...] 构建(重新构建)项目中的服务容器。
–compress 通过gzip压缩构建上下环境 –force-rm 删除构建过程中的临时容器 –no-cache 构建镜像过程中不使用缓存 –pull 始终尝试通过拉取操作来获取更新版本的镜像 -m, –memory MEM为构建的容器设置内存大小 –build-arg key=val为服务设置build-time变量 服务容器一旦构建后,将会带上一个标记名。可以随时在项目目录下运行docker-compose build来重新构建服务
|
以上是几个常用的命令行指令,还有down
,start
等指令比较易懂,就不放这了。
Service配置项
docker-compose.yml
的大部分配置都聚焦在service
这一块,有很多参数需要了解:
build
指定 Dockerfile
所在文件夹的路径,Compose
将会利用它自动构建镜像。如:
1 2 3 4 5
| version: '3.8' services:
webapp: build: ./dir
|
context
可以使用 context
指定文件夹路径(可以是 Dockerfile 的目录路径,也可以是 git 存储库的 url),使用 dockerfile
指定 Dockerfile
文件名,使用 arg
为 Dockerfile
中的变量赋值。如:
1 2 3 4 5 6 7 8 9
| version: '3.8' services:
webapp: build: context: ./dir dockerfile: Dockerfile-alternate args: buildno: 1
|
如果在 build
同时指定了 image
,那么 Compose 会使用在 image
中指定的名字和标签来命名最终构建的镜像。如:
1 2
| build: ./dir image: webapp:tag
|
这将从 ./dir
构建,生成名为 webapp
,标签为:tag
的镜像。
build
参数下包括了以下参数:
- context:上下文路径。
- dockerfile:指定构建镜像的 Dockerfile 文件名。
- args:添加构建参数,这是只能在构建过程中访问的环境变量。
- labels:设置构建镜像的标签。
- target:多层构建,可以指定构建哪一层。
image
指定要从哪个镜像启动容器,以下是一个示例:
1 2 3 4 5 6
| version: "3.8" services: webserver: image: nginx:latest ports: - "80:80"
|
假如已经在本地有了一个自定义的镜像,也可以进行类似的操作:
1 2 3 4 5 6
| version: "3.8" services: myservice: image: myusername/myapp:1.0 ports: - "8080:80"
|
当需要使用docker compose build
时,docker compose.yml
文件中需要包含的是build
参数,这需要和images
区分开。
volumes
设置挂载路径,书写格式是宿主机文件路径:容器文件路径
:
1 2 3 4 5 6 7 8 9 10 11
| version: "3.8" services: db: image: postgres volumes: - db-data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: mysecretpassword
volumes: db-data:
|
restart
restart
是容器的重启策略,包括了:
no
:默认值,容器不会在退出时自动重启。
always
:无论容器因何种原因停止,都将尝试重启容器。
on-failure
:仅当容器非正常退出时(退出状态非零)重启容器。
unless-stopped
:除非容器被人为停止(例如通过 Docker 命令),否则在退出时总是重启。
1 2 3 4 5 6 7
| version: "3.8" services: web: image: nginx ports: - "80:80" restart: always
|
command
覆盖容器启动的默认命令:
1
| command: ["bundle", "exec", "thin", "-p", "3000"]
|
这样就不需要重写Dockerfile并构建新镜像。
container_name
指定容器名称。默认将会使用 项目名称_服务名称_序号
这样的格式,设置此项后可以自定义容器名:
1 2 3 4 5 6 7 8 9 10 11
| version: "3.8" services: webapp: image: node:14 container_name: webapp_dev ports: - "3000:3000" volumes: - .:/app working_dir: /app command: npm start
|
这样设置以后,就可以通过docker logs webapp_dev
或 docker exec -it webapp_dev /bin/bash
等命令来操作这个容器,而不是项目名_webapp_
这样类似的格式。
depends_on
指定服务之间的依赖关系,以便按顺序启动服务。以下例子中会先启动 redis
db
再启动 web
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: '3.8'
services: web: build: . depends_on: - db - redis
redis: image: redis
db: image: postgres
|
env_file
从文件中添加环境变量。可以是单个值或列表。
如果通过 docker-compose -f FILE
方式来指定了 Compose 文件,则 env_file
中变量的路径相对于文件所在目录。
在 environment
声明的变量,会覆盖这些值。即使这些值为空或未定义。
1 2 3 4 5 6
| env_file: .env
env_file: - ./common.env - ./apps/web.env - /opt/secrets.env
|
假设我们有一个.env
文件内容如下:
1 2 3
| DB_HOST=localhost DB_USER=myuser DB_PASS=mypassword
|
我们在docker compose
文件中定义:
1 2 3 4 5 6 7 8
| version: "3.8" services: webapp: image: my-webapp-image ports: - "5000:5000" env_file: - .env
|
当服务启动时,docker compose
会从配置文件中读取环境变量并设置在容器中。
environment
管理环境变量的另一种方式是使用environment
参数。它可以显式的在yaml文件中配置环境变量:
1 2 3 4 5 6 7 8 9 10
| version: "3.8" services: webapp: image: my-webapp-image ports: - "5000:5000" environment: - DB_HOST=localhost - DB_USER=myuser - DB_PASS=mypassword
|
使用env_file
可以更好地管理大量或敏感的环境变量,而environment
提供了直接和灵活的方式来设置少量或不敏感的环境变量。
secrets
存储敏感数据,例如 mysql
服务密码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: "3.8" services: redis: image: redis:latest deploy: replicas: 1 secrets: - my_secret - my_other_secret secrets: my_secret: file: ./my_secret.txt my_other_secret: external: true
|
network
通过network
参数设置不同容器之间的通信:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| version: "3.8" services: webapp: image: my-webapp-image networks: - frontend ports: - "5000:5000"
db: image: my-db-image networks: - backend
networks: frontend: backend:
|
上述例子中:
- webapp 服务只连接到 frontend 网络。
- db 服务只连接到 backend 网络。
通过这种方式,webapp 不能直接访问 db,除非将 db 也连接到 frontend 网络或者 webapp 连接到 backend 网络。
expose
在服务内部的网络里开放端口,这是让一个服务内的不同容器相互通信。它与ports
不同,并不对外开放端口。
假如网络中有前后端服务的容器服务,我们需要前后端的容器相互对接端口,我们需要编写以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| version: "3.8" services: frontend: image: frontend-image ports: - "5000:5000" networks: - app-network
backend: image: backend-image expose: - "4000" networks: - app-network
networks: app-network:
|
在这个配置中:
frontend
服务通过ports
指令将容器的 5000 端口映射到宿主机的 5000 端口,允许外部网络访问。
backend
服务使用expose
指令暴露 4000 端口,但这个端口只在内部app-network
网络中可见,外部网络无法直接访问。
frontend
和backend
都连接到了同一个app-network
网络,因此frontend
可以通过内部网络访问backend
的 4000 端口,进行必要的数据交互。
如果不设置networks
选项的话,会链接到默认的网络中。
2024/4/21 于苏州