3. 环境搭建与配置:Docker化部署仿真环境,依赖库安装与版本管理

说实话,搞自动驾驶仿真最头疼的事,不是算法写不出来,而是环境搭不起来。

我见过太多团队,代码写得挺好,一换机器就崩。库版本冲突、系统依赖缺失、Python 环境乱成一锅粥。嗯,这章我们就来解决这个问题。

3.1 为什么一定要用 Docker?

你想想看,一个典型的仿真环境要装多少东西?

  • ROS 2(Humble 或 Galactic)
  • Carla / SUMO / Gazebo 仿真器
  • Python 3.8+ 以及一堆科学计算库
  • CUDA + cuDNN + TensorRT(如果你用 GPU 加速)
  • 各种消息中间件和协议库

这些依赖之间互相打架是常有的事。我个人习惯,从一开始就把所有环境锁在 Docker 里。好处很明显:

问题 传统方式 Docker 方式
环境迁移 重装一遍,踩坑无数 一条命令拉镜像
版本冲突 pip 和 apt 互相覆盖 隔离的容器层
团队协作 每个人环境不一样 完全一致的镜像
CI/CD 集成 手动配置,容易漏 Dockerfile 自动化

核心原则:把环境当作代码来管理。Dockerfile 就是你的环境说明书,版本控制里必须包含它。

3.2 我们的 Docker 镜像架构

先给你看一张整体架构图,心里有个底。

仿真环境 Docker 镜像分层架构 Layer 1: 基础系统 (Ubuntu 22.04 + CUDA 11.8) Layer 2: ROS 2 Humble + 核心依赖 Layer 3: 仿真引擎 (Carla 0.9.14 + SUMO 1.18) Layer 4: 算法库 (PyTorch + TensorRT + 轨迹优化) Layer 5: 仿真联调应用 + 轨迹验证脚本 每层 可单独 缓存 构建 时只 需重 编变 动层

为什么要分这么多层?说白了,就是利用 Docker 的缓存机制。你改一行 Python 代码,不需要重新编译 CUDA 和 ROS。我在项目中遇到过,一开始把所有东西写在一个 RUN 指令里,每次改个参数都要等半小时重新构建。后来拆成五层,构建时间从 30 分钟降到了 2 分钟。

3.3 编写 Dockerfile

好,我们直接上手。这是我们的 Dockerfile 核心部分:

# ===== Layer 1: 基础系统 =====
FROM nvidia/cuda:11.8.0-devel-ubuntu22.04 AS base

RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    wget \
    python3-pip \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

