Docker Compose:多容器时代的高效编排利器

序言

  在容器化技术迅速普及的今天,应用架构早已从单体走向微服务,随之而来的,是数量不断增长的容器实例与更加复杂的部署组合。对于开发和运维团队而言,单靠手动方式去创建、启动、管理这些容器,已经变成一件低效且极易出错的工作。

  Docker Compose 正是在这样的背景下诞生。它以一个简洁的 YAML 文件,将复杂的多容器应用抽象成“可定义、可复用、可一键启动”的工程,使开发者能够在本地快速搭建完整的服务环境,让运维人员轻松管理多实例部署。通过它,我们可以避免重复繁琐的命令操作,更能像管理一个整体项目一样描述和控制成百上千个容器。

  本篇文章将从 Compose 的基础概念、安装方式、常用命令到网络机制、外部容器链接方式等多个角度进行全面梳理,让你在理解 Compose 核心理念的同时,也能掌握实际使用中的细节与技巧,为构建稳定、灵活的容器化环境提供坚实基础。

简介

  Docker Compose 是用于定义和运行多容器的云原生应用程序工具。

为什么使用 Docker Compose ?

  在前文使用 Docker 容器时,我们需要定义并 build dockerfile 文件,然后启动运行它(不同容器可能还需要配置相关参数)。
  但是,我们的系统一般都包含上百的服务,而每个服务又有多个实例,若全部采用手动方式来启动关闭的话,工作量之大可想而知。

  因此,为了把程序员从这些繁杂的工作当中解脱出来,Docker Compose 便应运而生!

现状与作用

  目前,Docker Compose 已被数百万的开发人员所使用,并在 GitHub 上拥有超过 650,000 个文件,由于它定义了一种基于多容器应用程序简单的与云和编排器无关的方式,因此被开发人员广泛接受。
  Docker Compose 通过允许开发人员在单个文件中定义一个复杂的堆栈并使用单个命令运行它,极大地简化了开发人员到云程序和工具链的代码。
  这样,便无需手动构建和启动每个容器,从而节省了开发团队的宝贵时间

下载安装

  不论你通过 Windows 还是 Mac 安装完 Docker Desktop ,都会默认自动安装好 docker-compose。

  Linux 则需要额外安装,可以通过 docker-compose 下载地址下载,之后执行以下命令即可:

1
2
3
4
sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 检测是否安装成功
docker-compose version

基本概念

  Docker Compose 允许用户通过一个单独的docker- compose.yml模板文件来定义一组相关联的应用容器为一个项目。
  Docker Compose 有两个重要的概念:

  • 服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器
  • 实例项目(project):由一组关联的应用容器组成的一个完整业务单元

  Docker Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
  Docker Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容 器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Docker Compose 来进行编排管理。

基本使用

  Docker Compose 的使用步骤如下:

  • 根据容器相关参数来编写docker-compose.yml文件
  • 执行docker-compose.yml文件

  下面以 MySQL 容器为例:
  首先,根据 MySQL 相关参数来编写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
version: '3.1'

services:
db: # 根据以下配置创建一个容器,名称为 image_db_num
image: mysql # 拉取最新镜像
command: # 配置基础参数
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
restart: always # 宕机总是重启
environment: # 环境配置:下面为 root 初识密码
MYSQL_ROOT_PASSWORD: 123456
ports: # 端口映射
- 3309:3306
volumes: # 目录挂载
- ./data:/var/lib/mysql

adminer: # 根据以下配置创建一个容器,名称为 image_adminer_num
image: adminer
restart: always
ports:
- 8091:8080

注意哦:vim 下输入:set paste则粘贴不会出现格式错误

  之后就可以执行如下命令来构建容器啦:

1
docker-compose up -d

  该命令默认会根据当前目录下的docker-compose.yml文件来构建容器。

Docker Compose 命令

  以下命令前缀均为docker-compose(如docker-compose ps):

