音视频开发实战
第30章:Docker容器化部署
本章目标:掌握使用Docker容器化部署直播系统,包括镜像构建、网络配置和服务编排。
容器化是现代应用部署的标准方式。本章介绍如何将SFU/MCU等直播组件打包为Docker镜像并部署。
目录
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.02. 容器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
;;
esac5. 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:80825.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-server6. 持久化存储原理
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: local6.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: local6.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/164.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.pem4.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 -d8. 生产环境配置
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-net5.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-net5.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 课后思考
镜像优化:如何进一步减小Docker镜像体积?分析各层的作用。
网络选择:SFU使用host网络性能最好,但失去了容器隔离性。如何权衡?
热更新:如何在不中断服务的情况下更新SFU代码?
多机房部署:当用户分布在多个地区时,如何设计容器化部署架构?