10_apollo_docker_scripts子模块软件架构分析

10_apollo_docker_scripts子模块软件架构分析文档

1. 概述

Apollo Docker Scripts子模块是Apollo自动驾驶平台的Docker环境管理组件,提供容器启动、入驻、GPU配置、跨平台支持等核心功能。该模块通过封装Docker命令行工具,实现了对开发环境的统一管理,简化了开发者在不同硬件平台上的环境配置流程,支持NVIDIA GPU加速、AMD GPU支持以及ARM64架构设备。模块采用模块化设计,将复杂的容器生命周期管理分解为可复用的函数集合,为上层应用提供了简洁的接口调用方式。该子模块在整个Apollo系统架构中扮演着基础设施层的关键角色,为开发者提供了一致的开发体验和灵活的环境配置能力。

2. 软件架构图

graph TB subgraph "用户交互层" U1[dev_start.sh
启动开发容器] U2[dev_into.sh
进入开发容器] U3[dev_start_gdb_server.sh
启动GDB调试服务器] end subgraph "基础支撑层" B1[BUILD
Bazel构建配置] B2[docker_base.sh
Docker基础功能库] end subgraph "Docker运行时层" D1[容器生命周期管理] D2[GPU资源配置] D3[用户环境初始化] D4[跨平台支持] end subgraph "系统依赖层" S1[Docker Daemon] S2[NVIDIA Container Toolkit] S3[AMD ROCm] S4[硬件设备驱动] end U1 --> B2 U2 --> B2 U3 --> B2 B2 --> D1 B2 --> D2 B2 --> D3 B2 --> D4 D1 --> S1 D2 --> S2 D2 --> S3 D4 --> S4 style U1 fill:#e3f2fd style U2 fill:#e3f2fd style U3 fill:#e3f2fd style B2 fill:#fff3e0 style D1 fill:#e8f5e9 style D2 fill:#e8f5e9 style D3 fill:#e8f5e9 style D4 fill:#e8f5e9
graph TD subgraph "功能模块架构" F1[License管理] F2[地理配置] F3[GPU检测] F4[容器操作] F5[用户设置] F6[跨平台下载] end F1 --> F2 F2 --> F3 F3 --> F4 F4 --> F5 F5 --> F6 style F1 fill:#fce4ec style F2 fill:#f3e5f5 style F3 fill:#e8f5e9 style F4 fill:#e3f2fd style F5 fill:#fff3e0 style F6 fill:#e0f7fa

3. 调用流程图

3.1 容器启动流程

sequenceDiagram participant User as 用户 participant DevStart as dev_start.sh participant AEM as aem工具 participant DockerBase as docker_base.sh participant Docker as Docker Daemon participant Container as Apollo容器 User->>DevStart: 执行启动命令 DevStart->>AEM: 调用aem start命令 AEM->>DockerBase: 加载基础函数库 DockerBase->>DockerBase: check_agreement()许可证检查 DockerBase->>DockerBase: determine_gpu_use_host()GPU检测 DockerBase->>DockerBase: geo_specific_config()地理配置 DockerBase->>Docker: docker run创建容器 Docker->>Container: 容器启动 Container->>DockerBase: postrun_start_user()用户初始化 Container->>DockerBase: postrun_cross_platform_download()跨平台下载 DockerBase->>User: 启动完成

3.2 容器入驻流程

sequenceDiagram participant User as 用户 participant DevInto as dev_into.sh participant AEM as aem工具 participant DockerBase as docker_base.sh participant Docker as Docker Daemon participant Container as Apollo容器 User->>DevInto: 执行入驻命令 DevInto->>AEM: 调用aem enter命令 AEM->>DockerBase: 加载基础函数库 DockerBase->>DockerBase: check_agreement()许可证检查 DockerBase->>Docker: docker exec进入容器 Docker->>Container: 执行shell Container->>User: 交互式终端

3.3 GDB服务器启动流程

sequenceDiagram participant User as 用户 participant GDB as dev_start_gdb_server.sh participant Docker as Docker Daemon participant Container as Apollo容器 participant GDBServer as GDB服务器 User->>GDB: 启动调试
模块名 端口号 GDB->>GDB: 参数验证 GDB->>GDB: check_docker_open()检查容器状态 GDB->>GDB: xhost +local:root授权X显示 GDB->>Docker: docker exec执行命令 Docker->>Container: 调用start_gdb_server.sh Container->>GDBServer: 启动GDB服务器 GDBServer->>GDBServer: 监听指定端口 GDB->>GDB: xhost -local:root撤销X授权 GDB->>User: 调试环境就绪

3.4 GPU配置流程

sequenceDiagram participant Script as docker_base.sh participant System as 操作系统 participant NVIDIA as NVIDIA驱动 participant AMD as AMD驱动 participant Docker as Docker Daemon Script->>System: determine_gpu_use_host() System->>Script: 获取CPU架构 alt ARM64架构 System->>NVIDIA: 检查nvgpu内核模块 NVIDIA-->>Script: 返回检测结果 else x86_64架构 System->>NVIDIA: 检查nvidia-smi NVIDIA-->>Script: GPU设备信息 System->>AMD: 检查rocm-smi AMD-->>Script: GPU设备信息 end alt NVIDIA GPU Docker->>Docker: 检查nvidia-container-toolkit Docker-->>Script: 运行时配置 Script->>Docker: 设置--runtime nvidia或--gpus all else AMD GPU Docker->>Docker: 配置设备访问 Script->>Docker: 设置--device=/dev/kfd等参数 else 无GPU Script->>System: 输出警告信息 end

3.5 容器停止流程

