14 KiB
Docker Compose 多文件组合机制详解
1. 核心概念
Docker Compose 支持通过 -f 参数指定多个配置文件,这些文件会按照指定的顺序**合并(merge)**成一个完整的配置。
1.1 基本语法
docker compose \
-f docker-compose.yml \ # 第一个文件(基础配置)
-f docker-compose.postgres.yml \ # 第二个文件(覆盖/扩展)
-f docker-compose.kafka.yml \ # 第三个文件(继续覆盖/扩展)
up -d
1.2 合并规则
重要原则:后面的文件会覆盖或扩展前面的文件
- 如果服务存在:后面的配置会合并到现有服务
- 如果服务不存在:会添加新服务
- 配置项合并:
- 标量值(字符串、数字):后面的覆盖前面的
- 数组(ports、volumes、env_file):后面的追加到前面的
- 对象(environment、logging):后面的合并到前面的
2. ThingsBoard 中的实际应用
2.1 文件组合流程
以 docker-start-services.sh 为例:
# 1. 读取 .env 文件
source .env
# DATABASE=postgres
# CACHE=valkey
# TB_QUEUE_TYPE=kafka
# 2. 调用工具函数,根据配置选择文件
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs)
# 返回: -f docker-compose.postgres.yml
ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs)
# 返回: -f docker-compose.valkey.yml
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs)
# 返回: -f docker-compose.kafka.yml
# 3. 组合成完整命令
COMPOSE_ARGS="\
-f docker-compose.yml \ # 主配置
${ADDITIONAL_CACHE_ARGS} \ # -f docker-compose.valkey.yml
${ADDITIONAL_COMPOSE_ARGS} \ # -f docker-compose.postgres.yml
${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ # -f docker-compose.kafka.yml
up -d"
# 4. 执行命令
docker compose $COMPOSE_ARGS
实际执行的命令:
docker compose \
-f docker-compose.yml \
-f docker-compose.valkey.yml \
-f docker-compose.postgres.yml \
-f docker-compose.kafka.yml \
up -d
3. 具体示例分析
3.1 示例1:tb-core1 服务的配置合并
步骤1:主配置文件 (docker-compose.yml)
services:
tb-core1:
restart: always
image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
ports:
- "8080"
- "7070"
environment:
TB_SERVICE_ID: tb-core1
TB_SERVICE_TYPE: tb-core
EDGES_ENABLED: "true"
env_file:
- tb-node.env # 通用配置
volumes:
- ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard
depends_on:
- zookeeper
- tb-js-executor
- tb-rule-engine1
- tb-rule-engine2
步骤2:数据库配置 (docker-compose.postgres.yml)
services:
postgres: # 新增服务
restart: always
image: "postgres:16"
ports:
- "5432"
environment:
POSTGRES_DB: thingsboard
POSTGRES_PASSWORD: postgres
volumes:
- ./tb-node/postgres:/var/lib/postgresql/data
tb-core1: # 扩展现有服务
env_file:
- tb-node.postgres.env # 追加环境变量文件
depends_on:
- postgres # 追加依赖
步骤3:缓存配置 (docker-compose.valkey.yml)
services:
valkey: # 新增服务
restart: always
image: bitnamilegacy/valkey:8.0
ports:
- '6379:6379'
volumes:
- ./tb-node/valkey-data:/bitnami/valkey/data
tb-core1: # 扩展现有服务
env_file:
- cache-valkey.env # 追加环境变量文件
depends_on:
- valkey # 追加依赖
步骤4:队列配置 (docker-compose.kafka.yml)
services:
kafka: # 新增服务
restart: always
image: "bitnamilegacy/kafka:4.0"
ports:
- "9092:9092"
env_file:
- kafka.env
tb-core1: # 扩展现有服务
env_file:
- queue-kafka.env # 追加环境变量文件
depends_on:
- kafka # 追加依赖
最终合并结果
Docker Compose 会将所有文件合并,tb-core1 的最终配置为:
services:
tb-core1:
# 来自 docker-compose.yml
restart: always
image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
ports:
- "8080" # 来自主配置
- "7070" # 来自主配置
environment:
TB_SERVICE_ID: tb-core1
TB_SERVICE_TYPE: tb-core
EDGES_ENABLED: "true"
# env_file 数组被合并(追加)
env_file:
- tb-node.env # 来自主配置
- tb-node.postgres.env # 来自 postgres.yml
- cache-valkey.env # 来自 valkey.yml
- queue-kafka.env # 来自 kafka.yml
# volumes 数组被合并(追加)
volumes:
- ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard
# depends_on 数组被合并(追加)
depends_on:
- zookeeper # 来自主配置
- tb-js-executor # 来自主配置
- tb-rule-engine1 # 来自主配置
- tb-rule-engine2 # 来自主配置
- postgres # 来自 postgres.yml
- valkey # 来自 valkey.yml
- kafka # 来自 kafka.yml
3.2 关键点说明
env_file 数组的合并
# 主配置
env_file:
- tb-node.env
# postgres.yml 追加
env_file:
- tb-node.postgres.env
# 最终结果:两个文件都会被加载
env_file:
- tb-node.env
- tb-node.postgres.env
注意:环境变量文件的加载顺序很重要!如果两个文件有相同的变量,后面的会覆盖前面的。
例如:
tb-node.env:ZOOKEEPER_URL=zookeeper:2181tb-node.postgres.env:DATABASE_TS_TYPE=sql
最终所有变量都会被加载,但如果 tb-node.postgres.env 也定义了 ZOOKEEPER_URL,它会覆盖 tb-node.env 中的值。
depends_on 数组的合并
# 主配置
depends_on:
- zookeeper
- tb-js-executor
# postgres.yml 追加
depends_on:
- postgres
# 最终结果:所有依赖都会包含
depends_on:
- zookeeper
- tb-js-executor
- postgres
新增服务
附加配置文件可以添加新服务:
# docker-compose.postgres.yml
services:
postgres: # 这个服务在主配置中不存在,会被添加
image: "postgres:16"
...
4. 配置覆盖规则详解
4.1 数组类型的合并
数组会追加,不会覆盖:
# 文件1
ports:
- "8080"
# 文件2
ports:
- "9090"
# 最终结果:两个端口都保留
ports:
- "8080"
- "9090"
4.2 对象类型的合并
对象会合并,相同键会被覆盖:
# 文件1
environment:
VAR1: value1
VAR2: value2
# 文件2
environment:
VAR2: new_value2 # 覆盖 VAR2
VAR3: value3 # 新增 VAR3
# 最终结果
environment:
VAR1: value1 # 保留
VAR2: new_value2 # 被覆盖
VAR3: value3 # 新增
4.3 标量值的覆盖
标量值(字符串、数字、布尔值)会被覆盖:
# 文件1
restart: always
# 文件2
restart: never
# 最终结果:后面的覆盖前面的
restart: never
5. 实际执行流程
5.1 启动服务的完整流程
# 1. 用户执行
./docker-start-services.sh
# 2. 脚本读取 .env
DATABASE=postgres
CACHE=valkey
TB_QUEUE_TYPE=kafka
# 3. 脚本调用工具函数
additionalComposeArgs() → "-f docker-compose.postgres.yml"
additionalComposeCacheArgs() → "-f docker-compose.valkey.yml"
additionalComposeQueueArgs() → "-f docker-compose.kafka.yml"
# 4. 组合命令
docker compose \
-f docker-compose.yml \
-f docker-compose.valkey.yml \
-f docker-compose.postgres.yml \
-f docker-compose.kafka.yml \
up -d
# 5. Docker Compose 执行
# - 读取并解析所有 Compose 文件
# - 按顺序合并配置
# - 创建服务定义
# - 启动容器
5.2 文件读取顺序
Docker Compose 按照 -f 参数的顺序读取文件:
1. docker-compose.yml # 基础配置(所有服务定义)
2. docker-compose.valkey.yml # 添加 valkey 服务,扩展其他服务
3. docker-compose.postgres.yml # 添加 postgres 服务,扩展其他服务
4. docker-compose.kafka.yml # 添加 kafka 服务,扩展其他服务
重要:后面的文件可以:
- ✅ 添加新服务
- ✅ 扩展现有服务的配置(追加数组、合并对象)
- ✅ 覆盖现有服务的标量值
6. 为什么这样设计?
6.1 模块化配置
优势:
- ✅ 关注点分离:每个文件负责一个方面(数据库、队列、缓存)
- ✅ 易于维护:修改数据库配置只需要改一个文件
- ✅ 灵活组合:可以根据需求选择不同的配置组合
6.2 配置复用
优势:
- ✅ 避免重复:不需要在每个配置文件中重复定义所有服务
- ✅ 统一管理:主配置文件定义所有服务的基础配置
- ✅ 按需扩展:只在需要的时候添加特定配置
6.3 环境适配
优势:
- ✅ 开发环境:
postgres + valkey + kafka - ✅ 生产环境:
hybrid + valkey-cluster + confluent - ✅ 测试环境:
postgres + valkey + kafka + monitoring
通过简单的 .env 配置就能切换不同的环境!
7. 实际配置示例
7.1 开发环境配置
.env 文件:
DATABASE=postgres
CACHE=valkey
TB_QUEUE_TYPE=kafka
MONITORING_ENABLED=false
EDQS_ENABLED=false
组合的文件:
docker compose \
-f docker-compose.yml \
-f docker-compose.valkey.yml \
-f docker-compose.postgres.yml \
-f docker-compose.kafka.yml \
up -d
结果:
- ✅ 所有 ThingsBoard 服务
- ✅ PostgreSQL 数据库
- ✅ Valkey 单节点缓存
- ✅ Kafka 消息队列
- ❌ 无监控
- ❌ 无 EDQS
7.2 生产环境配置
.env 文件:
DATABASE=hybrid
CACHE=valkey-cluster
TB_QUEUE_TYPE=kafka
MONITORING_ENABLED=true
EDQS_ENABLED=true
组合的文件:
docker compose \
-f docker-compose.yml \
-f docker-compose.valkey-cluster.yml \
-f docker-compose.hybrid.yml \
-f docker-compose.kafka.yml \
-f docker-compose.prometheus-grafana.yml \
-f docker-compose.edqs.yml \
up -d
结果:
- ✅ 所有 ThingsBoard 服务
- ✅ PostgreSQL + Cassandra(混合模式)
- ✅ Valkey 集群(高可用)
- ✅ Kafka 消息队列
- ✅ Prometheus + Grafana 监控
- ✅ EDQS 服务
8. 调试技巧
8.1 查看合并后的配置
# 查看合并后的完整配置
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
config
# 查看特定服务的配置
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
config | grep -A 50 "tb-core1:"
8.2 验证文件组合
# 手动执行组合命令,查看输出
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
config --services
# 输出所有服务名称
# zookeeper
# postgres
# tb-core1
# tb-core2
# ...
8.3 检查环境变量
# 查看最终的环境变量配置
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
config | grep -A 20 "tb-core1:" | grep env_file
9. 常见问题
9.1 为什么 env_file 是追加而不是覆盖?
原因:env_file 是数组类型,Docker Compose 会合并数组而不是覆盖。
影响:如果两个环境变量文件定义了相同的变量,后面的会覆盖前面的(在文件加载时)。
解决方案:
- 避免在不同文件中定义相同的变量
- 或者明确变量的优先级顺序
9.2 如何覆盖主配置中的配置?
方法1:在附加文件中重新定义(覆盖标量值)
# docker-compose.override.yml
services:
tb-core1:
restart: never # 覆盖主配置中的 restart: always
方法2:使用不同的配置项名称
# 主配置
environment:
LOG_LEVEL: INFO
# 附加配置
environment:
LOG_LEVEL: DEBUG # 覆盖 LOG_LEVEL
9.3 为什么 depends_on 会合并?
原因:depends_on 是数组类型,Docker Compose 会将所有依赖合并。
好处:每个配置文件可以添加自己的依赖,最终所有依赖都会被包含。
注意:依赖的顺序不影响启动顺序,Docker Compose 会自动解析依赖关系。
10. 最佳实践
10.1 文件组织
- 主配置文件:定义所有服务的通用配置
- 模块化文件:每个文件负责一个方面(数据库、队列、缓存)
- 环境文件:使用
.env选择不同的配置组合
10.2 配置优先级
- 主配置:基础配置,最通用
- 模块配置:特定功能的配置
- 环境变量:运行时配置,优先级最高
10.3 避免冲突
- 不要重复定义:相同的配置项不要在不同文件中重复定义
- 明确职责:每个文件只负责自己的部分
- 文档说明:在文件开头注释说明该文件的用途
11. 总结
11.1 核心机制
Docker Compose 多文件组合的核心机制:
- 文件顺序:按照
-f参数的顺序读取和合并 - 合并规则:
- 数组:追加
- 对象:合并
- 标量:覆盖
- 服务扩展:后面的文件可以扩展或覆盖前面文件中的服务配置
- 服务添加:后面的文件可以添加新服务
11.2 ThingsBoard 的优势
通过这种机制,ThingsBoard 实现了:
- ✅ 灵活的配置:根据
.env选择不同的配置组合 - ✅ 模块化设计:每个配置文件职责单一
- ✅ 易于维护:修改配置只需要改对应的文件
- ✅ 环境适配:开发、测试、生产环境使用不同的组合
11.3 关键理解点
- 主配置文件 (
docker-compose.yml) 定义所有服务的基础配置 - 附加配置文件 (
docker-compose.*.yml) 扩展或覆盖特定配置 - 脚本 (
compose-utils.sh) 根据.env动态选择需要组合的文件 - 最终配置 是所有文件的合并结果
这种设计模式在 Docker Compose 中非常常见,也是 Docker Compose 官方推荐的最佳实践!