命令 说明
docker-compose images [options] 列出所有服务所使用的镜像信息
docker-compose ps 显示所有容器
docker-compose up -d tomcat 构建并启动 tomcat 容器
docker-compose exec -it tomcat /bin/bash 以交互式方式进入 tomcat 容器
docker-compose start tomcat 启动 tomcat 容器
docker-compose restart tomcat 重新启动 tomcat 容器
docker-compose stop tomcat 停止 tomcat 容器
docker-compose rm tomcat 删除容器(删除前必须关闭容器)
docker-compose down 删除所有 tomcat 容器及镜像
docker-compose build tomcat 构建镜像
docker-compose logs tomcat 查看 tomcat 的日志
docker-compose pause tomcat 暂停 tomcat 容器
docker-compose unpause tomcat 恢复 tomcat 容器

docker-compose -f

默认情况下,Docker Compose 会查找当前目录下的 docker-compose.yml 文件。如果需要使用其他文件名或路径的 Compose 文件,就需要使用 -f 选项来指定。格式如下:

1
docker-compose -f <file_path> [command]
  • <file_path>:指定自定义的 Compose 文件路径。
  • [command]:执行的 Docker Compose 命令,例如上面表格中的 updownps 等命令。

实战案例

部署 Nginx

  docker-compose.yaml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
services:
nginx:
image: nginx:stable-alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
volumes:
- ./logs:/var/log/nginx
- ./front:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf

  注意,需要提前创建nginx.conf文件:

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
user  nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;


events {
worker_connections 1024;
}


http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;
# 注意下面的注释掉,不然可能和匹配规则冲突
#include /etc/nginx/conf.d/*.conf;

server {
listen 80;
server_name localhost;

location /app1/ {
alias /usr/share/nginx/html/app1/;
try_files $uri $uri/ /app1/index.html;
index index.html index.htm;
}

location /app2/ {
alias /usr/share/nginx/html/app2/;
try_files $uri $uri/ /app2/index.html;
index index.html index.htm;
}

location /prod-api/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://xxx:8080/;
}

