第09章:Docker Compose 编排

第09章:Docker Compose 编排

本章目标:掌握 Docker Compose 的语法和使用,学会编排多容器应用,实现一键部署和管理。


9.1 Docker Compose 是什么

9.1.1 定义与价值

Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件,你可以配置应用的所有服务、网络和存储卷,然后用一个命令创建并启动所有服务。

复制代码
传统方式(多个终端分别操作):
终端1: docker run -d --name db mysql
终端2: docker run -d --name redis redis
终端3: docker run -d --name app myapp
终端4: docker network connect ...
终端5: docker run -d --name nginx nginx

Docker Compose 方式(一个文件,一条命令):
docker compose up -d

9.1.2 Compose 的优势

优势 说明
声明式配置 一个 YAML 文件描述整个应用架构
一键启停 docker compose up/down 管理所有服务
环境隔离 每个 compose 项目独立的网络和卷
开发/测试/生产 同一配置适配不同环境
可版本控制 compose 文件可以纳入 Git 管理

9.2 Compose 文件语法详解

9.2.1 文件结构

yaml 复制代码
# docker-compose.yml 完整结构

version: '3.8'  # Compose 文件版本

services:       # 服务定义(容器配置)
  web:
    # ... 服务配置
  db:
    # ... 服务配置

networks:       # 自定义网络
  frontend:
  backend:

volumes:        # 声明命名卷
  db-data:
  cache-data:

configs:        # 配置对象
  app-config:
    file: ./config.yml

secrets:        # 敏感信息
  db-password:
    file: ./secrets/db_password.txt

9.2.2 services 配置详解

yaml 复制代码
services:
  # ========== 服务名称 ==========
  web-app:
    # 基本配置
    image: myapp:latest                    # 使用镜像
    # 或从 Dockerfile 构建
    build:
      context: .                           # 构建上下文
      dockerfile: Dockerfile               # Dockerfile 文件名
      args:                                # 构建参数
        VERSION: 1.0.0
      target: production                   # 多阶段构建的目标阶段
    
    container_name: my-web-app             # 容器名称
    hostname: web-server                   # 主机名
    restart: unless-stopped                # 重启策略
    
    # 环境变量
    environment:
      - APP_ENV=production
      - DB_HOST=db
      - DB_PORT=3306
      - REDIS_HOST=redis
    
    # 从文件加载环境变量
    env_file:
      - .env
      - .env.production
    
    # 端口映射
    ports:
      - "80:80"
      - "443:443"
      # - "127.0.0.1:8080:80"    # 仅本地访问
      # - "8080:80/tcp"          # 指定协议
    
    # 卷挂载
    volumes:
      - ./src:/app/src:ro              # 只读挂载
      - app-logs:/app/logs             # 命名卷
      - /data/images:/app/images       # 绝对路径
    
    # 网络
    networks:
      - frontend
      - backend
    
    # 资源限制
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M
      replicas: 2                       # 副本数
      restart_policy:
        condition: on-failure
        max_attempts: 3
    
    # 健康检查
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    
    # 依赖关系
    depends_on:
      db:
        condition: service_healthy      # 等待依赖服务健康
      redis:
        condition: service_started
    
    # 链接(旧语法,不推荐)
    links:
      - db
      - redis
    
    # 别名(DNS 名称)
    aliases:
      - web
      - application
    
    # 日志配置
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    
    # 安全选项
    security_opt:
      - no-new-privileges:true
    read_only: true                     # 只读文件系统
    tmpfs:
      - /tmp
      - /var/run
    
    # 容器内命令
    command: ["python3", "app.py"]
    # 或
    # command: python3 app.py
    
    # 入口点
    entrypoint: ["/app/entrypoint.sh"]
    
    # 工作目录
    working_dir: /app
    
    # 用户
    user: "1000:1000"
    
    # 其他容器
    extra_hosts:
      - "other-host:192.168.1.100"
    
    # PID 模式
    pid: "host"
    
    # 配置文件
    configs:
      - source: app-config
        target: /app/config.yml
        mode: 0444

9.2.3 networks 配置详解

yaml 复制代码
networks:
  # 默认 bridge 网络
  default:
    driver: bridge
  
  # 自定义网络
  frontend:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: "frontend-br"
    ipam:
      driver: default
      config:
        - subnet: 192.168.100.0/24
          gateway: 192.168.100.1
    labels:
      - "com.example.network.frontend=true"
  
  # Overlay 网络(用于 Swarm)
  backend:
    driver: overlay
    attachable: true
  
  # 外部网络(使用已存在的网络)
  existing-network:
    external: true

