2. Docker Compose 核心回顾:docker-compose.yml 结构、services/volumes/networks 定义、depends_on 与健康检查、环境变量注入

在构建多租户隔离栈之前,必须彻底掌握 Docker Compose 的核心构造块。本章将系统回顾 docker-compose.yml 的骨架结构、三大顶级元素(services、volumes、networks)的定义方式、服务依赖与健康检查的配合模式,以及环境变量注入的多种手段。这些知识是后续实现租户级网络隔离、存储隔离和启动顺序编排的基础。

2.1 docker-compose.yml 骨架结构

一个标准的 Compose 文件由版本声明、服务、卷、网络四个顶层块组成。以下是最小化但完整的骨架:

version: "3.9"

services:
  app:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - app_data:/usr/share/nginx/html
    networks:
      - frontend

volumes:
  app_data:

networks:
  frontend:
    driver: bridge

各顶层块的作用如下表所示:

顶层块 作用 多租户场景关注点
services 定义每个容器的镜像、端口、挂载、网络、命令等 每个租户对应一组独立 service 或通过变量复用同一 service 模板
volumes 声明持久化存储卷,可指定驱动、驱动选项、外部卷 租户数据必须使用独立卷,避免跨租户数据泄露
networks 定义容器间通信网络,支持 bridge、overlay、macvlan 等驱动 每个租户应有独立网络栈,默认 bridge 无法满足隔离需求
configs / secrets (Swarm 模式)注入配置文件和敏感信息 租户密钥应通过 secret 机制分发,避免明文写入镜像
最佳实践:始终显式声明 networks 和 volumes,即使只使用默认值。这为后续扩展租户隔离策略保留了清晰的配置入口。

2.2 services 定义详解

services 块是 Compose 的核心,每个 service 对应一个或多个容器实例。以下是生产级 service 定义的常用字段:

services:
  web:
    image: myapp:latest
    container_name: tenant-${TENANT_ID}-web
    ports:
      - "${HOST_PORT}:80"
    environment:
      - DB_HOST=db
      - TENANT_ID=${TENANT_ID}
    volumes:
      - web_data:/var/www/html
    networks:
      - tenant_net
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: "256M"

关键字段说明:

  • image:指定镜像来源,支持私有仓库地址,多租户场景建议使用带租户标签的镜像版本。
  • container_name:显式命名容器,便于日志和监控定位,通常结合环境变量动态生成。
  • ports:端口映射,多租户时必须使用不同宿主机端口,通过变量 ${HOST_PORT} 避免冲突。
  • environment:注入环境变量,是传递租户标识、数据库连接串的主要方式。
  • volumes:挂载命名卷或绑定挂载,租户数据卷必须隔离。
  • networks:指定容器加入的网络,每个租户应有独立网络。
  • depends_on:控制启动顺序,配合 healthcheck 实现真正的就绪等待。
  • healthcheck:定义容器健康检测命令,是 depends_on 条件判断的依据。
  • deploy:Swarm 模式下的扩缩容和资源限制,单机 Compose 中部分字段仅作声明。

2.3 volumes 定义与租户数据隔离

volumes 块用于声明持久化存储。在多租户架构中,每个租户必须拥有独立的卷,避免数据交叉访问。以下是卷定义的三种常见模式:

模式 定义方式 适用场景 隔离级别
匿名卷 volumes: - /data 临时数据,不关心持久化位置 低(Docker 自动生成哈希名称)
命名卷 volumes: - app_data:/data 需要显式管理的数据,如数据库存储 高(卷名可包含租户 ID)
绑定挂载 volumes: - ./data:/data 开发环境或需要直接访问宿主机文件 中(依赖路径唯一性)

租户隔离的卷命名策略示例:

volumes:
  tenant_a_db_data:
    driver: local
  tenant_b_db_data:
    driver: local
  tenant_a_upload_data:
    driver: local
    driver_opts:
      type: none
      device: /mnt/ssd/tenant_a/uploads
      o: bind
注意:绑定挂载虽然灵活,但在多租户场景中容易因路径冲突或权限设置不当导致跨租户访问。生产环境优先使用命名卷,并配合卷驱动实现存储配额和加密。

2.4 networks 定义与租户网络隔离

networks 块定义容器间的通信拓扑。默认的 bridge 网络无法满足多租户隔离需求,必须为每个租户创建独立的网络栈。以下是常用网络驱动对比:

驱动 隔离性 跨主机 多租户适用性
bridge 中等(通过子网隔离) 否 单机多租户,每个租户一个 bridge
overlay 高(VXLAN 封装) 是 Swarm 集群多租户,推荐生产使用
macvlan 高(独立 MAC/IP) 是(需物理网络支持) 需要容器直接暴露在物理网络时使用
ipvlan 高(共享 MAC) 是 macvlan 的轻量替代,适合大规模租户

租户网络定义示例:

networks:
  tenant_a_net:
    driver: bridge
    ipam:
      config:
        - subnet: "172.20.1.0/24"
          gateway: "172.20.1.1"
  tenant_b_net:
    driver: bridge
    ipam:
      config:
        - subnet: "172.20.2.0/24"
          gateway: "172.20.2.1"

每个租户的 service 通过 networks: - tenant_x_net 加入对应网络,确保租户 A 的容器无法直接访问租户 B 的容器。

Docker Compose 多租户网络隔离架构 Docker Compose 多租户网络隔离架构 宿主机 / Docker Host 租户 A 网络栈 tenant_a_net (bridge) 子网: 172.20.1.0/24 web-a 172.20.1.2 db-a 172.20.1.3 卷: tenant_a_db_data / tenant_a_upload_data 租户 B 网络栈 tenant_b_net (bridge) 子网: 172.20.2.0/24 web-b 172.20.2.2 db-b 172.20.2.3 卷: tenant_b_db_data / tenant_b_upload_data X 隔离

2.5 depends_on 与健康检查

depends_on 控制服务启动顺序,但默认只等待容器启动(service_started),不等待服务就绪。生产环境必须配合 healthcheck 使用 condition: service_healthy 实现真正的就绪等待。

三种 depends_on 条件对比:

条件 行为 适用场景
service_started 仅等待容器进程启动(默认) 无依赖或依赖方启动极快
service_healthy 等待健康检查通过 数据库、缓存等需要就绪后才能接受连接的服务
service_completed_successfully 等待容器正常退出(exit 0) 初始化任务、数据库迁移等一次性作业

完整示例:

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  app:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
    environment:
      DB_HOST: db
关键点:没有 healthcheck 的 depends_on 只是“启动顺序保证”,不是“就绪保证”。在数据库迁移或缓存预热场景中,必须显式定义 healthcheck 并设置合理的 start_period 避免误判。

2.6 环境变量注入的四种方式

环境变量是向容器传递租户配置、数据库连接、密钥等动态信息的主要手段。Compose 支持以下四种注入方式:

方式 语法 优先级 多租户场景推荐
硬编码 environment: - KEY=value 最高 不推荐,泄露敏感信息且无法复用
宿主变量引用 environment: - KEY=${VAR_NAME} 中 适合通过 .env 文件或 shell 导出变量
env_file env_file: ./tenant_a.env 低 每个租户一个 env 文件,结构清晰
Docker Secrets secrets: - db_password 中(Swarm 模式) 生产环境敏感信息,加密存储

推荐的多租户 env_file 策略:

# 目录结构
config/
  tenant_a.env
  tenant_b.env

# tenant_a.env 内容
TENANT_ID=a
DB_NAME=tenant_a_db
DB_USER=tenant_a_user
DB_PASSWORD=pass_a_123
REDIS_DB=0

# docker-compose.yml 引用
services:
  app:
    env_file:
      - ./config/tenant_${TENANT_ID}.env
提示:使用 env_file 时,Compose 会按文件顺序加载,后加载的变量会覆盖先加载的同名变量。将公共变量放在基础 env 文件中,租户特有变量放在租户文件中,实现分层配置。
Docker Compose 环境变量注入流程 Docker Compose 环境变量注入流程 变量来源 .env 文件 Shell 环境变量 env_file 文件 Docker Secrets docker-compose.yml 变量替换 ${VAR} 优先级:硬编码 > Shell > .env > env_file Secrets 通过文件挂载注入 容器运行时 环境变量表 TENANT_ID / DB_HOST / DB_PASSWORD Secrets 文件 /run/secrets/db_password 应用读取配置

2.7 本章小结

Docker Compose 的核心回顾为多租户隔离栈的构建奠定了语法和概念基础。需要牢记的关键点:

  • services 是容器模板,通过环境变量和卷挂载实现租户差异化配置。
  • volumes 必须按租户独立命名,避免数据交叉。
  • networks 必须按租户独立创建,使用不同子网实现二层隔离。
  • depends_on + healthcheck 是生产级启动顺序控制的标配。
  • 环境变量注入 优先使用 env_file 配合 .env 文件,敏感信息使用 Docker Secrets。