# 避免actuator暴露
if ($uri ~ "/actuator") {
return 403;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

  Nginx 映射多个前端服务时,如 app1、app2,前端打包时需要增加配置,比如部署在/app1/前缀下,需要根据前端技术栈修改构建配置:

  • Vue.js: vue.config.js → publicPath: '/app1/'
  • React (CRA): package.json → "homepage": "/app1/"

参数详解

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
Compose和Docker兼容性:
Compose 文件格式有3个版本,分别为1, 2.x 3.x
目前主流的为 3.x 其支持 docker 1.13.0 及其以上的版本

常用参数:
version # 指定 compose 文件的版本,高版本不再需要此参数
services # 定义所有的 service 信息, services 下面的第一级别的 key 既是一个 service 的名称

build # 指定包含构建上下文的路径, 或作为一个对象,该对象具有 context 和指定的 dockerfile 文件以及 args 参数值
context # context: 指定 Dockerfile 文件所在的路径
dockerfile # dockerfile: 指定 context 指定的目录下面的 Dockerfile 的名称(默认为 Dockerfile)
args # args: Dockerfile 在 build 过程中需要的参数 (等同于 docker container build --build-arg 的作用)
cache_from # v3.2中新增的参数, 指定缓存的镜像列表 (等同于 docker container build --cache_from 的作用)
labels # v3.3中新增的参数, 设置镜像的元数据 (等同于 docker container build --labels 的作用)
shm_size # v3.5中新增的参数, 设置容器 /dev/shm 分区的大小 (等同于 docker container build --shm-size 的作用)

command # 覆盖容器启动后默认执行的命令, 支持 shell 格式和 [] 格式

container_name # 指定容器的名称 (等同于 docker run --name 的作用)

deploy # v3 版本以上, 指定与部署和运行服务相关的配置, deploy 部分是 docker stack 使用的, docker stack 依赖 docker swarm
endpoint_mode # v3.3 版本中新增的功能, 指定服务暴露的方式
vip # Docker 为该服务分配了一个虚拟 IP(VIP), 作为客户端的访问服务的地址
dnsrr # DNS轮询, Docker 为该服务设置 DNS 条目, 使得服务名称的 DNS 查询返回一个 IP 地址列表, 客户端直接访问其中的一个地址
labels # 指定服务的标签,这些标签仅在服务上设置
mode # 指定 deploy 的模式
global # 每个集群节点都只有一个容器
replicated # 用户可以指定集群中容器的数量(默认)
replicas # deploy 的 mode 为 replicated 时, 指定容器副本的数量
resources # 资源限制
limits # 设置容器的资源限制
cpus: "0.5" # 设置该容器最多只能使用 50% 的 CPU
memory: 50M # 设置该容器最多只能使用 50M 的内存空间
reservations # 设置为容器预留的系统资源(随时可用)
cpus: "0.2" # 为该容器保留 20% 的 CPU
memory: 20M # 为该容器保留 20M 的内存空间
restart_policy # 定义容器重启策略, 用于代替 restart 参数
condition # 定义容器重启策略(接受三个参数)
none # 不尝试重启
on-failure # 只有当容器内部应用程序出现问题才会重启
any # 无论如何都会尝试重启(默认)
delay # 尝试重启的间隔时间(默认为 0s)
max_attempts # 尝试重启次数(默认一直尝试重启)
window # 检查重启是否成功之前的等待时间(即如果容器启动了, 隔多少秒之后去检测容器是否正常, 默认 0s)
update_config # 用于配置滚动更新配置
parallelism # 一次性更新的容器数量
delay # 更新一组容器之间的间隔时间
failure_action # 定义更新失败的策略
continue # 继续更新
rollback # 回滚更新
pause # 暂停更新(默认)
monitor # 每次更新后的持续时间以监视更新是否失败(单位: ns|us|ms|s|m|h) (默认为0)
max_failure_ratio # 回滚期间容忍的失败率(默认值为0)
order # v3.4 版本中新增的参数, 回滚期间的操作顺序
stop-first #旧任务在启动新任务之前停止(默认)
start-first #首先启动新任务, 并且正在运行的任务暂时重叠
rollback_config # v3.7 版本中新增的参数, 用于定义在 update_config 更新失败的回滚策略
parallelism # 一次回滚的容器数, 如果设置为0, 则所有容器同时回滚
delay # 每个组回滚之间的时间间隔(默认为0)
failure_action # 定义回滚失败的策略
continue # 继续回滚
pause # 暂停回滚
monitor # 每次回滚任务后的持续时间以监视失败(单位: ns|us|ms|s|m|h) (默认为0)
max_failure_ratio # 回滚期间容忍的失败率(默认值0)
order # 回滚期间的操作顺序
stop-first # 旧任务在启动新任务之前停止(默认)
start-first # 首先启动新任务, 并且正在运行的任务暂时重叠

注意:
支持 docker-compose up docker-compose run 但不支持 docker stack deploy 的子选项
security_opt container_name devices tmpfs stop_signal links cgroup_parent
network_mode external_links restart build userns_mode sysctls

devices # 指定设备映射列表 (等同于 docker run --device 的作用)

depends_on # 定义容器启动顺序 (此选项解决了容器之间的依赖关系, 此选项在 v3 版本中 使用 swarm 部署时将忽略该选项)
示例:
docker-compose up 以依赖顺序启动服务,下面例子中 redis db 服务在 web 启动前启动
默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redis db 两个服务,因为在配置文件中定义了依赖关系
version: '3'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres

dns # 设置 DNS 地址(等同于 docker run --dns 的作用)

dns_search # 设置 DNS 搜索域(等同于 docker run --dns-search 的作用)

tmpfs # v2 版本以上, 挂载目录到容器中, 作为容器的临时文件系统(等同于 docker run --tmpfs 的作用, 在使用 swarm 部署时将忽略该选项)

entrypoint # 覆盖容器的默认 entrypoint 指令 (等同于 docker run --entrypoint 的作用)

env_file # 从指定文件中读取变量设置为容器中的环境变量, 可以是单个值或者一个文件列表, 如果多个文件中的变量重名则后面的变量覆盖前面的变量, environment 的值覆盖 env_file 的值
文件格式:
RACK_ENV=development

environment # 设置环境变量, environment 的值可以覆盖 env_file 的值 (等同于 docker run --env 的作用)

expose # 暴露端口, 但是不能和宿主机建立映射关系, 类似于 Dockerfile 的 EXPOSE 指令

external_links # 连接不在 docker-compose.yml 中定义的容器或者不在 compose 管理的容器(docker run 启动的容器, 在 v3 版本中使用 swarm 部署时将忽略该选项)

extra_hosts # 添加 host 记录到容器中的 /etc/hosts 中 (等同于 docker run --add-host 的作用)

healthcheck # v2.1 以上版本, 定义容器健康状态检查, 类似于 Dockerfile 的 HEALTHCHECK 指令
test # 检查容器检查状态的命令, 该选项必须是一个字符串或者列表, 第一项必须是 NONE, CMD 或 CMD-SHELL, 如果其是一个字符串则相当于 CMD-SHELL 加该字符串
NONE # 禁用容器的健康状态检测
CMD # test: ["CMD", "curl", "-f", "http://localhost"]
CMD-SHELL # test: ["CMD-SHELL", "curl -f http://localhost || exit 1"] 或者 test: curl -f https://localhost || exit 1
interval: 1m30s # 每次检查之间的间隔时间
timeout: 10s # 运行命令的超时时间
retries: 3 # 重试次数
start_period: 40s # v3.4 以上新增的选项, 定义容器启动时间间隔
disable: true # true 或 false, 表示是否禁用健康状态检测和 test: NONE 相同

image # 指定 docker 镜像, 可以是远程仓库镜像、本地镜像

init # v3.7 中新增的参数, true 或 false 表示是否在容器中运行一个 init, 它接收信号并传递给进程

isolation # 隔离容器技术, 在 Linux 中仅支持 default 值

labels # 使用 Docker 标签将元数据添加到容器, 与 Dockerfile 中的 LABELS 类似

links # 链接到其它服务中的容器, 该选项是 docker 历史遗留的选项, 目前已被用户自定义网络名称空间取代, 最终有可能被废弃 (在使用 swarm 部署时将忽略该选项)

logging # 设置容器日志服务
driver # 指定日志记录驱动程序, 默认 json-file (等同于 docker run --log-driver 的作用)
options # 指定日志的相关参数 (等同于 docker run --log-opt 的作用)
max-size # 设置单个日志文件的大小, 当到达这个值后会进行日志滚动操作
max-file # 日志文件保留的数量

network_mode # 指定网络模式 (等同于 docker run --net 的作用, 在使用 swarm 部署时将忽略该选项)

networks # 将容器加入指定网络 (等同于 docker network connect 的作用), networks 可以位于 compose 文件顶级键和 services 键的二级键
aliases # 同一网络上的容器可以使用服务名称或别名连接到其中一个服务的容器
ipv4_address # IP V4 格式
ipv6_address # IP V6 格式

示例:
version: '3.7'
services:
test:
image: nginx:1.14-alpine
container_name: mynginx
command: ifconfig
networks:
app_net: # 调用下面 networks 定义的 app_net 网络
ipv4_address: 172.16.238.10
networks:
app_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.16.238.0/24

pid: 'host' # 共享宿主机的 进程空间(PID)

ports # 建立宿主机和容器之间的端口映射关系, ports 支持两种语法格式
SHORT 语法格式示例:
- "3000" # 暴露容器的 3000 端口, 宿主机的端口由 docker 随机映射一个没有被占用的端口
- "3000-3005" # 暴露容器的 3000 到 3005 端口, 宿主机的端口由 docker 随机映射没有被占用的端口
- "8000:8000" # 容器的 8000 端口和宿主机的 8000 端口建立映射关系
- "9090-9091:8080-8081"
- "127.0.0.1:8001:8001" # 指定映射宿主机的指定地址的
- "127.0.0.1:5000-5010:5000-5010"
- "6060:6060/udp" # 指定协议

LONG 语法格式示例:(v3.2 新增的语法格式)
ports:
- target: 80 # 容器端口
published: 8080 # 宿主机端口
protocol: tcp # 协议类型
mode: host # host 在每个节点上发布主机端口, ingress 对于群模式端口进行负载均衡

security_opt # 为每个容器覆盖默认的标签 (在使用 swarm 部署时将忽略该选项)

stop_grace_period # 指定在发送了 SIGTERM 信号之后, 容器等待多少秒之后退出(默认 10s)

stop_signal # 指定停止容器发送的信号 (默认为 SIGTERM 相当于 kill PID; SIGKILL 相当于 kill -9 PID; 在使用 swarm 部署时将忽略该选项)

sysctls # 设置容器中的内核参数 (在使用 swarm 部署时将忽略该选项)

ulimits # 设置容器的 limit

userns_mode # 如果Docker守护程序配置了用户名称空间, 则禁用此服务的用户名称空间 (在使用 swarm 部署时将忽略该选项)

volumes # 定义容器和宿主机的卷映射关系, 其和 networks 一样可以位于 services 键的二级键和 compose 顶级键, 如果需要跨服务间使用则在顶级键定义, 在 services 中引用
SHORT 语法格式示例:
volumes:
- /var/lib/mysql # 映射容器内的 /var/lib/mysql 到宿主机的一个随机目录中
- /opt/data:/var/lib/mysql # 映射容器内的 /var/lib/mysql 到宿主机的 /opt/data
- ./cache:/tmp/cache # 映射容器内的 /var/lib/mysql 到宿主机 compose 文件所在的位置
- ~/configs:/etc/configs/:ro # 映射容器宿主机的目录到容器中去, 权限只读
- datavolume:/var/lib/mysql # datavolume 为 volumes 顶级键定义的目录, 在此处直接调用

LONG 语法格式示例:(v3.2 新增的语法格式)
version: "3.2"
services:
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- type: volume # mount 的类型, 必须是 bind、volume 或 tmpfs
source: mydata # 宿主机目录
target: /data # 容器目录
volume: # 配置额外的选项, 其 key 必须和 type 的值相同
nocopy: true # volume 额外的选项, 在创建卷时禁用从容器复制数据
- type: bind # volume 模式只指定容器路径即可, 宿主机路径随机生成; bind 需要指定容器和数据机的映射路径
source: ./static
target: /opt/app/static
read_only: true # 设置文件系统为只读文件系统
volumes:
mydata: # 定义在 volume, 可在所有服务中调用

restart # 定义容器重启策略(在使用 swarm 部署时将忽略该选项, 在 swarm 使用 restart_policy 代替 restart)
no # 禁止自动重启容器(默认)
always # 无论如何容器都会重启
on-failure # 当出现 on-failure 报错时, 容器重新启动

其他选项:
domainname, hostname, ipc, mac_address, privileged, read_only, shm_size, stdin_open, tty, user, working_dir
上面这些选项都只接受单个值和 docker run 的对应参数类似

对于值为时间的可接受的值:
2.5s
10s
1m30s
2h32m
5h34m56s
时间单位: us, ms, s, m, h
对于值为大小的可接受的值:
2b
1024kb
2048k
300m
1gb
单位: b, k, m, g 或者 kb, mb, gb
networks # 定义 networks 信息
driver # 指定网络模式, 大多数情况下, 它 bridge 于单个主机和 overlay Swarm 上
bridge # Docker 默认使用 bridge 连接单个主机上的网络
overlay # overlay 驱动程序创建一个跨多个节点命名的网络
host # 共享主机网络名称空间(等同于 docker run --net=host)
none # 等同于 docker run --net=none
driver_opts # v3.2以上版本, 传递给驱动程序的参数, 这些参数取决于驱动程序
attachable # driver 为 overlay 时使用, 如果设置为 true 则除了服务之外,独立容器也可以附加到该网络; 如果独立容器连接到该网络,则它可以与其他 Docker 守护进程连接到的该网络的服务和独立容器进行通信
ipam # 自定义 IPAM 配置. 这是一个具有多个属性的对象, 每个属性都是可选的
driver # IPAM 驱动程序, bridge 或者 default
config # 配置项
subnet # CIDR格式的子网,表示该网络的网段
external # 外部网络, 如果设置为 true 则 docker-compose up 不会尝试创建它, 如果它不存在则引发错误
name # v3.5 以上版本, 为此网络设置名称
文件格式示例:
version: "3"
services:
redis:
image: redis:alpine
ports:
- "6379"
networks:
- frontend
deploy:
replicas: 2
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
db:
image: postgres:9.4
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
placement:
constraints: [node.role == manager]

depends_on

  在docker-compose文件中,可以通过depends_on指定某个容器在其他容器启动后再启动,比如nacos容器增加:

1
2
depends_on:
- nginx

  其含义如下:

  • 仅控制 启动顺序:nacos 会在 nginx 容器启动后再启动
  • 不保证 nginx 已经完全可用(比如已经监听 80 端口)
  • 对于简单场景通常够用,但如果你需要确保 nginx 可响应 HTTP 请求,需要额外健康检查

  如果希望 nacos 等待 nginx 完全可用(端口可访问)

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
services:
nginx:
image: nginx:stable-alpine
container_name: nginx
restart: always
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 10s
retries: 5
timeout: 5s

nacos:
image: nacos/nacos-server:v2.5.2
container_name: nacos
restart: always
environment:
# === 运行模式 ===
- MODE=standalone
- PREFER_HOST_MODE=hostname
# === 启用外部 MySQL ===
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=192.168.3.42
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=123456
# === 权限系统 ===
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_IDENTITY_KEY=SecretKey8901234567890123456789
- NACOS_AUTH_IDENTITY_VALUE=securityValue890123456789012345
- NACOS_AUTH_TOKEN=N1t+U5j9SuOT6UFomt8aV/CySbu0Qvy1CyR9g9mmvIc=
# === JVM 参数(根据机器调整) ===
- JVM_XMS=512m
- JVM_XMX=512m
volumes:
- ./nacos_data:/home/nacos/data
- ./nacos_logs:/home/nacos/logs
ports:
- "8848:8848" # 控制台 + OpenAPI
- "9848:9848" # gRPC
- "9849:9849" # gRPC
depends_on:
nginx:
condition: service_healthy

  condition: service_healthy表示等待 nginx 健康检查通过后再启动 nacos,这样更加安全,避免 nacos 启动时 nginx 还没监听 80 端口导致请求失败。

链式顺序

  我们可能有个疑问,对depends_on参数,会遵循链式前置原则嘛?比如在 nacos 中 depends_on 了 nginx,在 gateway 中又 depends_on 了 nacos,那么 gateway 是不是启动顺序排第三,即以上容器的启动顺序为 nginx、nacso、gateway。

  答案是:没错,你的理解正确。

  Docker Compose 会按链式顺序启动容器,链式依赖会累积顺序,gateway 会在 nacos 启动后再启动,nacos 会又在 nginx 启动后再启动。

healthcheck

  healthcheck 基本结构如下:

1
2
3
4
5
6
7
8
9
10
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
# 两次健康检查间隔时间
interval: 30s
# 命令执行超时判断失败时间
timeout: 5s
# 失败次数后才判 unhealthy
retries: 3
# 宽限时长,避免启动慢误判
start_period: 30s

参数含义

test

  test参数用于定义检测容器健康的命令或方式。
  其语法格式如下:

  exec form 存在CMDCMD-SHELL两种格式可以选择:

1
2
3
4
# 第一种格式
test: ["CMD", "curl", "-f", "http://localhost"]
# 第二种格式
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]

  两种含义不同:

  • CMD: 执行命令数组,返回退出码判断状态
    • 0 → healthy
    • 非 0 → unhealthy
  • CMD-SHELL: 以 shell 执行字符串命令,支持管道和环境变量,CMD支持偏弱有更复杂的命令时建议使用此格式,上面的CMD-SHELL含义如下:
    • curl -f → 如果 HTTP 返回码 >= 400 会返回非 0
    • || exit 1 → 确保命令失败时退出码为 1,触发 unhealthy

  test: nonenone 表示禁用 healthcheck,即容器无健康检查。

