亚马逊AWS官方博客
为 AWS RoboMaker 构建和捆绑 ROS 应用程序
12 个月前,我们开始开发 AWS RoboMaker,这是一个云机器人服务。我们面临的最大问题之一是:如何才能轻松地在我们的服务上运行任何 ROS 应用程序? 机器人应用程序包含大量不同的软件包,拥有众多依赖项。在加入模拟时,依赖项会增加。经过大量的思考和研究,我们受到 appimage、flatpak 和 snapcraft 的启发,创造了一个可以同时在本地开发环境和我们的服务中使用的文件格式。我们将此格式称为 bundle。
在决定设计什么样的打包构件之后,我们想要创建一个命令行工具,使其易于生成。对我们来说非常重要的是,该工具要适用于现有的 ROS 生态系统。我们决定以 colcon 为基础进行构建,它是 ROS 构建工具中最新最好的。Colcon 可以构建 ROS1 和 ROS2 应用程序。并且高度可扩展,可提供我们所需的许多重要功能,开箱即用。
此处的文章介绍了现有的 ROS 构建工具以及选择 colcon
作为 ROS2 的构建工具的原因。Colcon 替我们完成了大量繁重的工作,让我们可以专注于特定功能。此外,colcon build
替换了 catkin_make
和 ament_make
,而没有对正在构建的软件包进行任何修改。这意味着,通过在 colcon
之上构建我们的工具,AWS RoboMaker 用户安装单个工具就可以执行完整工作流,以生成用于 AWS RoboMaker 的 bundle。
在这篇文章中,我将介绍创建用于 AWS RoboMaker 的 bundle 的常规工作流,以及常见问题的一些解决方案。在第二部分中,我将深入探讨最新版本的“bundle”文件格式,并概述当执行 colcon bundle
以创建 bundle 时会发生什么。
为什么创造 bundle?
ROS 模拟工作流需要具有已安装应用程序的 ROS 及其他运行时依赖项的环境。这通常通过执行多个 apt-get install
命令来实现。这种安装需要很长时间,并且无法可靠地再现,因为上游软件包可能随时发生更新。对于我们的服务,我们对使用 apt
为每次模拟运行安装新环境的可靠性有所担忧,因此我们研究了各种完整的打包格式。对于完整的 ROS 应用程序而言,最具说服力并且最可靠的分发格式是 Debian 软件包。它可以包含运行机器人所需的一切:ROS、boost 或其他外部库,以及实际的应用程序代码。
我们决定不使用已有的格式,因为我们希望可以灵活地进行下载、创建和更新优化。所以我们创造了“bundle”格式,并创建了相应的工具来支持它。我们的第一次迭代运行良好,但存在一些局限性:
- 仅使用工作区更新对 bundle 进行更新所花费的时间与最初的
colcon bundle
调用时间相同。 - 就下载和提取 bundle 内容而言,将更新的 bundle 部署到机器人的速度并不如预期的那么快。
我们很高兴宣布推出第二个版本的 bundle 格式,并通过优化来减少或消除这些局限性! 该格式现在支持部分下载和部分提取。这有三个主要好处:
- 我们减少了带宽受限的远程设备(例如使用 2G/3G 的机器人)的更新所使用的带宽。
- 我们只提取我们需要的内容,这样就不必下载整个 bundle,可以节省未更改部分的下载或提取成本。在低端设备上,这可以节省大量时间。在我们的 TurtleBots 上,重新部署时间从 60 分钟缩短到了 1 分钟。
- 既然外部存档没有压缩,我们就可以增量捆绑。对于简单的工作区更新,这可以将重新捆绑的时间从 5 分钟缩短到数秒。
AWS RoboMaker 以透明的方式支持这个新版本。在我们将其作为 colcon-bundle
的默认格式之前,如果您想尝试版本 2,请使用参数 --bundle-version 2
。我将在本博客的第二部分深入探讨这种新格式的实现。如果您有任何疑问、想法或问题,请在 GitHub 的问题部分提出,我们很乐意查看和了解。
使用 colcon
为 AWS RoboMaker 构建 bundle
将工作区与 colcon
捆绑在一起只需两个步骤:
- 使用
colcon build
构建工作区。 - 使用
colcon bundle
捆绑工作区。
构建
可以使用 colkin_make
构建的每个 ROS 软件包也可以使用 colcon build
构建。在使用 ROS 软件包的几个月中,我们通过 colcon build
构建软件包只遇到了一些小问题。如果您使用 colcon build
构建软件包时遇到问题,但却可以使用 catkin_make
成功构建,请在 colcon-ros 问题部分报告问题。
注意:从 catkin_make
到 colcon build
的最显著变化是不再有 devel
目录。如果要将工作区添加到本地环境,请执行 source install/setup.sh
。
捆绑
使用 colcon build
构建工作区后,您应该拥有 build
和 install
目录。要开始捆绑,请执行 colcon bundle
。我们的工具会检查依赖项,下载所有依赖项,并将它们与您构建的工作区一起压缩成一个 bundle,以便在 AWS RoboMaker 上使用。构建的 bundle 位于 bundle/output.tar
常见的构建问题
ROS1 开发的标准构建说明使用 catkin_make
。因为 colcon
相对较新,许多 ROS1 开发人员可能不熟悉它,但它与 catkin
版本完全兼容。在使用 colcon build
构建现有 ROS 软件包(之前使用 catkin_make
构建)时,我们遇到了一些小问题,因此我想在这里加以介绍,好让其他开发人员可以避免这些问题。
嵌套文件夹中的 CMAKElists.txt
我们遇到的常见构建问题是 colcon
无法正确枚举工作空间中的所有软件包,导致 colcon bundle
调用失败和奇怪的安装目录结构。这通常是由 CMakeLists.txt
位于嵌套文件夹结构中的中间目录所引起。这是使用 catkin_make
在目录中构建嵌套软件包所必需的,但会导致 colcon
出现问题。Colcon
支持任意深度嵌套的文件夹结构,并会自动查找软件包。以下是一个参考图表:
src/ ├── package_1/ │ ├── package.xml | └── CMakeLists.txt (OK) └── intermediate_directory/ ├── package_2 | ├── package.xml | └── CMakeLists.txt (OK) ├── package_3 | ├── package.xml | └── CMakeLists.txt (OK) └── CMakeLists.txt (REMOVE)
CMAKELists.txt 中缺少安装指令
我们遇到的另一个问题是缺少文件。当使用 catkin_make
时,devel
目录及其 setup.sh
会将所有本地软件包添加到 ROS 工具的搜索路径中。Colcon 的工作方式不同:它将您指定的所有目标安装到 install
目录中,这意味着将执行所有 CMake install()
指令。如果您遇到如下错误:[a_launchfile] is neither a launch file in package [a_package] nor is [a_package] a launch file name
或 [rosrun] Couldn't find executable named my_node below /opt/ros/kinetic/share/my_package
,那么在 CMakeLists.txt
中向 install()
添加调用可以解决这个问题。在 ROS 百科上有很好的操作示例。
常见的捆绑问题
以下是我们在使用 AWS RoboMaker 上的应用程序包时遇到的最常见问题。
找不到 [软件包名称] 的 rosdep 定义
确保为要依赖的软件包使用正确的 ROS 软件包名称。在 apt
中,软件包可能被命名为 ros-kinetic-packagename
,但在 package.xml 中它应该是 packagename
。搜索 ROS 发行版 GitHub 存储库,以查看该软件包是否在已有的 rosdep
数据库中。如果不是,您可以按照这些教程中的一个向 rosdep 添加依赖项。
缺少依赖项
我们在捆绑各种开源 ROS 库时遇到的最常见问题是缺少依赖项。很多时候,构建环境已经因为另一个软件包而安装了依赖项,并且所有内容都可以正常构建,但是在该软件包捆绑之后,却出现缺少依赖项的问题。缺少依赖项时的常见错误消息是:Could not load libxzy.so
, No such file or directory some_script.py
或 Could not load module 'python_dependency'
。要解决此问题,将依赖项添加到需要它的软件包的 package.xml
中,然后重新捆绑。
如果您在应用程序中使用来自自己的 apt
或 pip
存储库中的依赖项,则需要在 colcon bundle
调用中包含这些存储库。要解决此问题,请尝试以下操作:
- 您可以使用
--apt-sources-list
参数覆盖colcon bundle
的apt
安装程序使用的sources.list
。我们建议您将当前使用的所有源纳入其中,以避免基础 OS 和 ROS 软件包的解析错误。在 GitHub 上查看这个 examplesources.list。 - 要覆盖
pip
或pip3
源,请使用--pip-args
和--pip3-args
参数。此参数后面的字符串直接传递给pip
。例如:--extra-index-url
https://my-custom-pip-repo/index
构建和捆绑完成
现在,您可以构建并捆绑应用程序,以在 AWS RoboMaker 上进行模拟! 如果您有任何疑问或遇到问题,请在相关的 GitHub 储存库上发帖,我们将很乐意为您提供帮助。如果您对执行 colcon bundle
时实际会发生什么感兴趣,请继续阅读!
深入了解 Bundle
这部分深入探讨了 bundle 格式的内部结构以及 colcon bundle
的实现。在这部分的末尾,您将了解执行 colcon bundle
时会发生什么。在您了解了它是多么简单易用之后,我们希望您能够在 colcon
生态系统中构建您的工具,回报社区。
Bundle 文件格式
“bundle”是 AWS RoboMaker 用于运行整个 ROS 应用程序的打包格式。它包含机器人的整个传递闭包或相应的模拟应用程序。ROS 已经定义了标准打包格式,它指定了如何声明依赖项以及要使用的构建工具。使用 colcon
,我们的工具收集此信息,调用各种软件包安装程序(当前为 pip 和 apt )以下拉依赖项,将这些依赖项安装到本地临时区域,然后将它们与本地构建的工作区一起打包成一个 bundle 供 AWS RoboMaker 使用。
该工作流便产生了我们称之为“bundle”的东西。我们知道,这是一个无聊的名称,如果您对此格式的命名有什么好的想法,不妨在我们的存储库中新建一个问题!Bundle 格式的规范位于 colcon-bundle 存储库中。我会在下面对此进行概述。
此图显示了 bundle 格式 2.0 的结构。您可以在此处查看历史记录。“bundle”文件遵循标准 GNU tar 格式。它至少包含三个组件:
- 一个版本文件,可用于区分具有向后不兼容更改的软件包版本。
- 一个元数据 tarball,至少包含 bundle 中包含的一系列覆盖,还包含该 bundle 的使用者可能感兴趣的任何其他元数据。
- 至少一个覆盖。ROS 工作区支持工作区覆盖的概念。我们采用了这个概念并将其应用到整个应用程序。这允许 AWS RoboMaker 在基础 Linux 操作系统之上运行 ROS 应用程序,而无需修改基础操作系统。
我在上面概述了该格式的最新版本的优势。以下是版本 1.0 和 2.0 之间的主要差别:
- 将外部格式从
.tar.gz
更改为.tar。
它让我们可以在文件中随机访问。这很好,因为它允许已经拥有部分内容的使用者只下载更改了的内容。压缩多个覆盖会导致整个文件大小受到影响,因为gzip
对于多个较小的文件效率不高。我们认为这不是一个问题,因为对于主要更新用例,我们现在可以只下载更改了的内容。 - 从单个覆盖更改为任意数量的覆盖,并
gzip
每个覆盖。 这极大地提高了 bundle 格式的灵活性。通过以上直接是tar
的更改,现在可以单独访问每个覆盖。我们现在可以灵活地根据需要拥有多个覆盖,以便优化更新用例。在最精细的程度上,每个库可以存在于单独的覆盖中,并且如果单个库发生更改,那么这是机器人必须下载的唯一更改(如果它是最新的)。这为远程设备节省了大量时间和带宽。 - 将
overlays.json
添加到元数据中。 此文件包含要应用于工作空间的有序覆盖列表,以及可用于比较覆盖版本的哈希值。此元数据允许具有任意数量的覆盖,但确保按顺序正确设置环境。哈希值让智能下载和提取成为可能,因为哈希值改变意味着特定覆盖已更改。
总的来说,我们对这些改进非常满意。如果您有任何想法,不妨在 GitHub 上的问题部分提出,我们很乐意听取您的意见。
colcon bundle
执行流
在这个部分,我将详细介绍执行 colcon bundle
时实际会发生什么。(在 colcon
之上构建我们的工具是一次很棒的体验。如果您希望创建专门的构建工具,我强烈建议您试试 colcon
,看看它是否可以用作基础框架。)
colcon
生态系统由单独的 Python 软件包组成,这些软件包只以直接依赖项和 colcon-core 作为基础。colcon
的几乎所有功能都被建模为 colcon-core 中的基础架构定义的 ExtensionPoints
。colcon
利用 setuptoolsentry points 作为其插件机制,来实现其模块化和松散耦合的目标。
要扩展命令行界面,colcon
支持通过 VerbExtensionPoints
添加命令。为了构建我们的功能,我们创建了 bundle
谓词,并在 setup.cfg
中进行了注册。这就是将集成命令并入这个工具所要做的一切。(之后的所有工作都是针对我们的域名。) 如果您希望围绕您的 ROS 工作流构建工具,请使用 colcon
! 强烈推荐。
一旦调用了 colcon bundle
,就会发生一系列操作,其中一些由 colcon
基础架构提供,还有一些由我们自己编写。我将在这里进行概述,但我鼓励您深入研究代码,以进一步了解所有东西是如何工作的。
- 调用
VerbExtensionPoint
、BundleVerb
。我们获得context
,其中包含我们注册的所有参数以及来自其他扩展点的参数。 - 然后,我们调用一些
colcon-core
功能来抓取工作区文件树并识别软件包。然后,为要包含在bundle
执行中的每个软件包创建PackageDescriptor
。使用 PackageIdentificationExtensionPoints 完成软件包识别。注册用于识别 ROS 软件包的扩展是 colcon-ros 中定义的 RosPackageIdentification。当它识别 ROS 软件包时,它将PackageDescriptor
中的type
设置为ros.cmake
、ros.catkin
或ros.ament
。 - 一旦识别出所有软件包,
colcon
会执行已为给定软件包类型注册的任何PackageAugmentationExtensionPoints
。除了识别之外,RosPackageIdentification
还提供了一个增强功能,可用于解析软件包的package.xml
,并将软件包构建、测试和运行时依赖项添加到软件包描述符元数据中。 - 回到我们的代码,我们检查附加到每个
PackageDescriptor
的依赖项元数据,以确定自上次调用以来是否有任何更改。如果没有任何改变,我们跳过进一步的依赖项分析,并继续将构建的工作区与现有的依赖项覆盖进行捆绑。如果我们确定依赖项发生改变,我们将继续进行完整的依赖项分析。 - 在完整的依赖项分析期间,对于每个
PackageDescriptor
,我们调用其相应的捆绑任务。我们目前支持ros.catkin
、ros.cmake
和python
软件包类型。此支持已在colcon-bundle/setup.cfg
和colcon-ros-bundle/setup.cfg
中注册。可以通过添加相应的捆绑任务来支持新的软件包类型。对于每个软件包类型,相应的软件包任务收集依赖项,然后为每个依赖项调用适当的BundleInstallerExtensionPoint
。我们的 ROS 任务使用rosdep
将 ROS 依赖项名称转换为匹配的系统软件包名称。 - 一旦所有依赖项都已在安装程序中注册并且已确定完整的传递闭包,我们会将黑名单应用于系统软件包列表,因为我们不想安装
libc*
、gazebo*
以及基础架构提供的其他软件包。如果要覆盖黑名单的行为,可以使用--apt-package-blacklist
参数提供自己的黑名单。 - 在应用黑名单之后,我们会在所有已注册的
BundleInstallerExtensionPoints
(依赖项注册步骤中使用的)上调用 `install()`。安装程序将所有内容安装到--bundle-base
(默认:bundle)目录中的目录。下载和安装完成后,我们将复制setup.sh
,它可以允许我们使用覆盖,然后tar
和gzip
文件夹为dependencies.tar.gz
。 - 在依赖项存档后,我们存档最新构建的
install
文件夹。我们tar
和gzip
整个--install-base
(默认:install)目录到一个具有setup.sh
的单独覆盖中,以启用覆盖。 - 创建两个存档后,我们将收集捆绑过程中生成的所有元数据,并添加描述覆盖大小、偏移和排序的元数据。然后我们 `tar` 并将该元数据压缩为
metadata.tar.gz
。 - 然后我们将各种存档
tar
在一起,以创建“bundle”并将其写入磁盘位置bundle/output.tar
。该 bundle 现在可在 AWS RoboMaker 上本地使用。
使用 Bundle 运行应用程序
Bundle 格式可以在本地使用,就像在 AWS RoboMaker 上使用它一样。这样做可能对您的应用程序很有用,或者只是作为调试工具。我们将很快发布我们构建的 GoLang 库,以便在我们的服务中使用 colcon bundle
存档。在此我对我们目前读取和使用 colcon bundle
存档的步骤做个总结:
- 从存档中提取
version
和metadata.tar.gz
。 - 检查
version
以查看我们应该使用的兼容模式(从此步骤开始,我将假设 bundle 的版本为 2.0)。 - 从
metadata.tar.gz
中提取overlays.json
。 - 解析
overlays.json
以收集要提取到磁盘的存档列表。overlays.json
包含覆盖的sha256
哈希值。这些哈希值用于确定覆盖是否发生改变以及是否应重新提取。它们还可用于验证覆盖的完整性。 - 将覆盖提取到磁盘后,需要按照
overlays.json
中列出的顺序将其导入环境。要在环境中添加覆盖,请执行:BUNDLE_CURRENT_PREFIX=<path_to overlay_folder> source <path_to_overlay_folder>/setup.sh
。BUNDLE_CURRENT_PREFIX
环境变量是必需的,因为在获取文件时,我们无法确定文件的位置。 - 现在,您的环境已完成设置,可以像 ROS 工作区和 bundle 中的依赖项就安装在本地一样执行。
总结
我们对格式和工具的最新迭代感到满意,并将在此基础上继续改进。colcon bundle
实现可扩展到更多软件包类型和安装程序。如果您有兴趣扩展功能,我们提出有关 colcon-bundle
和 colcon-ros-bundle
的问题,以及参与开发。如果您想在 colcon
之上构建自己的工具,请查看 colcon 参与开发文档。