thingsboard/summary/15-Docker-Compose多文件组合机制详解.md

596 lines
14 KiB
Markdown
Raw Permalink Normal View History

2026-01-19 11:50:37 +08:00
# Docker Compose 多文件组合机制详解
## 1. 核心概念
Docker Compose 支持通过 `-f` 参数指定**多个配置文件**,这些文件会按照指定的顺序**合并merge**成一个完整的配置。
### 1.1 基本语法
```bash
docker compose \
-f docker-compose.yml \ # 第一个文件(基础配置)
-f docker-compose.postgres.yml \ # 第二个文件(覆盖/扩展)
-f docker-compose.kafka.yml \ # 第三个文件(继续覆盖/扩展)
up -d
```
### 1.2 合并规则
**重要原则****后面的文件会覆盖或扩展前面的文件**
1. **如果服务存在**:后面的配置会**合并**到现有服务
2. **如果服务不存在**:会**添加**新服务
3. **配置项合并**
- **标量值**(字符串、数字):后面的覆盖前面的
- **数组**ports、volumes、env_file后面的**追加**到前面的
- **对象**environment、logging后面的**合并**到前面的
## 2. ThingsBoard 中的实际应用
### 2.1 文件组合流程
`docker-start-services.sh` 为例:
```bash
# 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
```
**实际执行的命令**
```bash
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 示例1tb-core1 服务的配置合并
#### 步骤1主配置文件 (docker-compose.yml)
```yaml
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)
```yaml
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)
```yaml
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)
```yaml
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` 的最终配置为:
```yaml
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 数组的合并
```yaml
# 主配置
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:2181`
- `tb-node.postgres.env`: `DATABASE_TS_TYPE=sql`
最终所有变量都会被加载,但如果 `tb-node.postgres.env` 也定义了 `ZOOKEEPER_URL`,它会覆盖 `tb-node.env` 中的值。
#### depends_on 数组的合并
```yaml
# 主配置
depends_on:
- zookeeper
- tb-js-executor
# postgres.yml 追加
depends_on:
- postgres
# 最终结果:所有依赖都会包含
depends_on:
- zookeeper
- tb-js-executor
- postgres
```
#### 新增服务
附加配置文件可以**添加新服务**
```yaml
# docker-compose.postgres.yml
services:
postgres: # 这个服务在主配置中不存在,会被添加
image: "postgres:16"
...
```
## 4. 配置覆盖规则详解
### 4.1 数组类型的合并
**数组会追加,不会覆盖**
```yaml
# 文件1
ports:
- "8080"
# 文件2
ports:
- "9090"
# 最终结果:两个端口都保留
ports:
- "8080"
- "9090"
```
### 4.2 对象类型的合并
**对象会合并,相同键会被覆盖**
```yaml
# 文件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 标量值的覆盖
**标量值(字符串、数字、布尔值)会被覆盖**
```yaml
# 文件1
restart: always
# 文件2
restart: never
# 最终结果:后面的覆盖前面的
restart: never
```
## 5. 实际执行流程
### 5.1 启动服务的完整流程
```bash
# 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 文件**
```bash
DATABASE=postgres
CACHE=valkey
TB_QUEUE_TYPE=kafka
MONITORING_ENABLED=false
EDQS_ENABLED=false
```
**组合的文件**
```bash
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 文件**
```bash
DATABASE=hybrid
CACHE=valkey-cluster
TB_QUEUE_TYPE=kafka
MONITORING_ENABLED=true
EDQS_ENABLED=true
```
**组合的文件**
```bash
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 查看合并后的配置
```bash
# 查看合并后的完整配置
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 验证文件组合
```bash
# 手动执行组合命令,查看输出
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
config --services
# 输出所有服务名称
# zookeeper
# postgres
# tb-core1
# tb-core2
# ...
```
### 8.3 检查环境变量
```bash
# 查看最终的环境变量配置
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**:在附加文件中重新定义(覆盖标量值)
```yaml
# docker-compose.override.yml
services:
tb-core1:
restart: never # 覆盖主配置中的 restart: always
```
**方法2**:使用不同的配置项名称
```yaml
# 主配置
environment:
LOG_LEVEL: INFO
# 附加配置
environment:
LOG_LEVEL: DEBUG # 覆盖 LOG_LEVEL
```
### 9.3 为什么 depends_on 会合并?
**原因**`depends_on` 是数组类型Docker Compose 会将所有依赖合并。
**好处**:每个配置文件可以添加自己的依赖,最终所有依赖都会被包含。
**注意**依赖的顺序不影响启动顺序Docker Compose 会自动解析依赖关系。
## 10. 最佳实践
### 10.1 文件组织
1. **主配置文件**:定义所有服务的通用配置
2. **模块化文件**:每个文件负责一个方面(数据库、队列、缓存)
3. **环境文件**:使用 `.env` 选择不同的配置组合
### 10.2 配置优先级
1. **主配置**:基础配置,最通用
2. **模块配置**:特定功能的配置
3. **环境变量**:运行时配置,优先级最高
### 10.3 避免冲突
1. **不要重复定义**:相同的配置项不要在不同文件中重复定义
2. **明确职责**:每个文件只负责自己的部分
3. **文档说明**:在文件开头注释说明该文件的用途
## 11. 总结
### 11.1 核心机制
Docker Compose 多文件组合的核心机制:
1. **文件顺序**:按照 `-f` 参数的顺序读取和合并
2. **合并规则**
- 数组:追加
- 对象:合并
- 标量:覆盖
3. **服务扩展**:后面的文件可以扩展或覆盖前面文件中的服务配置
4. **服务添加**:后面的文件可以添加新服务
### 11.2 ThingsBoard 的优势
通过这种机制ThingsBoard 实现了:
-**灵活的配置**:根据 `.env` 选择不同的配置组合
-**模块化设计**:每个配置文件职责单一
-**易于维护**:修改配置只需要改对应的文件
-**环境适配**:开发、测试、生产环境使用不同的组合
### 11.3 关键理解点
1. **主配置文件** (`docker-compose.yml`) 定义所有服务的基础配置
2. **附加配置文件** (`docker-compose.*.yml`) 扩展或覆盖特定配置
3. **脚本** (`compose-utils.sh`) 根据 `.env` 动态选择需要组合的文件
4. **最终配置** 是所有文件的合并结果
这种设计模式在 Docker Compose 中非常常见,也是 Docker Compose 官方推荐的最佳实践!