interval

  interval代表容器 执行健康检查的间隔时间,用于控制 healthcheck 命令执行频率,默认值为 30 秒。

  一般建议默认即可,太短 → 频繁执行,增加 CPU / I/O 消耗,太长 → 容器状态更新慢,依赖服务启动等待更久。

语法示例
1
interval: 10s

timeout

  timeout参数用于单次健康检查命令 允许执行的最长时间,如果超过这个时间,healthcheck 会认为本次失败。

  timeout参数可以防止某些命令卡住或无限挂起,太短 → 健康检查容易失败,太长 → 状态更新慢。

语法示例
1
timeout: 5s

retries

  retries代表允许连续失败的次数后才判定容器为 unhealthy,可以用来防止偶发网络或临时错误导致容器被误判 unhealthy,默认值为 3。

  默认值情况下,前两次检查失败 → 状态仍为 starting,第三次失败 → 状态变为 unhealthy

语法示例
1
retries: 3

start_period(可选,但推荐)

  start_period代表容器启动后的初始宽限期,在此期间,healthcheck 失败不会计入 retries,默认值为 0。

  为了避免刚启动就被判定 unhealthy,推荐使用此参数,对于启动慢的服务(比如数据库或 Nacos)非常有用。

语法示例
1
start_period: 10s

