开发容器指南

在本指南中,我们将逐步介绍为该项目创建和使用dev容器的过程。尽管包含的子部分将提供有关该过程各个方面的更详细信息,但并非必须完全理解整个指南即可开始使用,但对于那些对dev容器的工作原理、如何自定义和优化它们以适应自己的个人工作流程感兴趣的人来说,建议阅读完整的指南。

创建开发容器

在创建dev容器之前,您需要选择要使用的确切配置。默认情况下选择.devcontainer/devcontainer.json配置,但您也可以选择.devcontainer/目录中的任何其他devcontainer.json文件,这样可以嵌套这样的配置以提供更大的自定义:可以通过针对不同的Dockerfile内的不同阶段,覆盖任何已合并的元数据或默认属性,或包含其他扩展和备用命令来进行自定义。

参见

有关 devcontainer.json 配置文件格式的规范、参考和模式,请参阅此处:

  • 规范

    • 开发容器规范

  • 参考

    • 元数据和属性参考

  • 模式

    • 用于 devcontainer.json 的 JSON 模式

构建镜像

当首次创建 Dev Containers 时,任何使用的支持工具或服务将下载并构建所需的 Docker 镜像以运行容器。这包括拉取项目的 Dockerfile 构建中所声明的任何父镜像(FROM),以及通过 cacheFrom 在选择的 devcontainer.json 配置文件中声明的任何标签或层。这可能需要一些时间,但只需要执行一次,或者至少在这些层被更新并推送到镜像注册表之前不需要再次执行。

特别是对于这个项目,默认的devcontainer.json文件针对项目根目录中的dever阶段的Dockerfile进行了配置,该阶段还包括用于开发项目的实用工具,如bash自动完成。而这个阶段是从builder阶段构建的,builder阶段仅包括构建项目所需的依赖项,由项目的CI重复使用。例如,dever阶段修改了/etc/bash.bashrc以自动从基础工作区中源install/setup.bash,确保所有VS Code扩展都以正确的环境加载,同时避免安装和启动期间的任何竞争条件。

为了加快初始构建速度,从这个 builder 阶段的镜像层通过拉取与项目的 CI 使用的相同镜像标签进行缓存,该标签从镜像注册表中托管。这确保您的本地开发容器尽可能地复制我们的 CI 环境,同时从 CI 预先执行的任何缓存工作中获益。然而,这仍然允许您自定义项目的 Dockerfile 并重新构建容器,而无需更新 CI 镜像以反映您的本地修改。

参见

可以在这里找到关于该项目的CI和相关的Docker镜像注册表的更多详细信息:

一旦构建了目标阶段的基础镜像,支持工具或服务可以向镜像添加其他层,例如安装其他功能或定制内容。对于 VS Code,这还包括一些用于稍后安装的扩展的文件缓存。构建此自定义镜像后,将使用它来启动开发容器。

启动容器

当首次创建 Dev Containers 时,任何支持工具或服务都将调用在选择的 devcontainer.json 配置文件中指定的一系列命令。这可能需要一些时间,但只需要执行一次,或者至少在重新构建容器之前不需要再次执行,可以通过更新 Dockerfile、基础镜像或 .devcontainer/ 配置来触发重新构建。

具体来说,对于这个项目,默认的devcontainer.json配置执行onCreateCommand,以便为项目的覆盖工作空间进行初始的colcon缓存、清理和构建。这确保了工作空间被预编译并准备就绪,同时还确保了对项目源代码的任何更改都会反映在容器中。这对于以下情况很有用:

  • IntelliSense

    • 使得VS Code扩展能够解析自动生成的代码

    • 适用于定义消息和服务文件的ROS软件包

    • 在代码建模、导航和语法高亮方面是必需的

  • 缓存

    • 使得Codespace预构建能够缓存工作空间的构建产物

    • 适用于在生成新的Codespaces时减少启动时间

    • 限制CPU和存储使用的成本是必需的

当构建colcon工作空间时,VS Code会同时安装任何指定的扩展和设置。接下来执行updateContentCommand命令,该命令在容器启动或重新启动时重新运行。具体来说,在这个项目中,该命令会重新清理和重新构建与之前相同的colcon工作空间,但仅适用于通过在onCreateCommand期间初始化的lockfiles检测到的无效包。这种缓存行为还复制了项目的CI工作流程。这对于以下情况非常有用:

  • 分支管理

    • 在切换分支时启用工作空间构建的缓存

    • 适用于在不重新构建整个容器的情况下审核拉取请求

    • 在生成新的Codespaces时减少启动时间所必需

提示

这些额外的colcon动词扩展的更多文档可以在这里找到:

  • colcon-cache

    • 一个colcon扩展,用于缓存软件包的处理

  • colcon-clean

    • 一个colcon扩展,用于清理软件包工作空间

最后,执行postCreateCommand,每当容器启动或重新启动时都会重新运行该命令。对于这个项目,该命令对用户的环境进行最后的微调,以提高开发体验。

为了加快后续的启动速度,被挂载到容器的卷会存储一个持久的ccache和colcon工作空间,同时环境被设置为通过colcon mixins启用ccache(参见colcon mixins)。这些卷使用devcontainerId变量进行标记,该变量在Docker主机上唯一标识开发容器,使我们能够引用一个对开发容器唯一且在重建过程中保持稳定的常见标识符。这对于以下情况很有用:

  • 缓存

    • 使colcon工作空间和ccache在容器重建之间持久存在

    • 适用于修改开发容器配置文件时避免重新编译

    • 在不必从头开始重新构建的情况下快速自定义映像或功能所必需的

