尝试以扩展 VS Code 中的代理模式!

自定义开发容器功能

2022 年 9 月 15 日,由 Brigit Murtaugh (@BrigitMurtaugh) 发表

我们都曾有过在设置开发环境时这样的时刻——“哦,我只需要再多一样东西!”——这个“东西”指的是再多一种语言或工具集(或者可能再多几样 😊),以便在你的项目上工作。

开发容器是简化环境设置的好方法——它们提供了一个完整的编码环境,包含了项目所需的工具。它们通过一个镜像、Dockerfile 或 Docker Compose 文件以及 devcontainer.json 来配置,后者是一种元数据格式,用于为容器丰富开发特定的内容和设置。

在创建开发容器时,你可能会反复遇到同样的“我只需要再多一样东西!”的反应——也许你在 Dockerfile 中使用了一个 Node.js 镜像,而只是需要添加 Git。或者你可能需要添加更复杂的东西,比如在你的开发容器内使用 Docker 或 Kubernetes。开发容器很棒,因为任何访问你代码的人都将获得与你添加的所有这些工具相同且一致的体验——但添加它们的最佳方式是什么?

如果有一种简单的方法,只需提及工具的名称和版本,就能在你的开发容器中安装那个额外的工具,那会怎么样?或者,作为工具使用者或作者,你是否可以创建一种简便的方式让其他人安装它?共享手动脚本有助于重用,但在引用时,你可能会忘记引用容器或工具的设置,例如为 Go、Rust 或 C++ 调试启用 ptrace 支持,添加一个在容器启动时触发的特定入口点,或确保包含了正确的 VS Code 扩展。

功能

我们很高兴地宣布,开发容器的“**功能**”(Features) 能帮助你顺利地在开发容器中获得所需的工具!

“功能”是自包含的单元,包含了安装代码、容器配置和/或设置及扩展,旨在为你的开发容器带来新的开发能力。它们可以被构建以适配各种基础容器镜像。作为我们关于开放开发容器规范工作的一部分,我们对获取预创建“功能”的途径以及如何创作和分发你自己的“功能”进行了一些改进。

让我们来看看有哪些新内容,以及你如何可以从任何支持开发容器的工具或服务(例如 VS Code 开发容器扩展或 GitHub Codespaces)开始使用“功能”!

向你的开发容器添加“功能”

开发容器“功能”提供了一种将开发容器元数据与一些安装步骤快速关联起来的方法。你可以通过简单的引用将它们添加到你的开发容器中。

这些“功能”现在可以作为 OCI 工件存储在任何支持的容器注册表中,这意味着你可以使用与引用容器镜像相同类型的标识符来引用它们。我们已经将一些早期位于 vscode-dev-containers 仓库中的“功能”迁移到了一个新的 devcontainers/features 仓库,并在那里使用这种新方法进行发布。

从 devcontainers/features 仓库引用不同的“功能”非常简单,只需在你的 devcontainer.json 中添加一个 features 属性即可。每个“功能”都有一个 README.md 文件,其中展示了如何引用该“功能”以及它有哪些可用选项。

下面的示例安装了 godocker-in-docker “功能”。

"name": "my-project-devcontainer",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
    "ghcr.io/devcontainers/features/go:1": {
        "version": "1.18"
    },
    "ghcr.io/devcontainers/features/docker-in-docker:1": {
        "version": "latest",
        "moby": true
    }
}

你还可以在规范网站上探索官方和社区贡献的“功能”。任何“功能”都可以通过编辑 devcontainer.json 来添加,而公开发布的“功能”则可以通过现有的开发容器配置体验(例如 VS Code 开发容器扩展中提供的功能)来添加。

Specification site list of available Features

你甚至可以从你喜欢的 CI 系统中使用带有“功能”的开发容器,方法是使用开发容器 CLI、GitHub Action 或 Azure DevOps 任务。我们在 devcontainers/ci 仓库中提供了 GitHub Action 和 Azure DevOps 任务。开发容器 CLI、GitHub Action 或 Azure DevOps 任务也可以用于预构建包含“功能”内容的镜像,以加快启动时间。

如果你不仅想使用公开可用的“功能”,还想创建自己的私有或公共“功能”并与他人分享,请继续阅读!

创作

开始创建你自己的“功能”的一个好地方是新的 “功能”模板仓库。除了为给定“功能”的内容提供一个好的模板外,该模板还包含一个 GitHub Actions 工作流,用于快速发布它们,它会使用你账户的 GitHub 容器注册表 (GHCR) 让你尽快上手。我们稍后会详细讨论发布。

一个“功能”的源代码包含两个部分:一个安装脚本 (install.sh) 和一个配置文件 (devcontainer-feature.json)。

+-- feature
|    +-- devcontainer-feature.json
|    +-- install.sh
|    +-- (other files)

install.sh:安装入口点脚本——它在概念上被添加为镜像 Dockerfile 的一个层,并在构建时执行。这个入口点脚本可以安装各种工具,如语言(例如 Ruby)和工具(GitHub CLI)。

