# 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 示例1:tb-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 官方推荐的最佳实践!