10_apollo_docker_scripts子模块软件架构分析文档
1. 概述
Apollo Docker Scripts子模块是Apollo自动驾驶平台的Docker环境管理组件,提供容器启动、入驻、GPU配置、跨平台支持等核心功能。该模块通过封装Docker命令行工具,实现了对开发环境的统一管理,简化了开发者在不同硬件平台上的环境配置流程,支持NVIDIA GPU加速、AMD GPU支持以及ARM64架构设备。模块采用模块化设计,将复杂的容器生命周期管理分解为可复用的函数集合,为上层应用提供了简洁的接口调用方式。该子模块在整个Apollo系统架构中扮演着基础设施层的关键角色,为开发者提供了一致的开发体验和灵活的环境配置能力。
2. 软件架构图
启动开发容器] 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
3. 调用流程图
3.1 容器启动流程
3.2 容器入驻流程
3.3 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配置流程
3.5 容器停止流程
4. UML类图
4.1 脚本模块类图
4.2 容器管理类图
4.3 运行时配置类图
4.4 数据流类图
5. 状态机分析
5.1 Docker环境初始化状态机
5.2 容器生命周期状态机
5.3 GPU运行时配置状态机
5.4 许可证验证状态机
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生态系统不可或缺的组成部分。