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

596 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 官方推荐的最佳实践!