11_apollo_docker_setup_host子模块软件架构分析
1. 概述
apollo_docker_setup_host子模块是Apollo自动驾驶平台的主机环境配置工具,负责在宿主机上安装和配置Docker环境、设置系统参数、配置设备规则以及管理Docker资源。该模块通过一系列Shell脚本实现自动化部署,包括Docker安装、内核模块加载、Udev设备规则配置、时间同步设置和核心转储配置等功能,为Apollo系统在容器化环境中运行提供必要的基础设施支持。
2. 软件架构图
graph TB
subgraph "用户层"
U1[系统管理员]
U2[开发人员]
U3[运维人员]
end
subgraph "脚本层"
S1[setup_host.sh]
S2[install_docker.sh]
S3[cleanup_resources.sh]
end
subgraph "配置层"
C1[Udev规则配置]
C2[内核参数配置]
C3[时间同步配置]
C4[Docker配置]
end
subgraph "系统层"
SYS1[Linux内核]
SYS2[Udev子系统]
SYS3[Docker守护进程]
SYS4[系统服务]
end
subgraph "设备层"
D1[USB串口设备]
D2[FPD-Link相机]
D3[其他外设]
end
U1 --> S1
U2 --> S2
U3 --> S3
S1 --> C1
S1 --> C2
S1 --> C3
S2 --> C4
S3 --> C4
C1 --> SYS2
C2 --> SYS1
C3 --> SYS4
C4 --> SYS3
SYS2 --> D1
SYS2 --> D2
SYS2 --> D3
style S1 fill:#e1f5fe
style S2 fill:#e1f5fe
style S3 fill:#e1f5fe
style C1 fill:#f3e5f5
style C2 fill:#f3e5f5
style C3 fill:#f3e5f5
style C4 fill:#f3e5f5
3. 调用流程图
sequenceDiagram
participant User as 用户
participant Setup as setup_host.sh
participant Install as install_docker.sh
participant Cleanup as cleanup_resources.sh
participant System as 系统服务
participant Docker as Docker守护进程
participant Udev as Udev子系统
User->>Setup: 执行setup_host.sh
Setup->>Setup: 配置核心转储格式
Setup->>Setup: 设置NTP时间同步
Setup->>Setup: 复制Udev规则文件
Setup->>Setup: 配置UVC视频模块
Setup->>Udev: 加载设备规则
Udev-->>Setup: 规则加载完成
Setup-->>User: 主机配置完成
User->>Install: 执行install_docker.sh
Install->>Install: 检查内核版本
Install->>Install: 安装文件系统支持
Install->>Install: 安装前置依赖包
Install->>Install: 配置Docker仓库
Install->>System: 安装Docker软件包
System-->>Install: 安装完成
Install->>Install: 配置用户权限
Install->>Docker: 重启Docker服务
Docker-->>Install: 服务启动完成
Install-->>User: Docker安装完成
User->>Cleanup: 执行cleanup_resources.sh
Cleanup->>Cleanup: 检查容器环境
Cleanup->>Docker: 查询已退出容器
Docker-->>Cleanup: 返回容器列表
alt 存在已退出容器
Cleanup->>Docker: 删除已退出容器
Docker-->>Cleanup: 删除完成
end
Cleanup->>Docker: 查询悬空镜像
Docker-->>Cleanup: 返回镜像列表
alt 存在悬空镜像
Cleanup->>Docker: 删除悬空镜像
Docker-->>Cleanup: 删除完成
end
Cleanup->>Docker: 查询悬空卷
Docker-->>Cleanup: 返回卷列表
alt 存在悬空卷
Cleanup->>Docker: 删除悬空卷
Docker-->>Cleanup: 删除完成
end
Cleanup-->>User: 资源清理完成
4. UML类图
4.1 核心脚本类图
classDiagram
class SetupHostScript {
+execute()
+configureCoreDump()
+setupNtpSync()
+installUdevRules()
+configureUvcModule()
-apolloRootDir: string
}
class InstallDockerScript {
+execute(action: string)
+installDocker()
+uninstallDocker()
+installFilesystemSupport()
+installPrereqPackages()
+setupDockerRepoAndInstall()
+postInstallSettings()
-arch: string
-archAlias: string
}
class CleanupResourcesScript {
+execute()
+cleanupExitedContainers()
+cleanupDanglingImages()
+cleanupDanglingVolumes()
-isInContainer: boolean
}
class UdevRuleManager {
+copyRules(source: string, dest: string)
+loadRules()
+reloadRules()
-ruleFiles: list~string~
}
class KernelConfigurator {
+setCorePattern(pattern: string)
+loadModule(moduleName: string)
+checkKernelVersion(): string
-kernelVersion: string
}
class TimeSyncManager {
+setupNtpSync()
+addCronJob(cronEntry: string)
+checkCronEntry(pattern: string): boolean
-ntpServer: string
}
class DockerInstaller {
+installPackages()
+setupRepository()
+configureUserPermissions()
+restartService()
-packages: list~string~
-repositoryUrl: string
}
class DockerCleaner {
+getExitedContainers(): list~string~
+getDanglingImages(): list~string~
+getDanglingVolumes(): list~string~
+removeContainers(ids: list~string~)
+removeImages(ids: list~string~)
+removeVolumes(ids: list~string~)
}
SetupHostScript --> UdevRuleManager
SetupHostScript --> KernelConfigurator
SetupHostScript --> TimeSyncManager
InstallDockerScript --> DockerInstaller
InstallDockerScript --> KernelConfigurator
CleanupResourcesScript --> DockerCleaner
4.2 配置管理类图
classDiagram
class ConfigManager {
+loadConfig(path: string)
+saveConfig(path: string)
+getValue(key: string): string
+setValue(key: string, value: string)
-configData: map~string,string~
}
class UdevConfig {
+getCameraRules(): list~UdevRule~
+getGpsRules(): list~UdevRule~
+addRule(rule: UdevRule)
+removeRule(ruleId: string)
-rules: list~UdevRule~
}
class UdevRule {
+getSubsystem(): string
+getDriver(): string
+getAttributes(): map~string,string~
+getSymlink(): string
+getMode(): string
+getOwner(): string
+getGroup(): string
-subsystem: string
-driver: string
-attributes: map~string,string~
-symlink: string
-mode: string
-owner: string
-group: string
}
class CameraRule {
+getCameraType(): CameraType
+getPosition(): CameraPosition
+getFocalLength(): string
-cameraType: CameraType
-position: CameraPosition
-focalLength: string
}
class GpsRule {
+getPortNumber(): int
+getDeviceName(): string
-portNumber: int
-deviceName: string
}
class SystemConfig {
+getCorePattern(): string
+setCorePattern(pattern: string)
+getNtpServer(): string
+setNtpServer(server: string)
-corePattern: string
-ntpServer: string
-cronEntries: list~string~
}
ConfigManager --> UdevConfig
ConfigManager --> SystemConfig
UdevConfig --> UdevRule
UdevRule <|-- CameraRule
UdevRule <|-- GpsRule
4.3 设备管理类图
classDiagram
class DeviceManager {
+detectDevices(): list~Device~
+mapDevice(device: Device, alias: string)
+getDeviceByAlias(alias: string): Device
-devices: map~string,Device~
}
class Device {
<<abstract>>
+getId(): string
+getType(): DeviceType
+getPath(): string
+getAttributes(): map~string,string~
-id: string
-type: DeviceType
-path: string
-attributes: map~string,string~
}
class CameraDevice {
+getResolution(): Resolution
+getFrameRate(): float
+getFocalLength(): string
+getPosition(): CameraPosition
-resolution: Resolution
-frameRate: float
-focalLength: string
-position: CameraPosition
}
class GpsDevice {
+getPortNumber(): int
+getBaudRate(): int
+getDriver(): string
-portNumber: int
-baudRate: int
-driver: string
}
class FpdLinkCamera {
+getModel(): string
+getInterface(): string
-model: string
-interface: string
}
class UsbSerialDevice {
+getVendorId(): string
+getProductId(): string
-vendorId: string
-productId: string
}
DeviceManager --> Device
Device <|-- CameraDevice
Device <|-- GpsDevice
CameraDevice <|-- FpdLinkCamera
GpsDevice <|-- UsbSerialDevice
5. 状态机分析
5.1 主机配置状态机
stateDiagram-v2
[*] --> IDLE: 脚本启动
IDLE --> CHECKING_ENVIRONMENT: 开始配置
CHECKING_ENVIRONMENT --> CONFIGURING_COREDUMP: 环境检查通过
CHECKING_ENVIRONMENT --> ERROR: 环境检查失败
CONFIGURING_COREDUMP --> CONFIGURING_NTP: 核心转储配置完成
CONFIGURING_NTP --> INSTALLING_UDEV_RULES: NTP配置完成
INSTALLING_UDEV_RULES --> CONFIGURING_UVC: Udev规则安装完成
CONFIGURING_UVC --> COMPLETED: UVC模块配置完成
COMPLETED --> [*]: 配置成功
ERROR --> [*]: 配置失败
note right of CHECKING_ENVIRONMENT : 检查Apollo根目录、系统权限
note right of CONFIGURING_COREDUMP : 设置/proc/sys/kernel/core_pattern
note right of CONFIGURING_NTP : 添加ntpdate到crontab
note right of INSTALLING_UDEV_RULES : 复制udev规则到/etc/
note right of CONFIGURING_UVC : 添加uvcvideo到/etc/modules
5.2 Docker安装状态机
stateDiagram-v2
[*] --> IDLE: 脚本启动
IDLE --> CHECKING_KERNEL: 开始安装
CHECKING_KERNEL --> INSTALLING_FS_SUPPORT: 内核版本检查通过
CHECKING_KERNEL --> ERROR: 内核版本不兼容
INSTALLING_FS_SUPPORT --> INSTALLING_PREREQS: 文件系统支持安装完成
INSTALLING_PREREQS --> SETTING_UP_REPO: 前置依赖安装完成
SETTING_UP_REPO --> INSTALLING_DOCKER: 仓库配置完成
INSTALLING_DOCKER --> CONFIGURING_PERMISSIONS: Docker安装完成
CONFIGURING_PERMISSIONS --> RESTARTING_SERVICE: 用户权限配置完成
RESTARTING_SERVICE --> COMPLETED: 服务重启成功
COMPLETED --> [*]: 安装成功
ERROR --> [*]: 安装失败
note right of CHECKING_KERNEL : 检查内核版本>=4.0,验证overlay模块
note right of INSTALLING_FS_SUPPORT : 加载overlay内核模块
note right of INSTALLING_PREREQS : 安装apt-transport-https、curl等
note right of SETTING_UP_REPO : 添加Docker官方APT仓库
note right of INSTALLING_DOCKER : 安装docker-ce、docker-ce-cli、containerd.io
note right of CONFIGURING_PERMISSIONS : 将用户添加到docker组
note right of RESTARTING_SERVICE : 重启Docker守护进程
5.3 资源清理状态机
stateDiagram-v2
[*] --> IDLE: 脚本启动
IDLE --> CHECKING_CONTAINER: 开始清理
CHECKING_CONTAINER --> CLEANING_CONTAINERS: 检测到已退出容器
CHECKING_CONTAINER --> CHECKING_IMAGES: 无已退出容器
CLEANING_CONTAINERS --> CHECKING_IMAGES: 容器清理完成
CHECKING_IMAGES --> CLEANING_IMAGES: 检测到悬空镜像
CHECKING_IMAGES --> CHECKING_VOLUMES: 无悬空镜像
CLEANING_IMAGES --> CHECKING_VOLUMES: 镜像清理完成
CHECKING_VOLUMES --> CLEANING_VOLUMES: 检测到悬空卷
CHECKING_VOLUMES --> COMPLETED: 无悬空卷
CLEANING_VOLUMES --> COMPLETED: 卷清理完成
COMPLETED --> [*]: 清理完成
note right of CHECKING_CONTAINER : 检查是否在容器内运行
note right of CLEANING_CONTAINERS : 删除status=exited的容器
note right of CHECKING_IMAGES : 查询dangling=true的镜像
note right of CLEANING_IMAGES : 删除悬空镜像
note right of CHECKING_VOLUMES : 查询dangling=true的卷
note right of CLEANING_VOLUMES : 删除悬空卷
5.4 设备映射状态机
stateDiagram-v2
[*] --> IDLE: 系统启动
IDLE --> DETECTING_DEVICE: 设备插入
DETECTING_DEVICE --> MATCHING_RULE: 设备检测完成
MATCHING_RULE --> CREATING_SYMLINK: 规则匹配成功
MATCHING_RULE --> IDLE: 规则匹配失败
CREATING_SYMLINK --> SETTING_PERMISSIONS: 符号链接创建完成
SETTING_PERMISSIONS --> SETTING_OWNERSHIP: 权限设置完成
SETTING_OWNERSHIP --> READY: 所有权设置完成
READY --> IDLE: 设备移除
note right of DETECTING_DEVICE : Udev检测设备属性
note right of MATCHING_RULE : 匹配udev规则文件
note right of CREATING_SYMLINK : 创建设备符号链接
note right of SETTING_PERMISSIONS : 设置设备访问权限
note right of SETTING_OWNERSHIP : 设置设备所有者和组
6. 源码分析
6.1 setup_host.sh脚本分析
6.1.1 脚本初始化部分
setup_host.sh脚本是主机配置的核心脚本,负责设置Apollo运行所需的基础环境。脚本首先通过版权声明和许可证信息,确保代码的合法性和规范性。脚本使用Apache 2.0许可证,这是开源项目中常用的许可证类型。
#!/usr/bin/env bash
###############################################################################
# Copyright 2018 The Apollo Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
6.1.2 Apollo根目录定位
脚本通过动态计算Apollo根目录,确保脚本可以从任何位置执行。这种设计提高了脚本的灵活性和可移植性。
APOLLO_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
这段代码使用了Bash的内置命令来获取脚本所在目录的绝对路径。${BASH_SOURCE[0]}获取当前脚本的路径,dirname命令提取目录部分,cd命令切换到该目录,pwd命令获取当前工作目录的绝对路径。最后通过../..导航到Apollo项目的根目录。
6.1.3 核心转储配置
核心转储配置是系统调试和故障分析的重要功能。脚本通过修改/proc/sys/kernel/core_pattern文件来设置核心转储文件的存储位置和命名格式。
# Setup core dump format.
if [ -e /proc/sys/kernel ]; then
echo "${APOLLO_ROOT_DIR}/data/core/core_%e.%p" | \
sudo tee /proc/sys/kernel/core_pattern
fi
这段代码首先检查/proc/sys/kernel目录是否存在,这是Linux内核参数的虚拟文件系统。如果存在,则使用echo命令将核心转储格式写入core_pattern文件。格式字符串core_%e.%p中,%e表示可执行文件名,%p表示进程ID。sudo tee命令确保以root权限写入文件。
6.1.4 NTP时间同步配置
时间同步对于分布式系统至关重要,特别是对于需要精确时间戳的自动驾驶系统。脚本通过配置cron任务来实现每分钟一次的时间同步。
# Setup ntpdate to run once per minute. Log at /var/log/syslog.
grep -q ntpdate /etc/crontab
if [ $? -ne 0 ]; then
echo "*/1 * * * * root ntpdate -v -u us.pool.ntp.org" | \
sudo tee -a /etc/crontab
fi
这段代码首先使用grep -q命令在/etc/crontab文件中搜索ntpdate关键字。-q选项表示静默模式,只返回退出状态码。如果未找到(退出状态码不为0),则添加新的cron任务。cron任务格式*/1 * * * *表示每分钟执行一次,root指定执行用户,ntpdate -v -u us.pool.ntp.org是实际执行的命令,其中-v表示详细输出,-u表示使用非特权端口。
6.1.5 Udev规则安装
Udev规则用于管理Linux系统中的设备,包括设备命名、权限设置和符号链接创建。脚本将Apollo项目中的Udev规则文件复制到系统目录。
# Add udev rules.
sudo cp -r ${APOLLO_ROOT_DIR}/docker/setup_host/etc/* /etc/
这段代码使用cp -r命令递归复制docker/setup_host/etc/目录下的所有内容到系统的/etc/目录。sudo确保以root权限执行复制操作。这种设计允许Apollo项目自带设备规则,简化了部署过程。
6.1.6 UVC视频模块配置
UVC(USB Video Class)是USB视频设备的标准驱动。脚本通过配置UVC模块的时钟参数来优化视频设备的性能。
# Add uvcvideo clock config.
grep -q uvcvideo /etc/modules
if [ $? -ne 0 ]; then
echo "uvcvideo clock=realtime" | sudo tee -a /etc/modules
fi
这段代码首先检查/etc/modules文件中是否已包含uvcvideo模块。如果未包含,则添加配置行uvcvideo clock=realtime。clock=realtime参数指定使用实时时钟,这对于视频采集的同步非常重要。
6.2 install_docker.sh脚本分析
6.2.1 脚本架构和辅助函数
install_docker.sh脚本负责Docker的安装和卸载,采用模块化设计,将不同功能封装到独立的函数中。脚本首先定义了一些颜色和格式的辅助变量,用于美化输出信息。
ARCH="$(uname -m)"
##==============================================================##
## Note(storypku): DRY broken for this self-contained script.
##==============================================================##
BOLD='\033[1m'
RED='\033[0;31m'
WHITE='\033[34m'
NO_COLOR='\033[0m'
function info() {
(echo >&2 -e "[${WHITE}${BOLD}INFO${NO_COLOR}] $*")
}
function error() {
(echo >&2 -e "[${RED}ERROR${NO_COLOR}] $*")
}
脚本使用uname -m命令获取系统架构,支持x86_64和aarch64两种架构。颜色变量使用ANSI转义序列定义,info和error函数提供统一的日志输出接口。
6.2.2 文件系统支持安装
Docker使用overlay2存储驱动,需要内核支持overlay文件系统。install_filesystem_support函数负责检查和加载overlay模块。
function install_filesystem_support() {
local kernel_version="$(uname -r)"
if [ "$kernel_version" == "4.4.32-apollo-2-RT" ]; then
info "Apollo realtime kernel ${kernel_version} found."
sudo modprobe overlay
else
local kernel_version_major=${kernel_version:0:1}
local overlay_ko_path="/lib/modules/$kernel_version/kernel/fs/overlayfs/overlay.ko"
if [ "${kernel_version_major}" -ge 4 ] && [ -f "${overlay_ko_path}" ] ; then
info "Linux kernel ${kernel_version} has builtin overlay2 support."
sudo modprobe overlay
elif [ ${kernel_version_major} -ge 4 ]; then
error "Overlay kernel module not found at ${overlay_ko_path}." \
"Are you running on a customized Linux kernel? "
exit 1
else
error "Linux kernel version >= 4 expected. Got ${kernel_version}"
exit 1
fi
fi
}
该函数首先获取内核版本,然后进行多种情况的处理。对于Apollo专用的实时内核,直接加载overlay模块。对于其他内核,检查主版本号是否大于等于4,并验证overlay模块文件是否存在。如果条件满足,加载overlay模块;否则输出错误信息并退出。
6.2.3 前置依赖包安装
Docker安装需要一些前置依赖包,install_prereq_packages函数负责安装这些依赖。
function install_prereq_packages() {
sudo apt-get -y update
sudo apt-get -y install \
apt-transport-https \
ca-certificates \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
}
该函数首先更新APT包索引,然后安装一系列必要的软件包。apt-transport-https支持通过HTTPS访问APT仓库,ca-certificates提供SSL证书,curl用于下载文件,gnupg-agent用于GPG密钥管理,software-properties-common用于管理软件源。
6.2.4 Docker仓库配置和安装
setup_docker_repo_and_install函数负责配置Docker官方仓库并安装Docker软件包。
function setup_docker_repo_and_install() {
local issues_link="https://github.com/ApolloAuto/apollo/issues"
local arch_alias=
if [ "${ARCH}" == "x86_64" ]; then
arch_alias="amd64"
elif [ "${ARCH}" == "aarch64" ]; then
arch_alias="arm64"
else
error "Currently, ${ARCH} support has not been implemented yet." \
"You can create an issue at ${issues_link}."
exit 1
fi
curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=${arch_alias}] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
sudo apt-get update
sudo apt-get install -y docker-ce \
docker-ce-cli \
containerd.io
}
该函数首先根据系统架构设置架构别名,x86_64对应amd64,aarch64对应arm64。然后使用curl下载Docker的GPG密钥并添加到APT密钥环。接着添加Docker官方APT仓库,仓库URL包含架构别名和Ubuntu发行版代号。最后更新包索引并安装Docker相关软件包。
6.2.5 安装后配置
post_install_settings函数负责Docker安装后的配置,包括用户权限设置和服务重启。
function post_install_settings() {
sudo usermod -aG docker $USER
sudo systemctl restart docker
# sudo groupadd docker
# sudo gpasswd -a $USER docker
# newgrp docker
}
该函数使用usermod命令将当前用户添加到docker组,这样用户就可以在不使用sudo的情况下执行Docker命令。然后使用systemctl重启Docker服务,使配置生效。注释掉的代码提供了另一种用户组配置方法。
6.2.6 Docker卸载功能
uninstall_docker函数提供了Docker的卸载功能,用于清理Docker相关软件包和配置。
function uninstall_docker() {
sudo apt-get -y remove docker docker-engine docker.io
sudo apt-get purge docker-ce
sudo sed -i '/download.docker.com/d' /etc/apt/sources.list
sudo apt-key del 0EBFCD88
}
该函数首先使用apt-get remove删除旧版本的Docker软件包,然后使用apt-get purge彻底删除Docker CE及其配置文件。接着使用sed命令从APT源列表中删除Docker仓库配置,最后删除Docker的GPG密钥。
6.2.7 主函数和命令行参数处理
main函数和install_docker函数提供了命令行接口,支持install和uninstall两种操作。
function install_docker() {
# Architecture support, currently: x86_64, aarch64
install_filesystem_support
install_prereq_packages
setup_docker_repo_and_install
post_install_settings
}
function main() {
case $1 in
install)
install_docker
;;
uninstall)
uninstall_docker
;;
*)
install_docker
;;
esac
}
main "$@"
install_docker函数按顺序调用各个安装步骤函数。main函数使用case语句处理命令行参数,支持install、uninstall和默认操作。最后调用main函数并传递所有命令行参数。
6.3 cleanup_resources.sh脚本分析
6.3.1 容器环境检测
cleanup_resources.sh脚本用于清理Docker资源,首先检测是否在容器环境中运行。
if [ -f /.dockerenv ]; then
echo "Oops, this script is expected to run on host rather than from within container."
exit 1
fi
这段代码检查/.dockerenv文件是否存在,该文件是Docker容器创建的标识文件。如果存在,说明脚本在容器内运行,输出错误信息并退出。这种设计防止了在容器内执行主机清理操作。
6.3.2 已退出容器清理
脚本使用Docker命令查询和清理已退出的容器。
exited_containers="$(docker ps -qa --no-trunc --filter "status=exited")"
if [ -z "${exited_containers}" ]; then
echo "Congrats, no exited docker containers found."
else
echo "Exited containers found, cleanup ..."
docker rm ${exited_containers}
fi
这段代码使用docker ps -qa命令查询所有容器(包括停止的容器),--no-trunc选项显示完整的容器ID,--filter "status=exited"过滤出已退出的容器。如果查询结果为空,输出提示信息;否则使用docker rm命令删除这些容器。
6.3.3 悬空镜像清理
悬空镜像是没有标签且未被任何容器使用的镜像,占用磁盘空间。
dangling_images="$(docker images --filter "dangling=true" -q --no-trunc)"
if [ -z "${dangling_images}" ]; then
echo "Congrats, no dangling docker images found."
else
echo "Dangling images found, cleanup ..."
docker rmi ${dangling_images}
fi
这段代码使用docker images命令查询镜像,--filter "dangling=true"过滤出悬空镜像,-q选项只显示镜像ID,--no-trunc显示完整的镜像ID。如果查询结果为空,输出提示信息;否则使用docker rmi命令删除这些镜像。
6.3.4 悬空卷清理
悬空卷是不被任何容器使用的Docker卷。
dangling_volumes="$(docker volume ls -qf dangling=true)"
if [ -z "${dangling_volumes}" ]; then
echo "Congrats, no dangling docker volumes found."
else
echo "Dangling volumes found, cleanup ..."
docker volume rm ${dangling_volumes}
fi
这段代码使用docker volume ls命令查询卷,-qf dangling=true过滤出悬空卷并只显示卷名。如果查询结果为空,输出提示信息;否则使用docker volume rm命令删除这些卷。
6.4 Udev规则文件分析
6.4.1 FPD-Link相机规则
99-asucam.rules文件定义了FPD-Link相机的Udev规则,用于将相机设备映射到Apollo系统识别的标准设备名。
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD5-0106", MODE="0666", SYMLINK+="camera/front_6mm", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD5-0106", MODE="0666", SYMLINK+="camera/obstacle", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD5-0106", MODE="0666", SYMLINK+="camera/lanemark", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD4-0101", MODE="0666", SYMLINK+="camera/front_12mm", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD4-0101", MODE="0666", SYMLINK+="camera/trafficlights", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD1-0002", MODE="0666", SYMLINK+="camera/left_front", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD2-0103", MODE="0666", SYMLINK+="camera/right_front", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD3-0002", MODE="0666", SYMLINK+="camera/rear_6mm", OWNER="apollo", GROUP="apollo"
每条Udev规则包含多个匹配条件和动作。SUBSYSTEM=="video4linux"指定设备子系统为视频设备,SUBSYSTEMS=="pci"指定父设备子系统为PCI,ATTR{name}=="FPD5-0106"指定设备名称属性。动作部分包括MODE="0666"设置设备权限为读写,SYMLINK+="camera/front_6mm"创建符号链接,OWNER="apollo"和GROUP="apollo"设置设备所有者和组。
6.4.2 USB串口GPS规则
99-usbtty.rules文件定义了USB串口GPS设备的Udev规则。
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", DRIVERS=="novatel_gps", ATTRS{port_number}=="0", MODE="0666", SYMLINK+="novatel0", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", DRIVERS=="novatel_gps", ATTRS{port_number}=="1", MODE="0666", SYMLINK+="novatel1", OWNER="apollo", GROUP="apollo"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", DRIVERS=="novatel_gps", ATTRS{port_number}=="2", MODE="0666", SYMLINK+="novatel2", OWNER="apollo", GROUP="apollo"
这些规则用于将Novatel GPS设备的USB串口映射到标准设备名。SUBSYSTEM=="tty"指定设备子系统为终端设备,SUBSYSTEMS=="usb-serial"指定父设备子系统为USB串口,DRIVERS=="novatel_gps"指定驱动程序为Novatel GPS驱动,ATTRS{port_number}=="0"指定端口号。每条规则创建一个符号链接novatel0、novatel1或novatel2,对应不同的GPS端口。
7. 设计模式
7.1 模板方法模式(Template Method Pattern)
setup_host.sh脚本体现了模板方法模式的思想。脚本定义了主机配置的固定流程,包括核心转储配置、NTP时间同步、Udev规则安装和UVC模块配置等步骤。这些步骤按照固定的顺序执行,但每个步骤的具体实现可以独立变化。
# 模板方法:主机配置流程
# 1. 配置核心转储格式
if [ -e /proc/sys/kernel ]; then
echo "${APOLLO_ROOT_DIR}/data/core/core_%e.%p" | \
sudo tee /proc/sys/kernel/core_pattern
fi
# 2. 设置NTP时间同步
grep -q ntpdate /etc/crontab
if [ $? -ne 0 ]; then
echo "*/1 * * * * root ntpdate -v -u us.pool.ntp.org" | \
sudo tee -a /etc/crontab
fi
# 3. 安装Udev规则
sudo cp -r ${APOLLO_ROOT_DIR}/docker/setup_host/etc/* /etc/
# 4. 配置UVC视频模块
grep -q uvcvideo /etc/modules
if [ $? -ne 0 ]; then
echo "uvcvideo clock=realtime" | sudo tee -a /etc/modules
fi
这种设计模式的优势在于:
- 提供了统一的配置流程,确保所有必要的配置步骤都被执行
- 每个步骤可以独立修改,不影响其他步骤
- 便于添加新的配置步骤,扩展系统功能
7.2 策略模式(Strategy Pattern)
install_docker.sh脚本体现了策略模式的思想。脚本支持install和uninstall两种操作策略,通过命令行参数选择执行哪种策略。
function main() {
case $1 in
install)
install_docker
;;
uninstall)
uninstall_docker
;;
*)
install_docker
;;
esac
}
这种设计模式的优势在于:
- 将不同的操作策略封装到独立的函数中
- 通过统一的接口(main函数)选择执行策略
- 便于添加新的操作策略,如upgrade、reinstall等
7.3 责任链模式(Chain of Responsibility Pattern)
install_docker.sh脚本的install_docker函数体现了责任链模式的思想。函数按顺序调用多个子函数,每个子函数负责一个特定的安装步骤。如果某个步骤失败,整个安装过程终止。
function install_docker() {
# Architecture support, currently: x86_64, aarch64
install_filesystem_support
install_prereq_packages
setup_docker_repo_and_install
post_install_settings
}
这种设计模式的优势在于:
- 将复杂的安装过程分解为多个简单的步骤
- 每个步骤可以独立测试和调试
- 便于修改或替换某个步骤的实现
7.4 工厂模式(Factory Pattern)
Udev规则文件体现了工厂模式的思想。规则文件定义了设备映射的"工厂",根据设备属性创建相应的符号链接。
SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD5-0106", MODE="0666", SYMLINK+="camera/front_6mm", OWNER="apollo", GROUP="apollo"
这种设计模式的优势在于:
- 根据设备属性自动创建设备映射
- 统一管理设备的命名和权限
- 便于添加新的设备类型和映射规则
7.5 单例模式(Singleton Pattern)
setup_host.sh脚本中的Apollo根目录变量体现了单例模式的思想。脚本通过计算获取唯一的Apollo根目录,并在整个脚本中使用这个变量。
APOLLO_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
这种设计模式的优势在于:
- 确保整个脚本使用相同的根目录路径
- 避免重复计算,提高效率
- 便于维护和修改根目录路径
7.6 观察者模式(Observer Pattern)
cleanup_resources.sh脚本体现了观察者模式的思想。脚本"观察"Docker资源的状态,并根据状态执行相应的清理操作。
exited_containers="$(docker ps -qa --no-trunc --filter "status=exited")"
if [ -z "${exited_containers}" ]; then
echo "Congrats, no exited docker containers found."
else
echo "Exited containers found, cleanup ..."
docker rm ${exited_containers}
fi
这种设计模式的优势在于:
- 根据资源状态动态执行清理操作
- 避免不必要的清理操作,提高效率
- 提供清晰的反馈信息,增强用户体验
7.7 命令模式(Command Pattern)
install_docker.sh脚本体现了命令模式的思想。脚本将不同的操作封装到独立的函数中,通过main函数统一调度执行。
function install_docker() {
install_filesystem_support
install_prereq_packages
setup_docker_repo_and_install
post_install_settings
}
function uninstall_docker() {
sudo apt-get -y remove docker docker-engine docker.io
sudo apt-get purge docker-ce
sudo sed -i '/download.docker.com/d' /etc/apt/sources.list
sudo apt-key del 0EBFCD88
}
这种设计模式的优势在于:
- 将操作封装到独立的函数中,提高代码的可读性
- 便于添加新的操作命令
- 支持操作的撤销和重做(如uninstall操作)
7.8 装饰器模式(Decorator Pattern)
install_docker.sh脚本中的info和error函数体现了装饰器模式的思想。这些函数装饰了标准的echo命令,添加了颜色和格式化输出。
function info() {
(echo >&2 -e "[${WHITE}${BOLD}INFO${NO_COLOR}] $*")
}
function error() {
(echo >&2 -e "[${RED}ERROR${NO_COLOR}] $*")
}
这种设计模式的优势在于:
- 统一了日志输出的格式
- 提高了日志信息的可读性
- 便于修改日志输出的样式和行为
8. 总结
apollo_docker_setup_host子模块通过精心设计的Shell脚本实现了Apollo主机环境的自动化配置。该模块采用模块化设计,将Docker安装、系统配置、设备管理和资源清理等功能封装到独立的脚本中,提高了代码的可维护性和可重用性。通过模板方法、策略模式、责任链等设计模式的应用,实现了灵活的配置流程和错误处理机制。Udev规则文件提供了统一的设备映射方案,简化了设备管理。该模块为Apollo系统在容器化环境中运行提供了坚实的基础设施支持,是Apollo部署流程中不可或缺的重要组成部分。