小技巧

虽然这些卷具有唯一的名称,但您可以在本地将它们重命名,以进一步组织或分割正在进行中的工作。例如,将分支名称附加到卷名称,以快速切换不同的拉取请求和缓存的colcon工作空间。

此外,容器可以被授予privileged和非默认的Linux capabilities,使用host网络模式以及IPCPID空间进行连接,具有放宽的security configuration和seccomp限制,用于本机调试和外部连接。这对于以下情况非常有用:

  • 混合式开发

    • 使ROS节点可以连接到容器外部

    • 适用于调试或可视化分布式系统

    • DDS发现和共享内存传输所必需的

  • 设备连接性

    • 允许从主机向容器进行硬件转发

    • 适用于使用传感器和执行器的ROS软件包

    • 对于一些GPU驱动程序和USB设备是必需的

注意

devcontainer.json配置中,可以启用或自定义runArgs,可以扩展或限制范围,以更好地适应您所需的开发环境。默认配置仅仅注释掉这些参数,以限制意外的副作用或容器之间的交互,但可以取消注释以适应最广泛的开发用例。

参见

关于在Docker容器中使用DDS、调试器或设备的更多详细信息可以在这里找到:

使用 Dev Containers

一旦开发容器被创建并设置完成,VS Code将直接从项目的根目录打开一个新的工作区,该工作区本身在覆盖colcon工作空间中的源目录中挂载。从这里,您可以像通常一样构建、测试和调试项目,同时还可以享受项目的依赖项、智能感知、代码检查器和其他扩展的预配置和准备就绪。只需打开一个新的终端(Ctrl+Shift+`),cd到colcon工作空间的根目录,然后运行常规的colcon命令。

小技巧

您可以将与 devcontainer.json 配置文件相同的脚本纳入到您的本地开发工作流程中,以进一步自动化。

终端

如果您喜欢使用替代的终端模拟器,而不是内置的 VS Code 终端,您可以通过使用 Dev Container CLI 或直接使用 Docker CLI 的 exec 子命令来打开一个单独的 shell 会话。

注意

通过直接使用 docker exec 生成的 Shell 会话不会设置与使用 userEnvProbedevcontainer exec 相同的环境。其他环境变量包括 REMOTE_CONTAINERS_IPCREMOTE_CONTAINERS_SOCKETS,并且会被 vscode、ssh 和 X11 使用。

生命周期

在使用开发容器时,请记住容器本身的生命周期。具体来说,容器是暂时的,意味着它们通常在重建或更新开发环境时被销毁和重新创建。因此,最佳实践是避免在容器内存储任何持久数据,而是利用项目的源目录或单独的挂载卷。在容器内修改开发环境时,尽量记住将更改编码到 Dockerfile 或 devcontainer.json 配置文件中,以便轻松地复制和共享给他人。

重要

当主机机器本身是暂时性的时候,这一点尤其重要,比如在使用云环境(如 Codespaces)时,请务必提交并推送本地更改到远程存储库:

重建中

有时候,您可能需要重新构建开发容器,无论是因为基本镜像还是.devcontainer/配置文件的更新,或者仅仅是想要一个新的开发环境。要这样做,只需打开命令面板(Ctrl+Shift+P),然后选择Remote-Containers: Rebuild Container命令。

小心

重建容器将会销毁对容器本身所做的任何更改,如安装附加软件包或修改环境。但项目的源目录和任何挂载的卷将保持不受影响。

例如,当以下情况发生时,您可能需要重新构建开发容器:

  • 从容器注册表拉取更新的镜像

    • 具体而言,是在 Dockerfile 中构建的图像标签 FROM

    • 或在 devcontainer.json 中的 cacheFrom 下列出的标签

    • 定期手动执行以确保本地环境反映 CI

  • 更新开发容器配置

    • 具体而言,是在 Dockerfile 中修改依赖阶段时

    • 或在修改 ./devcontainer 文件和命令时

    • 构建缓存重用与所做更改的严重程度相关

如果需要,您还可以选择Remote-Containers: Rebuild Container Without Cache命令从头开始重新构建容器,即不使用docker缓存。这将从docker buildx命令中省略--cache-from标志,同时添加--no-cache--pull标志,以防止使用任何现有镜像层的缓存,并仅使用容器注册表中的最新镜像。

小心

重新构建容器时,可能会从容器注册表中拉取更新的镜像或安装更新的软件包,这在开发 ROS 2 Rolling 时很常见。然后,您可能需要清理覆盖卷,以避免ABI不兼容或过时的构件。

当需要重新构建而不使用缓存时可能需要这样做:

  • 需要更新基础镜像

    • 特别是如果开发容器配置保持不变

    • 以强制重新运行 Dockerfile 中的 RUN 指令

    • 例如未更改的apt upgraderosdep update命令

具体而言,对于此项目,卷不受此重建过程的影响,例如用于挂载 ccache 目录或 colcon 工作区的卷。虽然卷的管理由用户自行决定,但其他项目可能会以不同的方式处理,因此请务必检查 ./devcontainer 配置以了解如何管理各种容器资源。

小技巧

可以通过Docker CLI或VS Code Docker扩展来管理Docker卷: