音视频开发实战

第30章:Docker容器化部署

本章目标:掌握使用Docker容器化部署直播系统,包括镜像构建、网络配置和服务编排。

容器化是现代应用部署的标准方式。本章介绍如何将SFU/MCU等直播组件打包为Docker镜像并部署。


目录

  1. Docker基础
  2. 容器vs虚拟机详细对比
  3. Docker镜像分层原理
  4. SFU/MCU镜像构建
  5. Docker网络模型详解
  6. 持久化存储原理
  7. Docker Compose编排
  8. 生产环境配置
  9. 本章总结

1. Docker基础

1.1 核心概念

概念 说明 类比
镜像 (Image) 应用运行环境模板 类
容器 (Container) 镜像的运行实例 对象
仓库 (Registry) 存储和分发镜像 GitHub
Dockerfile 定义镜像构建步骤 Makefile
Volume 持久化数据存储 外部硬盘

1.2 Dockerfile最佳实践

# SFU Dockerfile
FROM ubuntu:22.04

# 避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive

# 安装依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    libssl-dev \
    libavcodec-dev \
    libavformat-dev \
    libavutil-dev \
    && rm -rf /var/lib/apt/lists/*

# 创建应用目录
WORKDIR /app

# 复制源码
COPY src/ ./src/
COPY CMakeLists.txt .

# 编译
RUN mkdir build && cd build \
    && cmake .. -DCMAKE_BUILD_TYPE=Release \
    && make -j$(nproc)

# 运行时阶段(多阶段构建,减小镜像)
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    libssl3 \
    libavcodec58 \
    libavformat58 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 从构建阶段复制二进制
COPY --from=0 /app/build/sfu_server .
COPY --from=0 /app/build/config.yaml ./config/

# 暴露端口
EXPOSE 8080/tcp  # HTTP API
EXPOSE 3478/tcp  # TURN TCP
EXPOSE 3478/udp  # TURN UDP
EXPOSE 10000-20000/udp  # RTP端口范围

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 运行
ENTRYPOINT ["./sfu_server"]
CMD ["-c", "config/config.yaml"]

1.3 镜像构建与推送

# 构建镜像
docker build -t your-registry/sfu-server:v1.0.0 .

# 本地测试运行
docker run -d \
  --name sfu \
  -p 8080:8080 \
  -p 3478:3478/tcp \
  -p 3478:3478/udp \
  -p 10000-10100:10000-10100/udp \
  your-registry/sfu-server:v1.0.0

# 推送镜像到仓库
docker login your-registry.com
docker push your-registry/sfu-server:v1.0.0

2. 容器vs虚拟机详细对比

2.1 架构差异

传统虚拟机架构:

┌─────────────────────────────────────────────┐
│  VM 1: Ubuntu          VM 2: CentOS        │
│  ┌─────────────┐       ┌─────────────┐     │
│  │ App A       │       │ App B       │     │
│  │ Bin/Libs    │       │ Bin/Libs    │     │
│  ├─────────────┤       ├─────────────┤     │
│  │ Guest OS    │       │ Guest OS    │     │
│  │ (内核+系统)  │       │ (内核+系统)  │     │
│  └─────────────┘       └─────────────┘     │
├─────────────────────────────────────────────┤
│  Hypervisor (VMware/KVM/Hyper-V)            │
├─────────────────────────────────────────────┤
│  Host OS (Linux/Windows)                    │
├─────────────────────────────────────────────┤
│  Hardware                                   │
└─────────────────────────────────────────────┘

Docker容器架构:

┌─────────────────────────────────────────────┐
│  Container 1            Container 2         │
│  ┌─────────────┐       ┌─────────────┐     │
│  │ App A       │       │ App B       │     │
│  │ Bin/Libs    │       │ Bin/Libs    │     │
│  └─────────────┘       └─────────────┘     │
│                                             │
│  ┌─────────────────────────────────────────┐│
│  │ Docker Engine (容器运行时管理)           ││
│  │  • 进程隔离 (Namespace)                  ││
│  │  • 资源限制 (Cgroup)                     ││
│  │  • 文件系统隔离 (UnionFS)                ││
│  └─────────────────────────────────────────┘│
├─────────────────────────────────────────────┤
│  Host OS (共享内核)                          │
│  ┌─────────────────────────────────────────┐│
│  │ Kernel (进程调度、内存管理、网络栈、文件系统)││
│  └─────────────────────────────────────────┘│
├─────────────────────────────────────────────┤
│  Hardware                                   │
└─────────────────────────────────────────────┘

2.2 资源占用对比

维度 虚拟机 容器 容器优势
启动时间 分钟级 (30s-2min) 秒级 (1-5s) 快速扩缩容
磁盘占用 GB级 (5-20GB) MB级 (50-500MB) 存储效率高
内存开销 高(完整OS) 低(共享内核) 单机可运行更多实例
CPU开销 5-15%(虚拟化) 接近原生 性能更好
密度 10-100/主机 100-1000/主机 资源利用率高
资源占用对比(运行相同应用):

虚拟机部署:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│   Guest OS 2GB  │ │   Guest OS 2GB  │ │   Guest OS 2GB  │
│   + App 100MB   │ │   + App 100MB   │ │   + App 100MB   │
│   = 2.1GB       │ │   = 2.1GB       │ │   = 2.1GB       │
└─────────────────┘ └─────────────────┘ └─────────────────┘
总计: 6.3GB内存 + 3个完整内核

容器部署:
┌─────────────────────────────────────────────────────────┐
│  Host OS Kernel (共享)                                  │
│  ┌───────────┐ ┌───────────┐ ┌───────────┐             │
│  │ App 100MB │ │ App 100MB │ │ App 100MB │  (隔离)      │
│  │ + Libs    │ │ + Libs    │ │ + Libs    │             │
│  │ = 150MB   │ │ = 150MB   │ │ = 150MB   │             │
│  └───────────┘ └───────────┘ └───────────┘             │
└─────────────────────────────────────────────────────────┘
总计: ~450MB内存 + 1个内核

2.3 隔离性对比

隔离维度 虚拟机 容器 说明
进程隔离 强(独立内核) 中(Namespace) 容器共享宿主机内核
文件系统隔离 完整 完整(UnionFS) 两者都完整
网络隔离 虚拟网卡 虚拟网卡/桥接 两者都支持
资源限制 硬限制 Cgroup软/硬限制 容器更灵活
安全边界 强(硬件级) 中(软件级) 虚拟机逃逸更难

2.4 适用场景对比

场景 推荐 理由
多租户严格隔离 VM 更强的安全边界
不同操作系统需求 VM 容器共享宿主机内核
快速CI/CD 容器 秒级启动,镜像版本控制
微服务部署 容器 轻量、高密度、编排友好
遗留系统迁移 VM 完整OS环境兼容
云原生应用 容器 与K8s等编排平台无缝集成

3. Docker镜像分层原理

3.1 UnionFS与分层存储

镜像分层的核心概念:

Docker镜像由多个只读层(Layer)组成,容器运行时在顶部添加一个可写层(Container Layer):

容器运行时视图:
┌─────────────────────────────────────┐
│  Container Layer (可写层)           │  ← 容器内修改都在这里
│  (写时复制 Copy-on-Write)          │
├─────────────────────────────────────┤
│  Layer 3: ADD app.py /app/          │  ← 应用代码层
├─────────────────────────────────────┤
│  Layer 2: RUN pip install -r ...    │  ← 依赖层
├─────────────────────────────────────┤
│  Layer 1: RUN apt-get install ...   │  ← 系统工具层
├─────────────────────────────────────┤
│  Layer 0: FROM ubuntu:20.04         │  ← 基础镜像层
└─────────────────────────────────────┘

所有层合并后的视图:
/                   ← 文件系统根
├── app/
│   └── app.py      ← Layer 3
├── usr/
│   ├── bin/
│   │   └── python3 ← Layer 2
│   └── lib/...     ← Layer 2
├── bin/...         ← Layer 1
└── etc/...         ← Layer 0

3.2 写时复制(Copy-on-Write)

CoW机制工作原理:

场景: 修改基础镜像中的文件 /etc/nginx/nginx.conf

1. 读取阶段:
   容器请求读取 /etc/nginx/nginx.conf
   ↓
   从Layer 0读取(只读)

2. 写入阶段(首次修改):
   容器请求修改 /etc/nginx/nginx.conf
   ↓
   CoW触发:
   a) 从Layer 0复制文件到Container Layer
   b) 在Container Layer中修改
   c) 后续读写都从Container Layer进行

3. 删除文件:
   容器请求删除 /etc/nginx/nginx.conf
   ↓
   在Container Layer中创建"删除标记"
   (实际文件仍然存在于镜像层,只是被隐藏)

3.3 分层缓存优化

Dockerfile指令与层的关系:

# 每条指令创建一个新层
FROM ubuntu:20.04              # Layer 0: 基础镜像

RUN apt-get update && \        # Layer 1
    apt-get install -y python3 # 系统依赖

WORKDIR /app                   # 元数据层(通常不计入)

COPY requirements.txt .        # Layer 2: 依赖列表
RUN pip install -r ...         # Layer 3: Python依赖

COPY src/ .                    # Layer 4: 应用代码

CMD ["python", "app.py"]       # 元数据层

缓存优化最佳实践:

原则 说明 示例
不常变的放上层 基础依赖先安装 FROM → RUN apt → …
常变的放下层 代码最后复制 COPY src 放最后
合并RUN指令 减少层数 RUN apt-get … && …
精确COPY 只复制必要文件 COPY requirements.txt 而非 .
# 优化前(缓存效率低)
FROM ubuntu:20.04
COPY . /app           # 代码变更会 invalidated 所有后续层
RUN apt-get update && apt-get install -y python3
RUN pip install -r /app/requirements.txt
RUN apt-get install -y ffmpeg

# 优化后(缓存效率高)
FROM ubuntu:20.04
# 1. 系统依赖(很少变化)
RUN apt-get update && apt-get install -y \
    python3 python3-pip ffmpeg \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 2. Python依赖(偶尔变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 3. 应用代码(频繁变化)
COPY src/ ./src/

CMD ["python", "-m", "src.main"]

3.4 镜像大小优化

多阶段构建:

# 构建阶段(包含编译工具,体积大)
FROM gcc:11 AS builder
WORKDIR /build
COPY src/ ./
RUN make -j$(nproc)  # 编译

# 运行阶段(仅包含运行时依赖,体积小)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 只复制编译结果
COPY --from=builder /build/sfu_server .
COPY config.yaml ./

EXPOSE 8080 3478/udp
CMD ["./sfu_server"]

镜像大小对比:

构建方式 基础镜像 最终大小 说明
单阶段 gcc:11 ~2GB 包含完整编译环境
多阶段 ubuntu:22.04 ~100MB 仅运行时依赖
多阶段+alpine alpine:latest ~50MB 最小化基础镜像
多阶段+distroless gcr.io/distroless ~30MB Google最小镜像

4. SFU/MCU镜像构建

4.1 精简镜像技巧

# 使用更小的基础镜像
FROM alpine:3.18 AS runtime

# Alpine使用musl libc,体积更小
RUN apk add --no-cache \
    libstdc++ \
    openssl \
    ca-certificates

# 或者使用distroless(Google维护的最小镜像)
FROM gcr.io/distroless/cc-debian11

# distroless没有shell,更安全
COPY --from=builder /app/sfu_server /
ENTRYPOINT ["/sfu_server"]

镜像大小对比: | 基础镜像 | 最终大小 | 特点 | |:—|:—:|:—| | ubuntu:22.04 | ~200MB | 功能完整 | | alpine:3.18 | ~50MB | 轻量 | | distroless | ~30MB | 最小攻击面 |

4.2 多服务镜像

# 多服务统一部署
FROM ubuntu:22.04

WORKDIR /app

# 复制所有服务
COPY sfu/sfu_server ./
COPY mcu/mcu_server ./
COPY signaling/signaling_server ./
COPY dashboard/dashboard ./

# 复制启动脚本
COPY scripts/start.sh ./
RUN chmod +x start.sh

# 环境变量指定启动哪个服务
ENV SERVICE_TYPE=sfu

EXPOSE 8080 8081 8082 3000

ENTRYPOINT ["./start.sh"]
# start.sh
#!/bin/bash

case $SERVICE_TYPE in
    sfu)
        exec ./sfu_server "$@"
        ;;
    mcu)
        exec ./mcu_server "$@"
        ;;
    signaling)
        exec ./signaling_server "$@"
        ;;
    dashboard)
        exec ./dashboard "$@"
        ;;
    *)
        echo "Unknown service type: $SERVICE_TYPE"
        exit 1
        ;;
esac

5. Docker网络模型详解

5.1 Linux网络基础

容器的网络本质:

Docker利用Linux内核的网络特性实现容器网络:

┌─────────────────────────────────────────────────────────┐
│  Namespace (网络隔离)                                    │
│  每个容器拥有独立的:                                       │
│  • 网络接口 (eth0, lo)                                    │
│  • 路由表                                                  │
│  • iptables规则                                            │
│  • Socket端口空间                                          │
└─────────────────────────────────────────────────────────┘

容器网络 ↔ 宿主机网络的连接方式:
┌─────────────────────────────────────────────────────────┐
│  容器A Namespace          容器B Namespace   宿主机        │
│  ┌─────────┐              ┌─────────┐                   │
│  │ veth0   │──────────────│ veth1   │                   │
│  │ 172.17.0.2            │ 172.17.0.3                   │
│  └────┬────┘              └────┬────┘                   │
│       │                        │                        │
│       └──────────┬─────────────┘                        │
│                  │                                      │
│            ┌─────┴─────┐                                │
│            │  docker0  │ ← Linux Bridge (虚拟交换机)      │
│            │ 172.17.0.1│                                │
│            └─────┬─────┘                                │
│                  │                                      │
│            ┌─────┴─────┐                                │
│            │  eth0     │ ← 物理网卡                      │
│            │ 10.0.0.5  │                                │
│            └───────────┘                                │
└─────────────────────────────────────────────────────────┘

核心网络组件:

组件 作用 Docker应用
Network Namespace 网络隔离 每个容器独立网络栈
veth pair 虚拟网卡对 容器与bridge的连接
Linux Bridge 虚拟交换机 docker0连接容器
iptables 包过滤/NAT 端口映射、网络隔离
路由表 包转发决策 容器间、容器外通信

5.2 Docker网络模式

五种网络模式详解:

模式 网络配置 适用场景 隔离性
bridge 独立IP,通过NAT访问外网 单机多容器 高
host 共享宿主机网络栈 高性能网络 无
none 无网络 安全隔离 最高
container 共享其他容器网络栈 Sidecar模式 共享
overlay 跨主机虚拟网络 Swarm/K8s集群 高

Bridge模式(默认):

┌─────────────────────────────────────────────────────────┐
│  宿主机                                                   │
│  ┌─────────────┐     ┌─────────────┐                    │
│  │  Container A│     │  Container B│                    │
│  │  172.17.0.2 │     │  172.17.0.3 │                    │
│  │  port 8080  │     │  port 8080  │ ← 容器内端口        │
│  └──────┬──────┘     └──────┬──────┘                    │
│         │                   │                           │
│         └─────────┬─────────┘                           │
│                   │                                     │
│            ┌──────┴──────┐                              │
│            │   docker0   │  172.17.0.1                   │
│            └──────┬──────┘                              │
│                   │                                     │
│            ┌──────┴──────┐                              │
│            │  MASQUERADE │ ← iptables NAT规则            │
│            │  port 8080  │ ← 映射到宿主机8080             │
│            └─────────────┘                              │
└─────────────────────────────────────────────────────────┘

命令:
docker run -d -p 8080:8080 sfu-server  # 映射宿主机8080到容器8080

Host模式:

┌─────────────────────────────────────────────────────────┐
│  宿主机网络栈(与容器共享)                                  │
│  ┌─────────────┐     ┌─────────────┐                    │
│  │  Container A│     │  Container B│                    │
│  │  10.0.0.5   │     │  10.0.0.5   │ ← 使用宿主机IP      │
│  │  port 8080  │     │  port 8081  │ ← 直接使用宿主机端口  │
│  └─────────────┘     └─────────────┘                    │
│  注意: 端口冲突需要手动避免                                   │
└─────────────────────────────────────────────────────────┘

命令:
docker run -d --network host sfu-server  # 无端口映射,直接使用宿主机端口

SFU推荐网络配置:

# 方案1: Bridge + 端口映射(一般场景)
docker run -d \
  --name sfu \
  --network bridge \
  -p 8080:8080 \
  -p 3478:3478/tcp \
  -p 3478:3478/udp \
  -p 10000-11000:10000-11000/udp \
  sfu-server

# 方案2: Host网络(高性能场景,避免NAT开销)
docker run -d \
  --name sfu \
  --network host \
  sfu-server

# 方案3: 自定义网络(容器间通信)
docker network create live-network
docker run -d --network live-network --name sfu sfu-server
docker run -d --network live-network --name signaling signaling-server
# 容器间可通过容器名直接通信: http://signaling:8082

5.3 自定义网络与DNS

Docker内置DNS:

在自定义网络中,Docker提供内置DNS:

容器A (sfu) ←──DNS查询──→ Docker DNS (127.0.0.11) ←──→ 容器IP
                                               ↓
容器B (signaling) 查询 "sfu" → 返回 172.20.0.2

无需 --link,直接使用容器名作为hostname
# 创建自定义网络
docker network create \
  --driver bridge \
  --subnet=172.20.0.0/16 \
  --gateway=172.20.0.1 \
  live-network

# 查看网络配置
docker network inspect live-network

# 运行容器并指定固定IP
docker run -d \
  --name sfu \
  --network live-network \
  --ip 172.20.0.10 \
  sfu-server

6. 持久化存储原理

6.1 容器存储的问题

容器层的特性:

特性 说明 影响
写入时复制 写操作发生在容器层 性能比原生低
容器删除即丢失 容器层随容器删除 数据持久化问题
联合文件系统 多层合并视图 不适合高频IO
容器存储问题示意:

┌─────────────────────────────────────────┐
│  Container Layer (可写,但...)           │
│  • 性能较低 (CoW开销)                    │
│  • 容器删除 → 数据丢失                   │
├─────────────────────────────────────────┤
│  Image Layers (只读)                    │
│  • 配置文件不可修改                      │
│  • 日志无法持久化                        │
└─────────────────────────────────────────┘

6.2 Volume绑定挂载

Volume的三种类型:

类型 创建方式 存储位置 适用场景
Named Volume docker volume create /var/lib/docker/volumes 数据持久化
Bind Mount -v /host:/container 宿主机任意路径 开发调试
tmpfs Mount --tmpfs 宿主机内存 敏感临时数据
# docker-compose中的存储配置
version: '3.8'
services:
  sfu:
    image: sfu-server
    volumes:
      # Named Volume - 生产推荐
      - sfu-logs:/app/logs
      - sfu-data:/app/data
      
      # Bind Mount - 开发调试
      - ./config/sfu.yaml:/app/config.yaml:ro
      - /etc/localtime:/etc/localtime:ro
      
      # tmpfs - 临时缓存,容器重启清空
      - type: tmpfs
        target: /app/cache
        tmpfs:
          size: 100M

volumes:
  sfu-logs:
    driver: local
  sfu-data:
    driver: local

6.3 直播系统存储方案

version: '3.8'
services:
  sfu:
    volumes:
      # 配置文件 - 只读绑定
      - ./config/sfu.yaml:/app/config/config.yaml:ro
      
      # 日志文件 - 持久化Volume
      - sfu-logs:/app/logs
      
      # 录制文件(如SFU支持录制)- 大容量Volume
      - type: bind
        source: /data/recordings
        target: /app/recordings
  
  recorder:
    volumes:
      # 录制服务 - 大容量存储
      - recordings:/recordings
      
      # 临时转码文件 - tmpfs加速
      - type: tmpfs
        target: /tmp/transcode
        tmpfs:
          size: 2G
  
  redis:
    volumes:
      # 缓存数据 - 需要持久化
      - redis-data:/data

volumes:
  # 日志(定期清理)
  sfu-logs:
    driver: local
  
  # 录制文件(大容量,可能使用外部存储)
  recordings:
    driver: local
    driver_opts:
      type: nfs
      o: addr=nfs-server,rw
      device: ":/exports/recordings"
  
  # Redis数据
  redis-data:
    driver: local

6.4 存储性能优化

优化策略 方法 效果
避免频繁写容器层 将日志写到Volume 提升IO性能
使用volume cache模式 consistent/delegated/cached 优化一致性开销
分离读写的存储 配置文件ro,数据rw 减少CoW
外部存储 NFS/Ceph/S3 扩展容量,跨节点共享
# 性能测试: Bind Mount vs Volume
docker run --rm -v /tmp/data:/data alpine sh -c "time dd if=/dev/zero of=/data/test bs=1M count=100"
docker run --rm -v test-volume:/data alpine sh -c "time dd if=/dev/zero of=/data/test bs=1M count=100"

7. Docker Compose编排

7.1 完整docker-compose.yml

version: '3.8'

services:
  # SFU服务
  sfu:
    build:
      context: ./sfu
      dockerfile: Dockerfile
    image: live-system/sfu:latest
    container_name: live-sfu
    restart: unless-stopped
    ports:
      - "8080:8080"      # HTTP API
      - "3478:3478/tcp"  # TURN TCP
      - "3478:3478/udp"  # TURN UDP
      - "10000-10100:10000-10100/udp"  # RTP
    environment:
      - SERVICE_TYPE=sfu
      - LOG_LEVEL=info
      - TURN_REALM=live.example.com
      - TURN_SHARED_SECRET=${TURN_SECRET}
    volumes:
      - ./config/sfu.yaml:/app/config/config.yaml:ro
      - sfu-logs:/app/logs
    networks:
      - live-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 4G
        reservations:
          cpus: '2.0'
          memory: 2G

  # MCU服务
  mcu:
    build:
      context: ./mcu
      dockerfile: Dockerfile
    image: live-system/mcu:latest
    container_name: live-mcu
    restart: unless-stopped
    ports:
      - "8081:8081"
    environment:
      - SERVICE_TYPE=mcu
      - GPU_ENABLED=true
    volumes:
      - ./config/mcu.yaml:/app/config/config.yaml:ro
      - mcu-logs:/app/logs
    networks:
      - live-net
    # GPU支持(需要nvidia-docker)
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    deploy:
      resources:
        limits:
          cpus: '8.0'
          memory: 16G

  # 信令服务
  signaling:
    build:
      context: ./signaling
      dockerfile: Dockerfile
    image: live-system/signaling:latest
    container_name: live-signaling
    restart: unless-stopped
    ports:
      - "8082:8082"
    environment:
      - SERVICE_TYPE=signaling
      - REDIS_URL=redis://redis:6379
      - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@postgres:5432/live
    depends_on:
      - redis
      - postgres
    volumes:
      - ./config/signaling.yaml:/app/config/config.yaml:ro
    networks:
      - live-net

  # 录制服务
  recorder:
    build:
      context: ./recorder
      dockerfile: Dockerfile
    image: live-system/recorder:latest
    container_name: live-recorder
    restart: unless-stopped
    environment:
      - SERVICE_TYPE=recorder
      - STORAGE_PATH=/recordings
    volumes:
      - ./recordings:/recordings
      - ./config/recorder.yaml:/app/config/config.yaml:ro
    networks:
      - live-net

  # Redis缓存
  redis:
    image: redis:7-alpine
    container_name: live-redis
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - live-net

  # PostgreSQL数据库
  postgres:
    image: postgres:15-alpine
    container_name: live-postgres
    restart: unless-stopped
    environment:
      - POSTGRES_DB=live
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - live-net

  # Nginx反向代理
  nginx:
    image: nginx:alpine
    container_name: live-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./nginx/html:/usr/share/nginx/html:ro
    depends_on:
      - sfu
      - signaling
    networks:
      - live-net

volumes:
  sfu-logs:
  mcu-logs:
  redis-data:
  postgres-data:

networks:
  live-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

4.2 环境变量配置

# .env文件
TURN_SECRET=your-turn-secret-key-here
DB_PASSWORD=your-database-password
REDIS_PASSWORD=your-redis-password

# Let's Encrypt证书
SSL_CERT_PATH=/etc/letsencrypt/live/your-domain.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/your-domain.com/privkey.pem

4.3 常用命令

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

# 查看日志
docker-compose logs -f sfu

# 重启服务
docker-compose restart sfu

# 停止所有服务
docker-compose down

# 完全清理(包括数据卷)
docker-compose down -v

# 扩展SFU实例
docker-compose up -d --scale sfu=3

# 更新镜像并重启
docker-compose pull
docker-compose up -d

8. 生产环境配置

5.1 日志收集

# docker-compose.logging.yml
version: '3.8'

services:
  # Fluentd日志收集
  fluentd:
    build: ./fluentd
    volumes:
      - ./fluentd/conf:/fluentd/etc
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    networks:
      - live-net

  # Elasticsearch
  elasticsearch:
    image: elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    volumes:
      - es-data:/usr/share/elasticsearch/data
    networks:
      - live-net

  # Kibana可视化
  kibana:
    image: kibana:8.5.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - live-net

5.2 监控告警

# 添加Prometheus监控
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - live-net

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
    networks:
      - live-net

5.3 自动更新

#!/bin/bash
# deploy.sh - 自动部署脚本

set -e

# 拉取最新代码
git pull origin master

# 重新构建镜像
docker-compose build

# 滚动更新(零停机)
docker-compose up -d --no-deps --scale sfu=2 sfu
docker-compose up -d --no-deps --scale sfu=1 sfu
docker-compose up -d

# 健康检查
sleep 10
if ! curl -f http://localhost:8080/health; then
    echo "Health check failed! Rolling back..."
    git revert HEAD
    docker-compose up -d
    exit 1
fi

echo "Deployment successful!"

9. 本章总结

6.1 容器化要点

方面 最佳实践
镜像构建 多阶段构建、选择合适基础镜像
网络配置 根据场景选择bridge/host/overlay
数据持久化 使用Volume管理配置和日志
服务编排 Docker Compose管理多服务依赖
生产部署 健康检查、资源限制、日志收集

6.2 课后思考

  1. 镜像优化:如何进一步减小Docker镜像体积?分析各层的作用。

  2. 网络选择:SFU使用host网络性能最好,但失去了容器隔离性。如何权衡?

  3. 热更新:如何在不中断服务的情况下更新SFU代码?

  4. 多机房部署:当用户分布在多个地区时,如何设计容器化部署架构?