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

14 KiB
Raw Permalink Blame History

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 合并规则

重要原则后面的文件会覆盖或扩展前面的文件

  1. 如果服务存在:后面的配置会合并到现有服务
  2. 如果服务不存在:会添加新服务
  3. 配置项合并
    • 标量值(字符串、数字):后面的覆盖前面的
    • 数组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 示例1tb-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:2181
  • tb-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 文件组织

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