11_apollo_docker_setup_host子模块软件架构分析

15 阅读9分钟

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=realtimeclock=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转义序列定义,infoerror函数提供统一的日志输出接口。

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"指定端口号。每条规则创建一个符号链接novatel0novatel1novatel2,对应不同的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

  这种设计模式的优势在于:

  1. 提供了统一的配置流程,确保所有必要的配置步骤都被执行
  2. 每个步骤可以独立修改,不影响其他步骤
  3. 便于添加新的配置步骤,扩展系统功能

7.2 策略模式(Strategy Pattern)

  install_docker.sh脚本体现了策略模式的思想。脚本支持install和uninstall两种操作策略,通过命令行参数选择执行哪种策略。

function main() {
  case $1 in
    install)
      install_docker
      ;;
    uninstall)
      uninstall_docker
      ;;
    *)
      install_docker
      ;;
  esac
}

  这种设计模式的优势在于:

  1. 将不同的操作策略封装到独立的函数中
  2. 通过统一的接口(main函数)选择执行策略
  3. 便于添加新的操作策略,如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
}

  这种设计模式的优势在于:

  1. 将复杂的安装过程分解为多个简单的步骤
  2. 每个步骤可以独立测试和调试
  3. 便于修改或替换某个步骤的实现

7.4 工厂模式(Factory Pattern)

  Udev规则文件体现了工厂模式的思想。规则文件定义了设备映射的"工厂",根据设备属性创建相应的符号链接。

SUBSYSTEM=="video4linux", SUBSYSTEMS=="pci", ATTR{name}=="FPD5-0106", MODE="0666", SYMLINK+="camera/front_6mm", OWNER="apollo", GROUP="apollo"

  这种设计模式的优势在于:

  1. 根据设备属性自动创建设备映射
  2. 统一管理设备的命名和权限
  3. 便于添加新的设备类型和映射规则

7.5 单例模式(Singleton Pattern)

  setup_host.sh脚本中的Apollo根目录变量体现了单例模式的思想。脚本通过计算获取唯一的Apollo根目录,并在整个脚本中使用这个变量。

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

  这种设计模式的优势在于:

  1. 确保整个脚本使用相同的根目录路径
  2. 避免重复计算,提高效率
  3. 便于维护和修改根目录路径

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

  这种设计模式的优势在于:

  1. 根据资源状态动态执行清理操作
  2. 避免不必要的清理操作,提高效率
  3. 提供清晰的反馈信息,增强用户体验

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
}

  这种设计模式的优势在于:

  1. 将操作封装到独立的函数中,提高代码的可读性
  2. 便于添加新的操作命令
  3. 支持操作的撤销和重做(如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}] $*")
}

  这种设计模式的优势在于:

  1. 统一了日志输出的格式
  2. 提高了日志信息的可读性
  3. 便于修改日志输出的样式和行为

8. 总结

  apollo_docker_setup_host子模块通过精心设计的Shell脚本实现了Apollo主机环境的自动化配置。该模块采用模块化设计,将Docker安装、系统配置、设备管理和资源清理等功能封装到独立的脚本中,提高了代码的可维护性和可重用性。通过模板方法、策略模式、责任链等设计模式的应用,实现了灵活的配置流程和错误处理机制。Udev规则文件提供了统一的设备映射方案,简化了设备管理。该模块为Apollo系统在容器化环境中运行提供了坚实的基础设施支持,是Apollo部署流程中不可或缺的重要组成部分。