# ===== Layer 2: ROS 2 Humble =====
FROM base AS ros2
RUN apt-get update && apt-get install -y \
    ros-humble-desktop \
    ros-humble-rmw-fastrtps-cpp \
    && rm -rf /var/lib/apt/lists/*

# ===== Layer 3: 仿真引擎 =====
FROM ros2 AS simulator
RUN pip3 install carla==0.9.14
RUN apt-get update && apt-get install -y sumo=1.18.0

# ===== Layer 4: 算法库 =====
FROM simulator AS algo
RUN pip3 install torch==2.0.0 torchvision==0.15.0
RUN pip3 install numpy==1.24.3 scipy==1.10.1 matplotlib==3.7.1

# ===== Layer 5: 应用层 =====
FROM algo AS app
WORKDIR /workspace
COPY ./src /workspace/src
RUN pip3 install -e /workspace/src

小技巧:把 apt-get install 和 pip install

3.4 依赖库版本管理

光有 Dockerfile 还不够。你想想看,Python 库的依赖关系有多复杂?

我个人习惯用 requirements.txt 配合 pip freeze 来管理。但这里有个坑:pip freeze 会把所有间接依赖也列出来,导致文件臃肿。我建议只锁定直接依赖,间接依赖让 pip 自己解析。

这是我们的 requirements.txt 示例:

# 核心依赖
numpy==1.24.3
scipy==1.10.1
matplotlib==3.7.1
pyyaml==6.0

# 深度学习
torch==2.0.0
torchvision==0.15.0

# 仿真接口
carla==0.9.14
sumo==1.18.0

# 轨迹处理
shapely==2.0.1
transforms3d==0.4.1

注意:不要在生产镜像里用 pip install --upgrade。我曾经在 CI 里加了 --upgrade,结果 numpy 从 1.24 被升到 1.26,接口变了,整个轨迹验证模块全部报错。排查了整整一天。

3.5 构建与启动脚本

每次手动敲 docker build 太累了。我写了个 Makefile 来封装常用操作:

# Makefile
IMAGE_NAME = sim-env
TAG = latest

build:
    docker build -t $(IMAGE_NAME):$(TAG) .

run:
    docker run -it --rm \
        --gpus all \
        --net=host \
        -v $(PWD)/data:/workspace/data \
        -v $(PWD)/config:/workspace/config \
        $(IMAGE_NAME):$(TAG)

shell:
    docker run -it --rm \
        --gpus all \
        --net=host \
        -v $(PWD):/workspace \
        $(IMAGE_NAME):$(TAG) \
        /bin/bash

clean:
    docker system prune -f

使用方式很简单:

  • make build — 构建镜像
  • make run — 启动仿真联调
  • make shell — 进入容器调试

3.6 避坑指南

嗯,这里总结几个我踩过的坑:

坑 1:CUDA 版本不匹配

我曾经把 CUDA 11.8 的镜像和 PyTorch 2.0(需要 CUDA 11.7)混用,结果训练时 GPU 利用率一直上不去。后来发现是 CUDA 运行时库版本冲突。解决办法:用 nvidia-smi 和 nvcc --version 确认版本一致。

坑 2:ROS 2 通信问题

Docker 默认网络模式是 bridge,ROS 2 的 DDS 发现协议会失败。我折腾了两天才发现,加 --net=host 就能解决。或者用 --env ROS_DOMAIN_ID=42 来隔离不同容器。

坑 3:磁盘空间爆炸

Docker 镜像每层都会缓存。如果你频繁构建,/var/lib/docker 会迅速膨胀。我养成了每周跑一次 docker system prune -a 的习惯,能释放几十 GB 空间。

3.7 验证环境是否搭建成功

环境搭好了,怎么确认一切正常?我写了一个简单的验证脚本:

#!/usr/bin/env python3
"""环境验证脚本"""

import sys

def check_imports():
    """检查关键库是否能导入"""
    libs = [
        ('numpy', '1.24.3'),
        ('torch', '2.0.0'),
        ('carla', '0.9.14'),
        ('sumo', '1.18.0'),
    ]
    
    for lib_name, expected_version in libs:
        try:
            module = __import__(lib_name)
            actual_version = module.__version__
            if actual_version != expected_version:
                print(f"[WARN] {lib_name}: 期望 {expected_version}, 实际 {actual_version}")
            else:
                print(f"[OK] {lib_name}: {actual_version}")
        except ImportError:
            print(f"[FAIL] {lib_name}: 未安装")
            return False
    return True

def check_gpu():
    """检查 GPU 是否可用"""
    try:
        import torch
        if torch.cuda.is_available():
            print(f"[OK] GPU: {torch.cuda.get_device_name(0)}")
        else:
            print("[WARN] GPU: 不可用,将使用 CPU")
    except:
        print("[WARN] GPU: 检查失败")

if __name__ == "__main__":
    print("=== 仿真环境验证 ===")
    success = check_imports()
    check_gpu()
    sys.exit(0 if success else 1)

跑一下这个脚本,所有依赖一目了然。我在 CI 流程里也集成了这个验证,每次构建完镜像自动跑一遍,有问题立刻报警。

3.8 小结

这一章我们做了三件事:

  • 用 Docker 分层构建了仿真环境镜像
  • 锁死了所有依赖库的版本
  • 写了构建脚本和验证脚本

环境搭好了,后面才能安心做仿真联调和轨迹验证。记住一句话:环境问题不解决,算法再牛也跑不起来。