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 直接交换中间数据,避免调度器成为瓶颈。
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 项目中选择网络模式时,可参考以下决策树:
2.7 常见陷阱与避坑指南
陷阱 1:服务名解析延迟
在 DAG 调度器启动瞬间,Worker 可能因 DNS 缓存未更新而解析到旧 IP。解决方案:在 Worker 启动脚本中加入
在 DAG 调度器启动瞬间,Worker 可能因 DNS 缓存未更新而解析到旧 IP。解决方案:在 Worker 启动脚本中加入
sleep 2 或使用 depends_on 的 condition: service_started 配合重试逻辑。
陷阱 2:端口冲突
当使用
当使用
ports 将容器端口映射到宿主机时,如果多个 DAG 集群部署在同一台机器,端口可能冲突。建议:在 Compose 文件中使用变量 ${SCHEDULER_PORT:-8080},通过环境文件区分不同集群。
最佳实践: 在开发阶段使用
docker compose config 命令验证 Compose 文件的正确性,该命令会输出解析后的完整配置,包括网络别名、卷挂载路径等,可提前发现网络定义错误。