2. Docker Compose 基础:Compose 文件结构、服务定义与网络模式

在 DAG 任务调度场景中,多个计算节点(如 Spark Executor、Flink TaskManager、自定义 Worker)需要以容器化方式协同工作。Docker Compose 提供了声明式编排能力,通过一个 YAML 文件即可定义多服务拓扑、依赖关系与网络通信策略。本章将聚焦 Compose 文件的核心结构、服务定义规范以及三种关键网络模式,为后续 DAG 调度器的网络优化打下基础。

2.1 Compose 文件结构概览

Compose 文件遵循 YAML 1.2 规范,顶层字段定义了整个应用栈的骨架。一个典型的 DAG 调度集群 Compose 文件包含以下核心部分:

顶层字段 作用 DAG 场景典型配置
version 声明 Compose 文件格式版本 推荐 "3.8" 或更高版本,支持完整网络与依赖特性
services 定义每个容器化服务的镜像、端口、环境变量、卷挂载等 scheduler、worker-a、worker-b、result-collector
networks 自定义网络拓扑,控制服务间通信隔离与发现 dag-net(内部通信)、monitor-net(监控流量隔离)
volumes 持久化数据卷,用于共享中间结果或日志 dag-data、log-volume
configs / secrets 注入配置文件或敏感信息(DAG 调度器配置、证书) dag-scheduler.yml、tls-cert
版本选择建议: 对于 DAG 任务调度场景,建议使用 version: "3.8" 或 "3.9"。这些版本支持 depends_on 的条件等待(condition: service_healthy),可确保调度器在 Worker 就绪后再启动任务分发,避免连接失败重试风暴。

2.2 服务定义:从单 Worker 到 DAG 集群

每个服务定义对应一个容器实例。在 DAG 调度中,服务定义需要特别关注 启动顺序、资源限制 和 健康检查。以下是一个包含调度器与两个 Worker 的 Compose 服务定义示例:

version: "3.8"

services:
  dag-scheduler:
    image: dag-scheduler:latest
    container_name: scheduler
    ports:
      - "8080:8080"          # 调度器 API 端口
      - "9090:9090"          # 指标暴露端口
    environment:
      - DAG_WORKER_TIMEOUT=30
      - DAG_MAX_RETRIES=3
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - dag-net
    volumes:
      - dag-data:/data/dags

  worker-a:
    image: dag-worker:latest
    container_name: worker-a
    environment:
      - WORKER_ID=worker-a
      - SCHEDULER_ENDPOINT=dag-scheduler:8080
    depends_on:
      dag-scheduler:
        condition: service_healthy
    networks:
      - dag-net
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G

  worker-b:
    image: dag-worker:latest
    container_name: worker-b
    environment:
      - WORKER_ID=worker-b
      - SCHEDULER_ENDPOINT=dag-scheduler:8080
    depends_on:
      dag-scheduler:
        condition: service_healthy
    networks:
      - dag-net
    deploy:
      resources:
        limits:
          cpus: '4'
          memory: 8G

networks:
  dag-net:
    driver: bridge

volumes:
  dag-data:
关键设计点:
  • 健康检查前置: 使用 depends_on 配合 condition: service_healthy,确保 Worker 只在调度器就绪后启动,避免 DAG 任务因连接拒绝而失败。
  • 资源差异化: 不同 Worker 可分配不同 CPU/内存上限,模拟异构计算节点,贴近真实 DAG 调度环境。
  • 服务发现: 通过服务名(如 dag-scheduler)进行通信,Compose 自动将服务名解析为容器 IP,无需硬编码地址。

2.3 网络模式:DAG 通信的三种选择

Docker Compose 支持多种网络驱动,在 DAG 任务调度场景中,最常用的三种模式是 bridge、host 和 自定义 overlay。下表对比了它们的核心差异:

网络模式 驱动类型 隔离级别 跨主机通信 DAG 适用场景
bridge(默认) bridge 容器间隔离,通过端口映射暴露 不支持(需额外配置) 单机开发/测试,调度器与 Worker 在同一宿主机
host host 无隔离,容器直接使用宿主机网络栈 依赖宿主机网络 对网络延迟极度敏感的任务(如实时流处理)
overlay overlay 跨主机容器间透明通信 原生支持(需 Swarm 或外部 KV 存储) 生产级多机 DAG 集群,Worker 分布在多台物理机
性能陷阱: 在 DAG 调度中,如果 Worker 与调度器之间需要频繁传输中间结果(如 Shuffle 数据),使用 host 模式 可减少网络封装开销,但会牺牲端口隔离。建议在性能测试中对比 bridge 与 host 模式的吞吐量差异,再决定生产部署方案。

2.4 网络拓扑可视化:DAG 调度器与 Worker 通信

下图展示了一个基于 bridge 网络的 DAG 调度集群内部通信拓扑。调度器通过服务名 dag-scheduler 暴露 API,Worker 通过该服务名注册并拉取任务;Worker 之间通过 dag-net 直接交换中间数据,避免调度器成为瓶颈。

DAG 调度集群网络拓扑(bridge 模式) dag-scheduler :8080 (API) worker-a CPU: 2 / Mem: 4G worker-b CPU: 4 / Mem: 8G dag-net (bridge) 任务分发 任务分发 中间结果交换 说明:调度器通过服务名分发任务;Worker 间通过 dag-net 直接交换 Shuffle 数据,避免调度器成为瓶颈

2.5 自定义网络与服务发现机制

在 Compose 中,当服务加入同一个自定义网络(如 dag-net)时,Docker 内置的 DNS 解析会自动将服务名映射为容器 IP。这一机制对 DAG 调度至关重要:

  • 动态注册: Worker 启动后,只需知道调度器的服务名 dag-scheduler,即可通过 DNS 获取其 IP,无需静态配置。
  • 负载均衡: 如果调度器有多个副本(通过 replicas 扩展),Docker DNS 会以轮询方式返回不同副本的 IP,实现基础负载均衡。
  • 网络隔离: 可以为监控服务单独创建 monitor-net,只将调度器的指标端口加入该网络,避免监控流量干扰任务数据通道。
DAG 优化技巧: 在 Compose 文件中为 Worker 服务设置 dns_search: dag-net,可缩短 DNS 解析路径,减少任务调度前的网络握手延迟。对于大规模 DAG 集群(超过 50 个 Worker),建议将 DNS 缓存时间(ndots)调整为 1,避免频繁 DNS 查询。

2.6 网络模式选择决策流程

在实际 DAG 项目中选择网络模式时,可参考以下决策树:

DAG 网络模式选择决策树 是否跨多台宿主机? 否(单机) 使用 bridge 模式 是(多机) 延迟敏感? 是 使用 host 模式 否 使用 overlay 模式 注意:host 模式会牺牲端口隔离;overlay 模式需要 Swarm 或外部 KV 存储(如 Consul) 生产环境建议先使用 bridge 模式进行功能验证,再根据性能测试结果决定是否切换

2.7 常见陷阱与避坑指南

陷阱 1:服务名解析延迟
在 DAG 调度器启动瞬间,Worker 可能因 DNS 缓存未更新而解析到旧 IP。解决方案:在 Worker 启动脚本中加入 sleep 2 或使用 depends_on 的 condition: service_started 配合重试逻辑。
陷阱 2:端口冲突
当使用 ports 将容器端口映射到宿主机时,如果多个 DAG 集群部署在同一台机器,端口可能冲突。建议:在 Compose 文件中使用变量 ${SCHEDULER_PORT:-8080},通过环境文件区分不同集群。
最佳实践: 在开发阶段使用 docker compose config 命令验证 Compose 文件的正确性,该命令会输出解析后的完整配置,包括网络别名、卷挂载路径等,可提前发现网络定义错误。