devcontainer-feature.json:它包含了关于“功能”的元数据,一组可以在安装期间传递给“功能”安装脚本的选项,以及将合并到最终开发容器中的 devcontainer.json “片段”。例如,如果任何“功能”在其配置中指明了 "privileged": true,那么整个开发容器将以 --privileged 标志启动。

“功能”可以用多种语言编写,最直接的是 shell 脚本。如果一个“功能”是用其他语言编写的,应该在元数据中包含相关信息,以便用户可以做出明智的选择。

注意: 虽然 install.sh 会运行任何语言的“功能”,但如果你用一种解释型语言编写“功能”,而该语言在开发容器中不存在,那么代码将无法执行。请确保在 install.sh 中获取你需要的语言。

你应该确保公开发布的“功能”除了安装自身外,还会检查并安装其依赖项。

此外,公共“功能”很可能会在 arm64 和 x86_64 两种架构的机器上使用——所以请确保在可能的情况下进行适配。

你可以在规范中查看 devcontainer-feature.json 的属性,以及在 devcontainers/features 仓库中查看公共示例。

现在我们已经了解了如何创建一个“功能”,那么我该如何将它分发给其他人呢?

分发

“功能”以 tarball 的形式分发。tarball 包含了“功能”子目录的全部内容,包括 devcontainer-feature.jsoninstall.sh 以及目录中的任何其他文件。

开放容器倡议 (OCI) 定义了容器和容器资源的行业标准。我们将“功能”视为 OCI 工件,并使用 OCI 注册表的概念来分发“功能”。

上面提到的 “功能”模板仓库包含一个 GitHub Actions 工作流来自动化发布过程。它将“功能”打包成一个 tarball,并将这些资产作为 OCI 工件发布到 GHCR。要触发模板仓库中的 release.yaml 工作流,只需在 GitHub 上仓库的“Actions”选项卡左侧选择它即可。该 GitHub Action 会将“功能”发布到 GHCR 的 <owner>/<repo> 命名空间下。“功能”只有在其 devcontainer-feature.json 中的版本属性更新后才会被重新发布。

注意: 使用 GHCR 时有一个手动步骤,即将 OCI 包标记为“公共”。每个“功能”只需执行一次。私有“功能”不需要此步骤,只要你已使用注册表的凭据登录到 Docker CLI,就可以访问它们。

与社区分享你的“功能”

如果你希望你的贡献出现在 VS Code 开发容器GitHub Codespaces 创建开发容器的 UI 中,你可以执行以下步骤:

一旦合并,你的更改将出现在 containers.dev/collections

“功能”安装顺序

如果我的“功能”应该在另一个“功能”之后安装怎么办?作为“功能”的作者,你可能会发现你的“功能”应该在其他“功能”之前或之后安装。在你的 devcontainer-feature.json 中,你可以使用 installsAfter 属性来列出应该在它之前执行的“功能”。

作为最终用户,你可以使用 devcontainer.json 中的 overrideFeatureInstallOrder 属性来进一步控制执行顺序。此数组中的任何“功能”ID 都将按提供的顺序在所有其他“功能”之前安装。例如:

"features": {
      "ghcr.io/devcontainers/features/java:1",
      "ghcr.io/devcontainers/features/node:1",
  },
  "overrideFeatureInstallOrder": [
    "ghcr.io/devcontainers/features/node"
  ]

默认情况下,“功能”会按照实现工具确定的最佳顺序安装在基础镜像之上。

如果在“功能”的 devcontainer-feature.json 或用户的 devcontainer.json 中提供了以下任何属性,则会遵循这些属性所指示的顺序(优先级递减)。

  1. 用户 devcontainer.json 中的 overrideFeatureInstallOrder 属性。允许用户控制其“功能”的执行顺序。
  2. 作为“功能” devcontainer-feature.json 一部分定义的 installsAfter 属性。

你可以在规范中阅读更多关于“功能”执行和安装顺序的信息。

还有什么新内容?

除了新的“功能”仓库,我们最近还开源了一个新的 devcontainers/images 仓库,其中托管了一组特定的镜像,这些镜像以前位于 vscode-dev-containers 仓库中。

我们正在为开发容器模板(在 vscode-dev-containers 中我们称之为“定义”)制定一个社区分发计划,我们预计它将与“功能”类似。我们一定会在 vscode-dev-containers 仓库中发布更新,就像我们宣布新的“功能”和镜像仓库时那样。

我如何了解更多?

这篇文章只是触及了“功能”能做什么的皮毛,我们很期待你来尝试!

正如上文内容中链接的那样,了解“功能”的构成以及如何分发它们的最佳地点是开发容器规范的“功能”“功能”分发页面。

我们期待在你使用、创建和发布“功能”时收到你的反馈——我们很想在“功能”“功能”分发的议题提案中听到它们对你的作用。

如果你有兴趣全面参与规范的制定,或者将另一个工具接入以利用它,请查看开发容器规范CLI仓库。

祝你创建开发容器愉快,编码愉快!

Brigit Murtaugh, @BrigitMurtaugh