sequenceDiagram participant User as 用户 participant Script as docker_base.sh participant Docker as Docker Daemon participant Container as Apollo容器 User->>Script: stop_all_apollo_containers() Script->>Docker: docker inspect查询所有容器 Docker-->>Script: 容器列表及标签 Script->>Script: 过滤用户和容器名 loop 遍历匹配的容器 Script->>Docker: docker stop停止容器 Docker->>Container: 发送停止信号 alt 强制停止 Script->>Docker: docker rm强制删除 Docker->>Container: 删除容器 end end Script->>User: 停止完成

4. UML类图

4.1 脚本模块类图

classDiagram class DockerScriptsModule { +String CURR_DIR +String TOP_DIR +String AEM +main() int #execute() void } class DevStart { +main() int #callAemStart() void } class DevInto { +main() int #callAemEnter() void } class DevStartGDB { +check_docker_open() bool +print_usage() void +main() int #executeGDBServer() void } class DockerBase { +String HOST_ARCH +String HOST_OS +String GEO_REGISTRY +String DOCKER_RUN_CMD +Boolean USE_GPU_HOST +Boolean USE_AMD_GPU +Boolean USE_NVIDIA_GPU +geo_specific_config(String geo) void +determine_gpu_use_host() void +remove_container_if_exists(String container) void +postrun_start_user(String container) void +postrun_cross_platform_download(String container, Int flag) void +download_tegra_lib(String container) void +stop_all_apollo_containers(String force) void +check_agreement() void } DockerScriptsModule <|-- DevStart DockerScriptsModule <|-- DevInto DockerScriptsModule <|-- DevStartGDB DevStart --> DockerBase : 依赖 DevInto --> DockerBase : 依赖 DevStartGDB --> DockerBase : 依赖

4.2 容器管理类图

classDiagram class ContainerManager { +String containerName +String userName +Boolean gpuEnabled +Boolean amdGpuEnabled +start() bool +stop() bool +restart() bool +remove() bool +isRunning() bool +executeCommand(String cmd) String +enter() void } class GPUManager { +String hostArch +String gpuVendor +Boolean gpuAvailable +detectGPU() GPUInfo +configureRuntime() String +checkNVIDIASupport() bool +checkAMDSupport() bool } class LicenseManager { +String agreementPath +Boolean agreed +checkAgreement() bool +requestAgreement() bool +recordAgreement() void } class GeoConfig { +String geo +String registry +loadConfig(String geo) void +getRegistry() String +applyConfig() void } ContainerManager --> GPUManager : 配置GPU ContainerManager --> LicenseManager : 检查许可 ContainerManager --> GeoConfig : 应用配置 GPUManager --> LicenseManager : 验证环境

4.3 运行时配置类图

classDiagram class RuntimeConfig { +String dockerRunCmd +Boolean useHostGPU +String gpuRuntime +Map~String, String~ envVars +List~String~ volumes +List~String~ devices +String user +String workingDir +buildRunCommand() String +applyEnvironment() void +mountVolumes() void +configureDevices() void } class UserSetup { +String customUser +String userHome +Boolean isRoot +setupUser(String container) void +configureShell() void +setupEnvironment() void } class CrossPlatformSupport { +String platform +Boolean isCrossCompile +downloadPlatformLibs(String container) void +configureTegra(String container) void +setupARM64Libs(String container) void } class AgreementChecker { +String agreementFile +String recordFile +validate() bool +prompt() String +record() void } RuntimeConfig --> UserSetup : 配置用户环境 RuntimeConfig --> CrossPlatformSupport : 支持跨平台 UserSetup --> AgreementChecker : 验证协议

4.4 数据流类图

classDiagram class DockerContext { <> +String hostArch +String hostOS +String user +String geo +Boolean gpuEnabled +Boolean amdEnabled } class GPUContext { <> +String vendor +String driverVersion +Integer deviceCount +Boolean nvidiaAvailable +Boolean amdAvailable +String runtime } class ContainerContext { <> +String name +String image +String status +String owner +List~String~ mounts +List~String~ devices } class AgreementContext { <> +Boolean agreed +String agreementPath +String recordPath +Date agreedTime } DockerContext --> GPUContext : 生成GPU配置 DockerContext --> AgreementContext : 验证许可 ContainerContext --> DockerContext : 基于Docker配置

5. 状态机分析

5.1 Docker环境初始化状态机

stateDiagram-v2 [*] --> IDLE: 脚本启动 state INIT { [*] --> LOAD_CONFIG: 加载配置 LOAD_CONFIG --> CHECK_ENV: 检查环境变量 CHECK_ENV --> DETECT_GPU: 检测GPU } state GPU_DETECTION { [*] --> DETECT_CPU_ARCH: 检测CPU架构 DETECT_CPU_ARCH --> CHECK_NVIDIA: NVIDIA检测 CHECK_NVIDIA --> NVIDIA_RESULT: 返回结果 state NVIDIA_CHECK { CHECK_NVGPU_MODULE: ARM64检查nvgpu CHECK_NVIDIA_SMI: x86_64检查nvidia-smi } CHECK_AMD --> AMD_RESULT: 返回结果 state AMD_CHECK { CHECK_ROCM_SMI: 检查rocm-smi CHECK_AMD_DEVICES: 检查/dev/kfd } } state LICENSE { [*] --> CHECK_RECORD: 检查许可记录 CHECK_RECORD --> EXISTS: 记录存在 CHECK_RECORD --> NOT_EXISTS: 记录不存在 EXISTS --> COMPLETED: 许可已同意 NOT_EXISTS --> DISPLAY: 显示协议 DISPLAY --> USER_INPUT: 用户输入 USER_INPUT --> VALID: 同意 USER_INPUT --> INVALID: 拒绝 VALID --> RECORD: 记录同意 RECORD --> COMPLETED: 完成 INVALID --> [*]: 退出 } IDLE --> INIT: 执行main函数 INIT --> GPU_DETECTION: 配置加载完成 GPU_DETECTION --> LICENSE: GPU检测完成 LICENSE --> READY: 许可验证通过 READY --> [*]: 准备就绪

5.2 容器生命周期状态机

stateDiagram-v2 [*] --> NON_EXISTENT: 容器不存在 NON_EXISTENT --> STOPPED: 创建容器 STOPPED --> STARTING: 启动容器 STARTING --> RUNNING: 容器运行中 RUNNING --> STOPPING: 停止容器 STOPPING --> STOPPED: 容器已停止 STOPPED --> REMOVING: 删除容器 REMOVING --> NON_EXISTENT: 删除完成 RUNNING --> EXECUTING: 进入容器 EXECUTING --> RUNNING: 退出容器 RUNNING --> RESTARTING: 重启容器 RESTARTING --> RUNNING: 重启完成 state STOPPED { [*] --> CHECK_EXISTS: 检查存在 CHECK_EXISTS --> FOUND: 容器存在 CHECK_EXISTS --> NOT_FOUND: 容器不存在 } state RUNNING { [*] --> HEALTH_CHECK: 健康检查 HEALTH_CHECK --> HEALTHY: 健康 HEALTH_CHECK --> UNHEALTHY: 不健康 HEALTHY --> GPU_CONFIG: GPU配置 UNHEALTHY --> LOGGING: 记录日志 } state EXECUTING { [*] --> EXEC_CMD: 执行命令 EXEC_CMD --> WAIT: 等待完成 WAIT --> RESULT: 返回结果 }

5.3 GPU运行时配置状态机

stateDiagram-v2 [*] --> DETECT: GPU检测 DETECT --> ARM64_CHECK: ARM64架构 DETECT --> X86_64_CHECK: x86_64架构 DETECT --> UNSUPPORTED: 不支持架构 state ARM64_CHECK { [*] --> CHECK_NVGPU: 检查nvgpu模块 NVGPU_PRESENT --> NVIDIA_ARM64: NVIDIA可用 NVGPU_ABSENT --> CPU_ONLY: 仅CPU } state X86_64_CHECK { [*] --> CHECK_NVIDIA_SMI: 检查nvidia-smi NVIDIA_PRESENT --> NVIDIA_X86: NVIDIA可用 NVIDIA_ABSENT --> CHECK_ROCM: 检查ROCM ROCM_PRESENT --> AMD_X86: AMD可用 ROCM_ABSENT --> CPU_ONLY: 仅CPU } state NVIDIA_ARM64 { [*] --> CHECK_TOOLKIT: 检查nvidia-container-toolkit TOOLKIT_PRESENT --> SET_RUNTIME: 设置--runtime nvidia TOOLKIT_ABSENT --> WARNING_NVIDIA: 输出警告 } state NVIDIA_X86 { [*] --> CHECK_VERSION: 检查Docker版本 VERSION_19_03_PLUS --> SET_GPU_FLAG: 设置--gpus all VERSION_LT_19_03 --> OLD_VERSION: 旧版本警告 OLD_VERSION --> CPU_ONLY: 回退到CPU } state AMD_X86 { [*] --> CONFIGURE_AMD: 配置AMD设备 CONFIGURE_AMD --> SET_AMD_FLAG: 设置设备参数 } state SET_RUNTIME { [*] --> UPDATE_CMD: 更新运行命令 UPDATE_CMD --> READY: 配置完成 } state SET_GPU_FLAG { [*] --> UPDATE_CMD: 更新运行命令 UPDATE_CMD --> READY: 配置完成 } state SET_AMD_FLAG { [*] --> UPDATE_CMD: 更新运行命令 UPDATE_CMD --> READY: 配置完成 } state CPU_ONLY { [*] --> WARNING_GPU: GPU不可用警告 WARNING_GPU --> READY: 继续运行 } READY --> [*]: 配置就绪

5.4 许可证验证状态机

stateDiagram-v2 [*] --> CHECK_FILE: 检查许可记录 CHECK_FILE --> EXISTS: 文件存在 CHECK_FILE --> NOT_EXISTS: 文件不存在 EXISTS --> VALIDATE: 验证记录 VALIDATE --> ACCEPTED: 有效记录 ACCEPTED --> [*]: 继续执行 NOT_EXISTS --> READ_AGREEMENT: 读取协议文件 READ_AGREEMENT --> DISPLAY: 显示协议内容 DISPLAY --> PROMPT: 提示用户输入 state PROMPT { [*] --> WAIT_INPUT: 等待输入 WAIT_INPUT --> 'y'/'Y': 同意 WAIT_INPUT --> OTHER: 拒绝 } 'y'/'Y' --> RECORD_AGREEMENT: 记录同意 RECORD_AGREEMENT --> [*]: 继续执行 OTHER --> EXIT: 退出程序 EXIT --> [*]: 终止执行

6. 源码分析

6.1 目录结构分析

Apollo Docker Scripts子模块位于docker/scripts/目录下,包含以下核心文件:首先是BUILD文件,这是Bazel构建系统的配置文件,定义了如何打包和安装脚本模块;其次是dev_start.sh脚本,负责启动Apollo开发容器;然后是dev_into.sh脚本,提供进入正在运行的Apollo容器的功能;接着是dev_start_gdb_server.sh脚本,用于在容器内启动GDB调试服务器;最后是docker_base.sh脚本,这是最核心的模块,封装了所有Docker相关的公共函数。该目录结构体现了良好的模块化设计原则,每个脚本专注于特定的功能领域,同时通过依赖公共基础模块来实现代码复用。

6.2 BUILD构建配置分析

6.2.1 构建配置概述
bazel 复制代码
# docker/scripts/BUILD
load("//tools/install:install.bzl", "install")

package(default_visibility = ["//visibility:public"])

install(
    name = "install",
    data = [
        ":runtime",
    ],
)

filegroup(
    name = "runtime",
    srcs = glob([
        "*.sh",
    ]),
)

该构建配置使用了Bazel的install规则来定义安装目标,将所有Shell脚本文件打包为运行时数据依赖。配置中使用了glob模式匹配所有.sh后缀的文件,这使得添加新脚本时无需手动更新配置。默认的public可见性确保了其他模块可以引用这些脚本。install目标作为顶层入口点,依赖于名为runtime的文件组,该文件组包含了所有需要部署的脚本文件。这种设计允许构建系统高效地处理脚本的打包和分发。

6.3 容器启动脚本分析

6.3.1 dev_start.sh脚本结构
bash 复制代码
#!/usr/bin/env bash

###############################################################################
# Copyright 2024 The Apollo Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# ...
###############################################################################

CURR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
TOP_DIR="${CURR_DIR}/../../aem"
AEM="${TOP_DIR}/aem"

function main() {
  ${AEM} start $@
}

main "$@"

该脚本是一个典型的Shell包装器,实现了命令转发的核心功能。首先通过Bash内置的dirname和cd命令组合,计算当前脚本所在目录的绝对路径;然后基于该路径计算aem工具的相对路径;最后定义main函数来调用aem的start子命令,并传递所有命令行参数。脚本使用$@来展开所有位置参数,确保原始命令行参数能够完整传递。这种设计模式遵循了Unix哲学中的"做一件事并做好"的原则,脚本本身不承担实际逻辑,只是作为用户命令行接口和aem工具之间的桥梁。

6.3.2 dev_into.sh脚本结构
bash 复制代码
#!/usr/bin/env bash

###############################################################################
# Copyright 2024 The Apollo Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# ...
###############################################################################

CURR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
TOP_DIR="${CURR_DIR}/../../aem"
AEM="${TOP_DIR}/aem"

function main() {
  ${AEM} enter $@
}

main "$@"

dev_into.sh与dev_start.sh具有几乎完全相同的结构设计,唯一的区别在于main函数中调用的是aem的enter子命令而非start子命令。这种代码复用和差异化的设计模式体现了良好的软件工程实践,通过最小化代码变更来满足不同的功能需求。两个脚本都遵循相同的模式:确定相对路径、设置AEM工具路径、定义主入口函数、处理命令行参数。

6.4 GDB服务器启动脚本分析

6.4.1 参数验证模块
bash 复制代码
function check_docker_open() {
  docker ps --format "{{.Names}}" | grep apollo_dev_$USER 1>/dev/null 2>&1
  if [ $? != 0 ]; then
    echo "The docker is not started, please start it first. "
    exit 1
  fi
}

该函数实现了对Docker容器运行状态的检查机制。函数使用docker ps命令列出所有运行中的容器,并结合--format参数格式化输出,仅保留容器名称字段;然后通过管道将结果传递给grep命令,搜索名为apollo_dev_$USER的容器;最后通过检查grep的退出状态码来确定容器是否正在运行。这种实现方式具有容错性,即使grep没有找到匹配的容器也不会导致脚本异常终止,而是优雅地输出提示信息并退出。

bash 复制代码
function print_usage() {
  RED='\033[0;31m'
  BLUE='\033[0;34m'
  BOLD='\033[1m'
  NONE='\033[0m'

  echo -e "\n${RED}Usage${NONE}:
  .${BOLD}/dev_debug_server.sh${NONE} MODULE_NAME PORT_NUMBER"

  echo -e "${RED}MODULE_NAME${NONE}:
  ${BLUE}planning${NONE}: debug the planning module.
  ${BLUE}control${NONE}: debug the control module.
  ${BLUE}routing${NONE}: debug the routing module.
  ..., and so on."

  echo -e "${RED}PORT_NUMBER${NONE}:
  ${NONE}a port number, such as '1111'."
}

print_usage函数提供了详细的命令行使用说明,使用ANSI转义序列实现终端颜色的输出。定义了红色用于标识错误和关键信息、蓝色用于标识参数名称和示例值、粗体用于突出显示命令语法。这种用户友好的错误提示设计帮助开发者快速理解正确的使用方法。函数按照Usage、MODULE_NAME、PORT_NUMBER三个部分组织输出,每个部分使用不同的颜色编码区分,使信息层次分明、易于阅读。

6.4.2 主执行逻辑
bash 复制代码
if [ $# -lt 2 ];then
    print_usage
    exit 1
fi

check_docker_open

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${DIR}/../.."

xhost +local:root 1>/dev/null 2>&1

docker exec \
    -u $USER \
    -it apollo_dev_$USER \
    /bin/bash scripts/start_gdb_server.sh $@

xhost -local:root 1>/dev/null 2>&1

脚本的执行流程体现了严谨的状态管理和资源控制逻辑。首先通过参数个数检查确保用户提供了模块名和端口号两个必要参数;然后调用check_docker_open验证目标容器正在运行;在执行docker exec命令之前,先通过xhost +local:root临时授权Docker容器访问宿主机的X服务器,这对于需要图形界面支持的调试场景至关重要;在容器内部执行start_gdb_server.sh脚本并传递原始参数;最后通过xhost -local:root撤销X服务器授权,防止潜在的安全风险。整个流程展示了良好的安全意识和资源管理实践。

6.5 Docker基础功能库分析

6.5.1 初始化配置模块
bash 复制代码
TOP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "${TOP_DIR}/scripts/apollo.bashrc"

unset TOP_DIR

export HOST_ARCH="$(uname -m)"
export HOST_OS="$(uname -s)"

该模块完成了脚本运行环境的初始化工作。首先计算项目根目录的绝对路径并赋值给TOP_DIR变量;然后加载Apollo项目的公共bashrc配置文件,该文件包含了系统级的环境变量和函数定义;紧接着unset TOP_DIR变量以避免变量污染;最后通过uname命令获取并导出宿主机的CPU架构和操作系统类型,这些信息将在后续的GPU检测和跨平台配置中发挥关键作用。export语句确保这些环境变量能够被子进程继承和使用。

6.5.2 地理区域配置模块
bash 复制代码
GEO_REGISTRY=
function geo_specific_config() {
  local geo="$1"
  if [[ -z "${geo}" ]]; then
    info "Use default GeoLocation settings"
    return
  fi
  info "Setup geolocation specific configurations for ${geo}"
  if [[ "${geo}" == "cn" ]]; then
    info "GeoLocation settings for Mainland China"
    GEO_REGISTRY="registry.baidubce.com"
  else
    info "GeoLocation settings for ${geo} is not ready, fallback to default"
  fi
}

geo_specific_config函数实现了针对不同地理区域配置镜像仓库地址的功能。函数接受一个地理区域参数,根据参数值设置相应的镜像仓库地址。对于中国大陆区域(cn),函数将GEO_REGISTRY设置为百度云的对象存储仓库地址,这在国内网络环境下能够提供更快的镜像拉取速度。对于不支持的区域,函数会输出提示信息并回退到默认配置。这种设计允许用户根据实际网络环境选择最优的镜像源,体现了系统配置的灵活性和可扩展性。

6.5.3 GPU检测配置模块
bash 复制代码
DOCKER_RUN_CMD="docker run"
USE_GPU_HOST=0
USE_AMD_GPU=0
USE_NVIDIA_GPU=0

function determine_gpu_use_host() {
  if [[ "${HOST_ARCH}" == "aarch64" ]]; then
    if lsmod | grep -q "^nvgpu"; then
      USE_GPU_HOST=1
      USE_NVIDIA_GPU=1
    fi
  elif [[ "${HOST_ARCH}" == "x86_64" ]]; then
    if [[ ! -x "$(command -v nvidia-smi)" ]]; then
      warning "No nvidia-smi found."
    elif [[ -z "$(nvidia-smi)" ]]; then
      warning "No NVIDIA GPU device found."
    else
      USE_GPU_HOST=1
      USE_NVIDIA_GPU=1
    fi
    if [[ ! -x "$(command -v rocm-smi)" ]]; then
      warning "No rocm-smi found."
    elif [[ -z "$(rocm-smi)" ]]; then
      warning "No AMD GPU device found."
    else
      USE_AMD_GPU=1
    fi
    if (($USE_NVIDIA_GPU == 1)) || (($USE_AMD_GPU == 1)); then
      USE_GPU_HOST=1
    else
      USE_GPU_HOST=0
      warning "No any GPU device found. CPU will be used instead."
    fi
  else
    error "Unsupported CPU architecture: ${HOST_ARCH}"
    exit 1
  fi
  ...
}

determine_gpu_use_host函数是GPU检测配置的核心逻辑,实现了跨平台的GPU设备识别和运行时配置。该函数首先根据宿主机的CPU架构执行不同的检测逻辑:对于ARM64架构(aarch64),主要检查nvgpu内核模块的存在性来判断NVIDIA GPU是否可用;对于x86_64架构,则同时检查NVIDIA GPU(通过nvidia-smi命令)和AMD GPU(通过rocm-smi命令)。函数使用command -v来检查命令是否存在,使用-x测试命令是否可执行,使用-z检查命令输出是否为空。这种多层次的检测机制确保了在不同硬件环境下的兼容性和健壮性。

bash 复制代码
  local nv_docker_doc="https://github.com/NVIDIA/nvidia-docker/blob/master/README.md"
  if (($USE_NVIDIA_GPU == 1)); then
    if [[ -x "$(which nvidia-container-toolkit)" || -x "$(which nvidia-container-runtime)" ]]; then
      local docker_version
      docker_version="$(docker version --format '{{.Server.Version}}')"
      if dpkg --compare-versions "${docker_version}" "ge" "19.03"; then
        if [[ "${HOST_ARCH}" == "aarch64" ]]; then
          DOCKER_RUN_CMD="docker run --runtime nvidia"
        else
          DOCKER_RUN_CMD="docker run --gpus all"
        fi
      else
        warning "Please upgrade to docker-ce 19.03+ to access GPU from container."
        USE_GPU_HOST=0
      fi
    elif [[ -x "$(which nvidia-docker)" ]]; then
      DOCKER_RUN_CMD="nvidia-docker run"
    else
      USE_GPU_HOST=0
      warning "Cannot access GPU from within container. Please install latest Docker" \
        "and NVIDIA Container Toolkit as described by: "
      warning "  ${nv_docker_doc}"
    fi
  elif (($USE_AMD_GPU == 1)); then
    DOCKER_RUN_CMD="docker run --device=/dev/kfd --device=/dev/dri --security-opt seccomp=unconfined --group-add video"
  fi

在完成GPU设备检测后,该代码段根据检测结果和Docker版本信息生成适当的容器运行时命令。对于NVIDIA GPU,代码首先检查nvidia-container-toolkit或nvidia-container-runtime是否存在,因为这些是容器访问GPU的必要组件;然后获取Docker服务器的版本号,根据版本号选择合适的运行时参数:Docker 19.03及以上版本支持--gpus all参数,更早的版本则需要使用--runtime nvidia参数;最后,对于ARM64架构,处理方式与x86_64略有不同,使用--runtime nvidia参数。对于AMD GPU,代码配置了设备访问参数,包括/dev/kfd(计算设备)、/dev/dri(显示设备)以及相关的安全选项。

6.5.4 容器生命周期管理模块
bash 复制代码
function remove_container_if_exists() {
  local container="$1"
  if docker ps -a --format '{{.Names}}' | grep -q "${container}"; then
    info "Removing existing Apollo container: ${container}"
    docker stop "${container}" > /dev/null
    docker rm -v -f "${container}" 2> /dev/null
  fi
}

remove_container_if_exists函数实现了容器存在性检查和删除功能。函数首先列出所有容器(包括已停止的容器),然后使用grep搜索指定的容器名称;如果找到匹配的容器,则执行停止操作并强制删除。docker rm命令使用-v参数删除与容器关联的匿名卷,使用-f参数强制删除正在运行的容器。这种设计确保了在创建新容器之前,系统中不存在同名的旧容器,避免了潜在的端口冲突和资源占用问题。

bash 复制代码
function stop_all_apollo_containers() {
  local force="$1"
  local running_containers
  running_containers=($(docker inspect \
    $(docker ps -q) \
    --format '{{or .Config.Labels.owner "-"}}...{{.Name}}'))
  for container in ${running_containers[*]}; do
    owner="${container%%...*}"
    name="${container##*...}"
    if [[ ("${owner}" == "${USER}") && ("${name}" =~ apollo_.*) ]]; then
      info "Now stop container ${name} ..."
      if docker stop "${name}" > /dev/null; then
        if [[ "${force}" == "-f" || "${force}" == "--force" ]]; then
          docker rm -f "${name}" 2> /dev/null
        fi
        info "Done."
      else
        warning "Failed."
      fi
    fi
  done
}

stop_all_apollo_containers函数实现了批量停止Apollo容器的功能。该函数首先使用docker inspect命令查询所有运行中容器的信息,包括容器名称和标签中定义的owner字段;然后通过字符串处理提取owner和容器名;接着过滤出当前用户拥有的且名称符合apollo_.*模式的容器;对于每个匹配的容器,首先执行停止操作,如果用户指定了-f或--force参数,则在停止后强制删除容器。该函数展示了复杂的字符串处理和模式匹配技术,能够精确地识别和管理属于特定用户的Apollo容器集合。

6.5.5 用户环境初始化模块
bash 复制代码
function postrun_start_user() {
  local container="$1"
  if [ "${CUSTOM_USER-$USER}" != "root" ]; then
    docker exec -u root "${container}" \
      bash -c '/apollo/scripts/docker_start_user.sh'
  fi
}

postrun_start_user函数负责在容器启动后执行用户环境初始化。当容器内的用户不是root用户时,函数以root身份执行docker_start_user.sh脚本,完成非root用户的配置工作。这种设计允许Apollo在容器内使用非root用户运行,提高了安全性,同时确保用户环境(环境变量、shell配置、用户权限等)能够正确设置。函数使用了参数扩展{CUSTOM_USER-USER},当CUSTOM_USER变量未设置时回退到USER环境变量,体现了灵活的参数处理方式。

bash 复制代码
function postrun_cross_platfrom_download() {
  local container="$1"
  local flag="$2"
  if [[ "$flag" -eq 1 ]]; then
    download_tegra_lib "${container}"
  fi
}

function download_tegra_lib() {
  local container="$1"
  local tegra_lib_url="https://apollo-pkg-beta.cdn.bcebos.com/archive/tegra.tar.gz"
  info "download external library for cross-compilation..."
  docker exec -u root "${container}" \
    bash -c "cd ~ && wget -nv ${tegra_lib_url} && tar -xzvf ~/tegra.tar.gz -C /usr/lib/aarch64-linux-gnu/ > /dev/null"
}

postrun_cross_platfrom_download和download_tegra_lib函数共同实现了跨平台库的下载和安装功能。当flag参数为1时,download_tegra_lib函数会被调用,从指定的URL下载Tegra库文件压缩包,并在容器内解压到系统库目录。这种设计支持ARM64架构设备的交叉编译需求,将专用的库文件预装到容器环境中。函数使用wget命令下载文件,-nv参数减少输出冗余,使用tar命令解压并通过-C参数指定目标目录。

6.5.6 许可证协议检查模块
bash 复制代码
function check_agreement() {
  local agreement_record="${HOME}/.apollo_agreement.txt"
  if [[ -e "${agreement_record}" ]]; then
    return 0
  fi
  local agreement_file
  agreement_file="${APOLLO_ROOT_DIR}/scripts/AGREEMENT.txt"
  if [[ ! -f "${agreement_file}" ]]; then
    error "AGREEMENT ${agreement_file} does not exist."
    exit 1
  fi

  cat "${agreement_file}"
  local tip="Type 'y' or 'Y' to agree to the license agreement above, \
or type any other key to exit:"

  echo -n "${tip}"
  local answer="$(read_one_char_from_stdin)"
  echo

  if [[ "${answer}" != "y" ]]; then
    exit 1
  fi

  cp -f "${agreement_file}" "${agreement_record}"
  echo "${tip}" >> "${agreement_record}"
  echo "${user_agreed}" >> "${agreement_record}"
}

check_agreement函数实现了用户许可协议的验证流程。函数首先检查用户主目录下是否已经存在许可记录文件,如果存在则直接返回,表示用户已经同意过协议;如果不存在,则显示协议文件内容并提示用户输入y或Y来同意协议。函数使用read_one_char_from_stdin读取用户输入,这是一个自定义函数,用于从标准输入读取单个字符。同意后,函数将协议文件复制到用户主目录的隐藏文件中作为记录,并追加用户的同意时间和确认信息。这种设计确保了用户在使用Apollo之前必须明确同意许可协议,同时提供了便捷的重复使用体验。

6.5.7 公共函数导出配置
bash 复制代码
export -f geo_specific_config
export -f determine_gpu_use_host
export -f stop_all_apollo_containers remove_container_if_exists
export -f check_agreement
export USE_GPU_HOST
export DOCKER_RUN_CMD
export GEO_REGISTRY

该配置段将docker_base.sh中定义的函数和变量导出为公共接口,供其他脚本或Bash会话使用。export -f用于导出函数定义,使得这些函数能够在子Shell中被调用;export用于导出变量值。这种机制允许docker_base.sh被source加载后,其定义的函数和变量能够在当前Shell环境中使用,实现了代码复用和配置共享。值得注意的是,stop_all_apollo_containers和remove_container_if_exists被合并在同一条export语句中,展示了Bash对多个函数同时导出的支持能力。

6.6 核心流程时序分析

6.6.1 容器启动时序分析

当用户执行dev_start.sh启动Apollo开发容器时,整个系统经历以下流程:首先脚本计算aem工具的相对路径并调用其start子命令;aem工具加载docker_base.sh作为公共函数库;接下来执行许可证检查流程,确保用户已经同意许可协议;然后进入GPU检测阶段,系统根据CPU架构选择不同的检测逻辑,并配置适当的Docker运行时参数;根据地理配置选择镜像仓库地址;最后执行docker run命令创建并启动容器;容器启动后,系统执行用户初始化脚本和跨平台库下载任务。整个流程涉及多个阶段的配置和验证,每个阶段都有明确的成功/失败判断和错误处理机制。

6.6.2 容器入驻时序分析

当用户执行dev_into.sh进入Apollo开发容器时,流程相对简单:脚本计算aem工具路径并调用enter子命令;aem加载docker_base.sh公共库并执行许可证检查;然后直接执行docker exec命令进入交互式shell会话。由于容器已经处于运行状态,该流程不需要执行GPU检测和容器创建操作。这种设计将复杂的环境准备流程集中在容器首次启动时完成,后续的进入操作仅需要简单的权限验证即可进入容器交互环境。

6.6.3 GDB调试时序分析

当用户启动GDB调试会话时,dev_start_gdb_server.sh脚本执行完整的验证和配置流程:首先验证命令行参数完整性;然后检查目标容器是否正在运行;接着临时授权Docker容器访问宿主机的X服务器显示;执行docker exec调用容器内的start_gdb_server.sh脚本并传递模块名和端口号参数;容器内的GDB服务器启动并监听指定端口;最后脚本撤销X服务器授权以释放安全权限。整个流程展示了调试场景下的特殊配置需求,包括图形界面访问授权、远程调试端口配置等。

7. 设计模式

7.1 包装器模式(Wrapper Pattern)

bash 复制代码
# dev_start.sh
function main() {
  ${AEM} start $@
}

main "$@"

# dev_into.sh
function main() {
  ${AEM} enter $@
}

main "$@"

包装器模式在Apollo Docker Scripts中得到了广泛应用。dev_start.sh和dev_into.sh本质上是AEM命令行工具的简单包装器,它们通过转发命令行参数来调用AEM提供的start和enter功能。这种设计模式的核心思想是将复杂的底层实现隐藏在简单的用户接口之后,提供了更友好、更语义化的命令行体验。用户无需记住AEM的完整调用语法,只需执行相应功能的专用脚本即可。包装器模式的优势在于实现了关注点分离,使得脚本的职责单一化,便于维护和测试。

7.2 模板方法模式(Template Method Pattern)

bash 复制代码
# docker_base.sh中的函数模板
function geo_specific_config() {
  local geo="$1"
  if [[ -z "${geo}" ]]; then
    info "Use default GeoLocation settings"
    return
  fi
  info "Setup geolocation specific configurations for ${geo}"
  if [[ "${geo}" == "cn" ]]; then
    GEO_REGISTRY="registry.baidubce.com"
  else
    info "GeoLocation settings for ${geo} is not ready, fallback to default"
  fi
}

模板方法模式通过定义操作的骨架,将具体步骤延迟到子类或后续调用中实现。在docker_base.sh中,geo_specific_config函数定义了处理地理配置的标准流程:首先检查参数是否为空,如果为空则使用默认配置;然后对于有效的地理参数,执行配置查找和设置;如果找不到对应配置,则输出提示并回退到默认行为。这种设计允许系统在未来轻松添加新的地理区域支持,只需扩展函数内部的配置逻辑即可,而无需修改函数的调用结构。

7.3 策略模式(Strategy Pattern)

bash 复制代码
# GPU配置策略选择
if (($USE_NVIDIA_GPU == 1)); then
  if [[ -x "$(which nvidia-container-toolkit)" || -x "$(which nvidia-container-runtime)" ]]; then
    if dpkg --compare-versions "${docker_version}" "ge" "19.03"; then
      if [[ "${HOST_ARCH}" == "aarch64" ]]; then
        DOCKER_RUN_CMD="docker run --runtime nvidia"
      else
        DOCKER_RUN_CMD="docker run --gpus all"
      fi
    ...
  elif [[ -x "$(which nvidia-docker)" ]]; then
    DOCKER_RUN_CMD="nvidia-docker run"
  fi
elif (($USE_AMD_GPU == 1)); then
  DOCKER_RUN_CMD="docker run --device=/dev/kfd --device=/dev/dri --security-opt seccomp=unconfined --group-add video"
fi

策略模式在GPU配置逻辑中得到了充分体现。系统根据检测到的GPU类型和可用工具,动态选择最合适的容器运行时策略。对于NVIDIA GPU,系统可能使用--runtime nvidia、--gpus all或nvidia-docker run中的一种;对于AMD GPU,则使用设备直通策略。这种设计将GPU配置的具体实现与调用逻辑分离,使得添加新GPU类型的支持变得简单,只需扩展配置策略选择逻辑即可。策略模式的核心优势在于算法的可互换性,系统能够在运行时根据环境条件选择最优策略。

7.4 门面模式(Facade Pattern)

bash 复制代码
# docker_base.sh作为公共接口层
export -f geo_specific_config
export -f determine_gpu_use_host
export -f stop_all_apollo_containers remove_container_if_exists
export -f check_agreement

docker_base.sh作为公共函数库,对外部提供了简化的接口层,隐藏了底层的复杂实现细节。对于其他脚本而言,它们只需要source加载这个库文件,然后调用导出的公共函数即可完成诸如GPU检测、许可证检查、容器管理等功能。这种门面模式的设计降低了模块间的耦合度,使得其他脚本无需了解函数内部的实现细节,只需关注函数的输入输出接口。门面模式提高了系统的可维护性和可扩展性,当底层实现需要修改时,只要保持接口不变,外部调用者无需任何更改。

7.5 工厂模式(Factory Pattern)

bash 复制代码
# 容器运行时命令工厂
function create_docker_run_cmd() {
  local gpu_flag=""
  local device_flags=""
  
  if [[ "${HOST_ARCH}" == "aarch64" ]]; then
    if (($USE_NVIDIA_GPU == 1)); then
      gpu_flag="--runtime nvidia"
    fi
  elif [[ "${HOST_ARCH}" == "x86_64" ]]; then
    if (($USE_NVIDIA_GPU == 1)); then
      gpu_flag="--gpus all"
    elif (($USE_AMD_GPU == 1)); then
      device_flags="--device=/dev/kfd --device=/dev/dri --security-opt seccomp=unconfined --group-add video"
    fi
  fi
  
  echo "docker run ${gpu_flag} ${device_flags}"
}

工厂模式在容器运行时命令的构建过程中得到应用。create_docker_run_cmd函数充当了命令工厂的角色,根据不同的系统配置(CPU架构、GPU类型、GPU可用性等)动态生成合适的Docker运行命令。函数封装了命令构建的复杂性,对外只暴露简单的调用接口。这种设计将命令生成的逻辑集中在一处,便于维护和修改,同时确保了在不同环境下生成的命令格式的一致性和正确性。

7.6 单例模式(Singleton Pattern)

bash 复制代码
# 全局配置状态管理
DOCKER_RUN_CMD="docker run"
USE_GPU_HOST=0
USE_AMD_GPU=0
USE_NVIDIA_GPU=0
GEO_REGISTRY=

function determine_gpu_use_host() {
  ...
}

虽然Bash脚本语言本身不直接支持面向对象的单例模式,但通过全局变量和函数配合,可以实现类似的单例效果。docker_base.sh中定义的这些全局配置变量在整个脚本执行期间保持唯一状态,被所有后续加载的脚本共享使用。determine_gpu_use_host函数作为配置初始化的唯一入口点,确保了GPU配置逻辑只被执行一次,避免了重复配置带来的不一致问题。这种设计模式确保了系统配置的全局一致性和唯一性。

7.7 责任链模式(Chain of Responsibility)

bash 复制代码
# 容器停止流程
function stop_all_apollo_containers() {
  local force="$1"
  local running_containers
  running_containers=($(docker inspect \
    $(docker ps -q) \
    --format '{{or .Config.Labels.owner "-"}}...{{.Name}}'))
  for container in ${running_containers[*]}; do
    owner="${container%%...*}"
    name="${container##*...}"
    if [[ ("${owner}" == "${USER}") && ("${name}" =~ apollo_.*) ]]; then
      info "Now stop container ${name} ..."
      if docker stop "${name}" > /dev/null; then
        if [[ "${force}" == "-f" || "${force}" == "--force" ]]; then
          docker rm -f "${name}" 2> /dev/null
        fi
        info "Done."
      else
        warning "Failed."
      fi
    fi
  done
}

责任链模式在容器管理和停止流程中得到了应用。stop_all_apollo_containers函数定义了处理容器停止的责任链:首先通过docker inspect获取所有运行中容器的信息;然后遍历容器列表,每个容器依次通过条件过滤(用户匹配、名称模式匹配);对于符合条件且需要停止的容器,执行停止操作;如果指定了强制参数,则在停止后执行删除操作。这种设计将复杂的批量操作分解为简单的步骤链,每个步骤专注于特定的任务,代码结构清晰,易于理解和维护。

8. 总结

Apollo Docker Scripts子模块作为Apollo自动驾驶平台的Docker环境管理组件,通过精心设计的模块化架构实现了对开发环境的高效管理。该模块提供了容器启动、入驻、GPU配置、跨平台支持等核心功能,采用包装器模式、策略模式、门面模式等多种设计模式,确保了系统的可维护性和可扩展性。模块支持多种GPU类型(NVIDIA和AMD)和多种CPU架构(ARM64和x86_64),能够适应不同的开发环境需求。许可证管理机制确保了平台的合规使用,而地理配置功能则为不同地区的用户提供了优化的镜像仓库访问。整体而言,该子模块在基础设施层面为Apollo开发者提供了稳定、一致的开发体验,是Apollo生态系统不可或缺的组成部分。

相关推荐
Coder个人博客2 小时前
08_apollo_scripts_scripts子模块软件架构分析
架构
Coder个人博客2 小时前
09_apollo_docker_build子模块软件架构分析文档
架构
云深19333 小时前
解剖某头部AI编程工具:一个终端原生 AI IDE 的工程哲学与架构全景
架构
爱浦路 IPLOOK3 小时前
分布式UPF架构:让低时延与大带宽不再是难题
分布式·架构
universeplayer3 小时前
我花了一天读完 Claude Code 泄露的全部源码,这是我发现的
架构
笑笑先生3 小时前
从接口搬运工到研发控制平面,BFF 到底在解决什么?
前端·架构·node.js
BPM_宏天低代码3 小时前
【宏天架构】CRM系统的API网关:基于Spring Cloud Gateway
微服务·云原生·架构
ai产品老杨3 小时前
协议融合与边缘协同:基于 GB28181/RTSP 的企业级 AI 视频中台架构解析
人工智能·架构·音视频
yoso3 小时前
Claude Code 源码架构深度解析:1884 个文件背后的 AI 编程工具设计哲学
算法·架构