3、Docker Compose核心概念:服务、项目、编排、配置文件结构、常用命令与生命周期管理

在掌握了Docker基础容器操作之后,面对多模型推理服务的部署场景——例如同时启动一个LLM推理服务、一个向量化嵌入服务、一个模型网关以及配套的Redis缓存——如果仍然逐个使用docker run命令,不仅命令冗长易错,而且难以管理服务间的网络依赖与启动顺序。Docker Compose正是为解决这一痛点而生的容器编排工具。本章将深入剖析Compose的五大核心概念:服务(Service)、项目(Project)、编排(Orchestration)、配置文件结构(Compose File)以及生命周期管理,帮助您建立从单容器到多容器协作的思维跃迁。

Docker Compose 核心概念关系与生命周期 项目(Project)— 由 docker-compose.yml 所在目录名定义 服务 A 模型网关 image: gateway:v1 ports: 8080:80 服务 B LLM 推理 image: llm:v2 deploy: gpu 服务 C 向量嵌入 image: embed:v3 env: MODEL=... HTTP gRPC docker-compose.yml(配置文件结构) version / services / networks / volumes / configs / secrets 编排 创建/启动/网络 生命周期管理 up ps logs down docker compose up -d docker compose ps docker compose logs -f docker compose down 核心:服务是单元,项目是边界,编排是手段,配置文件是蓝图,生命周期是过程

3.1 服务(Service)与项目(Project)的本质

核心原理:从容器到服务的抽象

在Compose语境中,服务(Service)是对一组功能相同、配置相同的容器实例的抽象定义。它描述的是“运行什么镜像、开放哪些端口、挂载哪些卷、依赖哪些环境变量”,而不是某个具体的容器ID。例如,在模型推理场景中,您可以定义一个名为llm-server的服务,指定镜像registry.example.com/llm:v2,并设置NVIDIA_VISIBLE_DEVICES=all环境变量。当执行docker compose up时,Compose会根据该服务定义创建1个或多个容器副本(通过replicas参数控制)。

项目(Project)则是多个服务的逻辑集合,其默认名称取自docker-compose.yml文件所在目录的基名。例如,在/opt/modelserving目录下执行Compose命令,项目名即为modelserving。项目为所有资源(容器、网络、卷)提供了命名空间前缀,例如网络会被命名为modelserving_default,卷命名为modelserving_model-cache。这种隔离机制允许多个项目在同一台宿主机上并行运行而互不干扰。

判断方法:如何区分服务与项目

  • 服务是“做什么”:关注镜像、命令、端口、资源限制等运行细节。
  • 项目是“属于谁”:关注目录边界、命名空间、资源归属。
  • 执行docker compose ls可查看当前所有项目及其状态;执行docker compose ps仅查看当前项目内的服务。
维度服务(Service)项目(Project)
定义位置compose文件中的services节点compose文件所在目录名
粒度单个应用组件(如API网关)一组关联服务的集合
命名规则由用户自定义(如web)默认目录名,可用-p覆盖
资源归属容器、网络别名网络、卷、容器统一前缀
操作命令docker compose up 服务名docker compose up(全部)

案例拆解:多模型推理项目结构

假设您要部署一个包含三个服务的推理平台:gateway(流量入口)、llm(大语言模型)、embedding(向量嵌入)。在/opt/ai-platform目录下创建docker-compose.yml,项目名自动为ai-platform。执行docker compose up -d后,Compose会创建ai-platform_gateway_1、ai-platform_llm_1、ai-platform_embedding_1三个容器,以及一个名为ai-platform_default的桥接网络。服务间通过服务名(如http://llm:8000)互相访问,无需关心容器IP变化。

经验提醒:在服务定义中显式设置container_name虽然方便,但会破坏Compose的扩展性(如水平伸缩时产生冲突)。建议依赖自动生成的容器名,通过服务名进行服务发现。

3.2 编排(Orchestration)的工作机制

核心原理:声明式编排与依赖解析

编排是Compose的核心价值所在。它读取配置文件中的声明(期望状态),然后调用Docker API逐步创建网络、卷、容器,并处理服务间的依赖关系。例如,gateway服务依赖llm和embedding,Compose会先启动被依赖的服务,再启动依赖方。这种依赖通过depends_on字段声明,但需要注意:depends_on仅控制启动顺序,不等待服务内部就绪(如模型加载完成)。

操作流程:编排的四个阶段

  1. 解析:读取docker-compose.yml,校验语法与字段合法性。
  2. 规划:计算需要创建/更新/删除的资源,生成执行计划。
  3. 执行:按依赖顺序创建网络、卷、拉取镜像、启动容器。
  4. 收敛:持续监控容器状态,尝试恢复异常退出的容器(除非设置了restart: "no")。

实战场景:健康检查与依赖就绪

对于模型推理服务,模型加载可能耗时数十秒。若gateway在llm尚未就绪时即开始转发请求,会导致大量502错误。解决方案是结合healthcheck与depends_on的条件判断:

services:
  llm:
    image: llm:v2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 5s
      timeout: 3s
      retries: 12
  gateway:
    image: gateway:v1
    depends_on:
      llm:
        condition: service_healthy

这样编排器会等待llm的健康检查通过后,才启动gateway,确保依赖真正可用。

风险提示:不要滥用depends_on构建过长的依赖链,这会拖慢启动速度并增加故障传播面。建议将强依赖控制在两层以内,弱依赖通过重试机制处理。

3.3 配置文件结构详解

核心原理:YAML的层次化描述

docker-compose.yml采用YAML格式,顶层节点通常包含version(已废弃但可保留)、services(必填)、networks、volumes、configs、secrets。其中services是唯一必填项,每个服务键名即服务名,键值包含镜像、构建上下文、环境变量、端口映射、卷挂载等属性。

操作流程:编写配置文件的步骤

  1. 定义服务清单:列出所有需要运行的应用组件。
  2. 确定镜像来源:使用image指定远程镜像,或使用build指定本地Dockerfile。
  3. 配置网络与端口:通过ports暴露宿主机端口,通过expose仅内部通信。
  4. 声明依赖与健康检查:设置depends_on和healthcheck。
  5. 挂载存储:使用volumes持久化模型权重或日志。
  6. 设置资源限制:通过deploy.resources.limits限制CPU/内存。

案例拆解:一个完整的推理服务配置

version: "3.8"
services:
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
  llm:
    image: registry.example.com/llm:v2
    environment:
      - MODEL_NAME=/models/llama-7b
      - NUM_GPU=1
    volumes:
      - model-cache:/models
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    depends_on:
      redis:
        condition: service_healthy
  gateway:
    image: gateway:v1
    ports:
      - "8080:80"
    depends_on:
      - llm
volumes:
  redis-data:
  model-cache:

该配置定义了三个服务:Redis缓存、LLM推理、API网关。通过命名卷model-cache持久化模型文件,通过deploy.resources申请GPU资源,通过健康检查确保Redis先就绪。

关键校验:执行docker compose config可验证配置文件的语法正确性,并查看合并后的完整配置(包括默认值)。这是CI/CD流水线中必备的检查步骤。

3.4 常用命令与生命周期管理

核心原理:状态机与命令映射

Compose管理的服务生命周期包括:创建(create)→ 启动(start)→ 运行(running)→ 暂停(pause)→ 停止(stop)→ 删除(rm)。常用命令是这些状态转换的触发器。理解状态转换有助于排障:例如docker compose restart会先停止再启动,而docker compose up -d --force-recreate会强制重新创建容器以应用新配置。

操作流程:标准部署与更新流程

  1. 首次部署:docker compose up -d(后台运行)或docker compose up(前台查看日志)。
  2. 查看状态:docker compose ps显示容器状态、端口映射、健康状态。
  3. 查看日志:docker compose logs -f 服务名跟踪特定服务输出。
  4. 更新配置:修改YAML后执行docker compose up -d,Compose自动检测变更并重建受影响的服务。
  5. 停止服务:docker compose stop保留容器与数据;docker compose down删除容器和默认网络(加-v同时删除卷)。
命令作用适用场景注意事项
up -d创建并后台启动日常部署幂等,可重复执行
down停止并删除容器/网络彻底清理默认不删卷,加-v删卷
ps列出项目内容器状态检查加-a显示已停止容器
logs查看日志输出排障与监控-f持续跟踪,--tail控制行数
exec进入运行中容器调试与运维需指定服务名,如exec llm bash
pull拉取最新镜像更新前准备不重启容器

案例拆解:模型版本升级的完整闭环

背景与约束:某团队维护的LLM服务需要从v1.2升级到v1.3,要求零停机(滚动更新),且模型权重文件需保留。

初始状态:docker compose ps显示llm服务运行正常,使用镜像llm:v1.2,卷model-cache挂载于/models。

操作步骤:

  1. 修改docker-compose.yml中的镜像标签为llm:v1.3。
  2. 执行docker compose pull llm预拉取新镜像,避免更新时超时。
  3. 执行docker compose up -d --no-deps llm,仅重建llm服务,不触碰依赖服务。
  4. 观察docker compose ps确认新容器健康,docker compose logs -f llm检查模型加载日志。

结果与复盘:由于使用了--no-deps,网关服务未受影响,实现了平滑升级。若新版本启动失败,可执行docker compose up -d --force-recreate --no-deps llm回滚镜像标签。此流程避免了全量重启带来的请求中断。

常见误区:许多用户误以为docker compose down会删除数据卷。实际上,默认情况下down仅删除容器和网络,卷仍然保留。只有显式添加-v标志才会删除卷。在清理测试环境时,务必确认是否需要保留数据。

3.5 总结复盘:从概念到实践的要点

  • 服务是配置单元,项目是隔离边界:理解两者的关系是使用Compose的基石。
  • 编排的核心是声明式管理:您描述期望状态,Compose负责收敛到该状态。
  • 配置文件是唯一事实来源:所有变更都应通过修改YAML并执行up完成,避免手动docker run造成漂移。
  • 生命周期命令需谨慎:down -v是危险操作,会删除所有关联卷;restart与up --force-recreate有本质区别。
  • 健康检查是生产环境的必备项:没有健康检查的依赖管理只是“伪编排”。

掌握这些核心概念后,您已经具备阅读和理解绝大多数Compose文件的能力。下一阶段将进入实际部署环节,学习如何编写适用于GPU推理场景的Compose配置,并处理设备映射、资源预留等高级主题。