性能影响

  CPU / 内存消耗:

  • 执行健康检查命令会占用少量 CPU / 内存
  • 对轻量命令(curl 或 pg_isready)影响微乎其微
  • 如果 healthcheck 太频繁(interval < 1s)或者命令复杂,会有明显性能影响

  I/O 消耗:

  • curl、数据库 ping、HTTP 请求都会产生网络 I/O
  • 高频请求可能导致网络压力或服务端日志增多

生产推荐

参数 建议
interval 10~30s,依赖服务敏感度调整
timeout 不超过 1/2 interval
retries 3~5
start_period 服务启动慢时设置,如 20~60s
test 命令 尽量轻量,避免复杂脚本

扩展─锚点合并

  有时候,在docker-compose文件中,我们希望定义一组变量,然后在某个配置中继承,这可以使用 YAML 的锚点与合并(YAML Anchors & Merge Keys) 功能进行实现。

核心规则

  • &xxx = 定义一个锚点(anchor)
  • *xxx = 引用一个锚点(alias)
  • <<: = YAML merge key,用来“把锚点内容合并进来”

示例

配置继承

  定义一个公共块:

1
2
3
4
x-template: &common
restart: always
environment:
- TZ=Asia/Shanghai

  后续引用并合并:

1
2
3
4
5
6
7
8
services:
web:
image: nginx
<<: *common

app:
image: myapp
<<: *common

  实际效果等价于:

1
2
3
4
5
6
7
8
9
10
11
12
services:
web:
image: nginx
restart: always
environment:
- TZ=Asia/Shanghai

app:
image: myapp
restart: always
environment:
- TZ=Asia/Shanghai

变量替换

1
2
3
4
5
6
7
8
9
10
11
12
13
x-shared-env: &shared-env
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
CONSOLE_WEB_URL: ${CONSOLE_WEB_URL:-}
SERVICE_API_URL: ${SERVICE_API_URL:-}

services:
web:
image: langgenius/dify-web:1.9.2
restart: always
environment:
<<: *shared-env
# 变量替换(如果外部没有传入变量,就用空字符串)
CONSOLE_API_URL: ${CONSOLE_API_URL:-}

注意:锚点合并问题

  以下配置存在问题:

1
2
3
4
gateway:
...
<<: *nacosHealthy
<<: *loggingStrategy

  因为,YAML 不允许同级重复的 << 键

  解决方案是把多个锚点放在一个列表里,形式如下:

1
2
3
gateway:
...
<<: [*nacosHealthy, *loggingStrategy]

文章信息

时间 说明
2019-06-11 初稿
2025-12-12 从主文章剥离重构
0%