11_apollo_docker_setup_host子模块软件架构分析
1. 概述
apollo_docker_setup_host子模块是Apollo自动驾驶平台的主机环境配置工具,负责在宿主机上安装和配置Docker环境、设置系统参数、配置设备规则以及管理Docker资源。该模块通过一系列Shell脚本实现自动化部署,包括Docker安装、内核模块加载、Udev设备规则配置、时间同步设置和核心转储配置等功能,为Apollo系统在容器化环境中运行提供必要的基础设施支持。
2. 软件架构图
3. 调用流程图
4. UML类图
4.1 核心脚本类图
4.2 配置管理类图
4.3 设备管理类图
5. 状态机分析
5.1 主机配置状态机
5.2 Docker安装状态机
5.3 资源清理状态机
5.4 设备映射状态机
6. 源码分析
6.1 setup_host.sh脚本分析
6.1.1 脚本初始化部分
setup_host.sh脚本是主机配置的核心脚本,负责设置Apollo运行所需的基础环境。脚本首先通过版权声明和许可证信息,确保代码的合法性和规范性。脚本使用Apache 2.0许可证,这是开源项目中常用的许可证类型。
bash
#!/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根目录,确保脚本可以从任何位置执行。这种设计提高了脚本的灵活性和可移植性。
bash
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文件来设置核心转储文件的存储位置和命名格式。
bash
# 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任务来实现每分钟一次的时间同步。
bash
# 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规则文件复制到系统目录。
bash
# 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模块的时钟参数来优化视频设备的性能。
bash
# 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的安装和卸载,采用模块化设计,将不同功能封装到独立的函数中。脚本首先定义了一些颜色和格式的辅助变量,用于美化输出信息。
bash
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模块。
bash
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函数负责安装这些依赖。
bash
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软件包。
bash
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安装后的配置,包括用户权限设置和服务重启。
bash
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相关软件包和配置。
bash
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两种操作。
bash
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资源,首先检测是否在容器环境中运行。
bash
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命令查询和清理已退出的容器。
bash
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 悬空镜像清理
悬空镜像是没有标签且未被任何容器使用的镜像,占用磁盘空间。
bash
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卷。
bash
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系统识别的标准设备名。
bash
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规则。
bash
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模块配置等步骤。这些步骤按照固定的顺序执行,但每个步骤的具体实现可以独立变化。
bash
# 模板方法:主机配置流程
# 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两种操作策略,通过命令行参数选择执行哪种策略。
bash
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函数体现了责任链模式的思想。函数按顺序调用多个子函数,每个子函数负责一个特定的安装步骤。如果某个步骤失败,整个安装过程终止。
bash
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规则文件体现了工厂模式的思想。规则文件定义了设备映射的"工厂",根据设备属性创建相应的符号链接。
bash
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根目录,并在整个脚本中使用这个变量。
bash
APOLLO_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
这种设计模式的优势在于:
- 确保整个脚本使用相同的根目录路径
- 避免重复计算,提高效率
- 便于维护和修改根目录路径
7.6 观察者模式(Observer Pattern)
cleanup_resources.sh脚本体现了观察者模式的思想。脚本"观察"Docker资源的状态,并根据状态执行相应的清理操作。
bash
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函数统一调度执行。
bash
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命令,添加了颜色和格式化输出。
bash
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部署流程中不可或缺的重要组成部分。