9.2.4 volumes 配置详解

yaml 复制代码
volumes:
  # 命名卷(Docker 管理)
  db-data:
    driver: local
    driver_opts:
      type: none
      device: /data/mysql
      o: bind
  
  # 带标签的卷
  app-logs:
    labels:
      com.example.description: "Application logs"
      com.example.department: "Operations"
  
  # 外部卷(使用已存在的卷)
  existing-volume:
    external: true
  
  # tmpfs 卷
  temp-cache:
    driver: local
    driver_opts:
      type: tmpfs
      device: tmpfs
      o: size=100m,uid=1000

9.3 Compose 常用命令

9.3.1 服务管理

bash 复制代码
# 启动所有服务
docker compose up

# 后台启动
docker compose up -d

# 重建并启动(有变更时)
docker compose up -d --build

# 停止并删除容器
docker compose down

# 停止并删除容器和卷
docker compose down -v

# 查看服务状态
docker compose ps

# 查看服务日志
docker compose logs

# 实时跟踪日志
docker compose logs -f

# 查看特定服务日志
docker compose logs -f web-app

# 查看服务资源使用
docker compose top

9.3.2 单个服务操作

bash 复制代码
# 启动特定服务
docker compose up -d db

# 停止特定服务
docker compose stop web-app

# 重启特定服务
docker compose restart web-app

# 进入服务容器
docker compose exec web-app bash
docker compose exec db mysql -uroot -p

# 查看服务进程
docker compose top web-app

9.3.3 构建相关

bash 复制代码
# 构建所有服务
docker compose build

# 构建特定服务
docker compose build web-app

# 不使用缓存构建
docker compose build --no-cache

# 拉取最新镜像
docker compose pull

# 推送镜像
docker compose push

9.3.4 镜像和卷管理

bash 复制代码
# 列出项目中的镜像
docker compose images

# 删除停止的容器
docker compose rm

# 列出项目中的卷
docker compose volume ls

# 删除未使用的卷
docker compose down -v

9.4 环境变量管理

9.4.1 多种设置方式

yaml 复制代码
services:
  web-app:
    # 方式1:直接在 compose 文件中设置
    environment:
      APP_ENV: production
      DB_HOST: db
    
    # 方式2:从文件加载
    env_file:
      - .env
    
    # 方式3:使用变量替换(compose 文件中)
    image: myapp:${VERSION:-latest}
    
    # 方式4:在容器内使用
    command: python3 app.py

9.4.2 .env 文件示例

bash 复制代码
# .env 文件
APP_ENV=production
APP_VERSION=1.0.0
DB_ROOT_PASSWORD=supersecret
DB_NAME=myapp
DB_USER=appuser
DB_PASSWORD=apppass123
REDIS_PASSWORD=redis123

9.4.3 多环境配置

bash 复制代码
# 项目结构
project/
├── docker-compose.yml           # 基础配置
├── docker-compose.override.yml  # 开发环境覆盖
├── docker-compose.prod.yml      # 生产环境配置
├── .env                         # 默认环境变量
├── .env.dev                     # 开发环境变量
└── .env.prod                    # 生产环境变量

# 开发环境(默认)
docker compose up -d

# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 或使用环境变量
COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml docker compose up -d

9.5 完整的多容器应用示例

9.5.1 LAMP 架构(Linux + Apache + MySQL + PHP)

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # Web 服务器
  apache:
    image: php:8.2-apache
    container_name: lamp-apache
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./src:/var/www/html
      - ./apache/conf.d:/etc/apache2/sites-enabled
    networks:
      - lamp-network
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      - DB_HOST=mysql
      - DB_NAME=${DB_NAME:-laravel}
      - DB_USER=${DB_USER:-user}
      - DB_PASSWORD=${DB_PASSWORD:-password}
    restart: unless-stopped

  # MySQL 数据库
  mysql:
    image: mysql:8.0
    container_name: lamp-mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpassword}
      MYSQL_DATABASE: ${DB_NAME:-laravel}
      MYSQL_USER: ${DB_USER:-user}
      MYSQL_PASSWORD: ${DB_PASSWORD:-password}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d
    networks:
      - lamp-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: lamp-redis
    command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}
    volumes:
      - redis-data:/data
    networks:
      - lamp-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

networks:
  lamp-network:
    driver: bridge

volumes:
  mysql-data:
  redis-data:

9.5.2 Python + Nginx + Gunicorn + PostgreSQL + Redis

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: app-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static-files:/app/static:ro
    networks:
      - app-network
    depends_on:
      - web
    restart: unless-stopped

  # Python Web 应用
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    container_name: app-web
    volumes:
      - static-files:/app/static
      - ./logs:/app/logs
    environment:
      - APP_ENV=production
      - DATABASE_URL=postgresql+asyncpg://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
    networks:
      - app-network
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  # PostgreSQL 数据库
  postgres:
    image: postgres:15-alpine
    container_name: app-postgres
    environment:
      POSTGRES_DB: ${DB_NAME:-myapp}
      POSTGRES_USER: ${DB_USER:-appuser}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-apppass123}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-appuser}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: app-redis
    command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}
    volumes:
      - redis-data:/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis123}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

networks:
  app-network:
    driver: bridge

volumes:
  static-files:
  postgres-data:
  redis-data:

9.5.3 Node.js + MongoDB + Redis

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # Node.js 应用
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: node-app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGODB_URI=mongodb://mongo:27017/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./uploads:/app/uploads
    networks:
      - node-network
    depends_on:
      mongo:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  # MongoDB 数据库
  mongo:
    image: mongo:6
    container_name: node-mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-admin}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-password}
    volumes:
      - mongo-data:/data/db
      - mongo-config:/data/configdb
    networks:
      - node-network
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: node-redis
    command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}
    volumes:
      - redis-data:/data
    networks:
      - node-network
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis123}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

networks:
  node-network:
    driver: bridge

volumes:
  mongo-data:
  mongo-config:
  redis-data:

9.6 Compose 高级特性

9.6.1 服务扩缩容

bash 复制代码
# 扩容服务(启动多个实例)
docker compose up -d --scale web=3

# 缩容
docker compose up -d --scale web=1

# 注意:使用 scale 时不要映射固定端口
# 应该使用随机端口或负载均衡器

9.6.2 服务依赖与启动顺序

yaml 复制代码
services:
  db:
    image: mysql:8.0
    # ...

  redis:
    image: redis:7
    # ...

  web:
    image: myapp
    depends_on:
      db:
        condition: service_healthy     # 等待数据库健康
      redis:
        condition: service_started     # 只需 Redis 启动
    # ...

9.6.3 配置文件挂载

yaml 复制代码
services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx-conf
        target: /etc/nginx/conf.d/default.conf
        mode: 0444

configs:
  nginx-conf:
    file: ./nginx/default.conf

9.6.4 敏感信息管理

yaml 复制代码
services:
  db:
    image: mysql:8.0
    secrets:
      - db-password
    environment:
      MYSQL_PASSWORD_FILE: /run/secrets/db-password

secrets:
  db-password:
    file: ./secrets/db_password.txt

9.7 动手实验

实验 9.1:WordPress 一键部署

bash 复制代码
# 创建项目目录
mkdir -p ~/docker-lab/wordpress
cd ~/docker-lab/wordpress

# 创建 docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-data:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  db-data:
  wp-data:
EOF

# 启动
docker compose up -d

# 访问 http://localhost:8080 完成 WordPress 安装

实验 9.2:多服务应用

bash 复制代码
# 使用上面的 Python + Nginx + PostgreSQL + Redis 示例
cd ~/docker-lab

# 创建项目结构
mkdir -p nginx/conf.d

# 创建 Nginx 配置
cat > nginx/conf.d/default.conf << 'EOF'
upstream web {
    server web:8000;
}

server {
    listen 80;
    server_name localhost;

    location /static/ {
        alias /app/static/;
    }

    location / {
        proxy_pass http://web;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

# 启动所有服务
docker compose up -d

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs -f

9.8 本章小结

命令 说明
docker compose up -d 后台启动所有服务
docker compose down 停止并删除所有服务
docker compose ps 查看服务状态
docker compose logs -f 实时跟踪日志
docker compose exec <svc> bash 进入服务容器
docker compose restart <svc> 重启指定服务
docker compose build 构建服务镜像
docker compose pull 拉取最新镜像

9.9 课后练习

  1. 基础题:使用 Docker Compose 部署 WordPress + MySQL。
  2. 进阶题:编写一个包含 Web、数据库、缓存、反向代理的完整 compose 文件。
  3. 实践题:使用多环境配置(dev/prod)管理不同的部署环境。

📖 下一章:企业级镜像仓库 ------ 搭建和管理私有镜像仓库